[DRE-commits] [ruby-diaspora-vines] 01/08: Imported Upstream version 0.2.0.develop.4

Praveen Arimbrathodiyil praveen at moszumanska.debian.org
Tue Jun 7 08:58:15 UTC 2016


This is an automated email from the git hooks/post-receive script.

praveen pushed a commit to branch master
in repository ruby-diaspora-vines.

commit 42da6ee05763349ddd95dc2c628addff5efa69e7
Author: sudheesh <sudheeshshetty at gmail.com>
Date:   Wed Feb 3 08:55:58 2016 +0530

    Imported Upstream version 0.2.0.develop.4
---
 Gemfile                                            |    5 +
 LICENSE                                            |   19 +
 README.md                                          |   10 +
 Rakefile                                           |   23 +
 bin/vines                                          |    4 +
 checksums.yaml.gz                                  |  Bin 0 -> 269 bytes
 conf/certs/README                                  |   39 +
 conf/certs/ca-bundle.crt                           | 3895 ++++++++++++++++++++
 conf/config.rb                                     |   50 +
 lib/vines.rb                                       |  216 ++
 lib/vines/cli.rb                                   |  103 +
 lib/vines/cluster.rb                               |  246 ++
 lib/vines/cluster/connection.rb                    |   26 +
 lib/vines/cluster/publisher.rb                     |   55 +
 lib/vines/cluster/pubsub.rb                        |   92 +
 lib/vines/cluster/sessions.rb                      |  125 +
 lib/vines/cluster/subscriber.rb                    |  133 +
 lib/vines/command/cert.rb                          |   50 +
 lib/vines/command/restart.rb                       |   12 +
 lib/vines/command/start.rb                         |   28 +
 lib/vines/command/stop.rb                          |   18 +
 lib/vines/config.rb                                |  236 ++
 lib/vines/config/diaspora.rb                       |   37 +
 lib/vines/config/host.rb                           |  137 +
 lib/vines/config/port.rb                           |  132 +
 lib/vines/config/pubsub.rb                         |  108 +
 lib/vines/contact.rb                               |  115 +
 lib/vines/daemon.rb                                |   78 +
 lib/vines/error.rb                                 |  150 +
 lib/vines/jid.rb                                   |   95 +
 lib/vines/kit.rb                                   |   30 +
 lib/vines/log.rb                                   |   28 +
 lib/vines/node.rb                                  |   31 +
 lib/vines/router.rb                                |  184 +
 lib/vines/stanza.rb                                |  175 +
 lib/vines/stanza/dialback.rb                       |   28 +
 lib/vines/stanza/iq.rb                             |   48 +
 lib/vines/stanza/iq/auth.rb                        |   18 +
 lib/vines/stanza/iq/disco_info.rb                  |   45 +
 lib/vines/stanza/iq/disco_items.rb                 |   29 +
 lib/vines/stanza/iq/error.rb                       |   16 +
 lib/vines/stanza/iq/ping.rb                        |   16 +
 lib/vines/stanza/iq/private_storage.rb             |   83 +
 lib/vines/stanza/iq/query.rb                       |   10 +
 lib/vines/stanza/iq/result.rb                      |   16 +
 lib/vines/stanza/iq/roster.rb                      |  140 +
 lib/vines/stanza/iq/session.rb                     |   17 +
 lib/vines/stanza/iq/vcard.rb                       |   56 +
 lib/vines/stanza/iq/version.rb                     |   25 +
 lib/vines/stanza/message.rb                        |   43 +
 lib/vines/stanza/presence.rb                       |  182 +
 lib/vines/stanza/presence/error.rb                 |   23 +
 lib/vines/stanza/presence/probe.rb                 |   37 +
 lib/vines/stanza/presence/subscribe.rb             |   42 +
 lib/vines/stanza/presence/subscribed.rb            |   51 +
 lib/vines/stanza/presence/unavailable.rb           |   15 +
 lib/vines/stanza/presence/unsubscribe.rb           |   38 +
 lib/vines/stanza/presence/unsubscribed.rb          |   38 +
 lib/vines/stanza/pubsub.rb                         |   22 +
 lib/vines/stanza/pubsub/create.rb                  |   39 +
 lib/vines/stanza/pubsub/delete.rb                  |   41 +
 lib/vines/stanza/pubsub/publish.rb                 |   66 +
 lib/vines/stanza/pubsub/subscribe.rb               |   44 +
 lib/vines/stanza/pubsub/unsubscribe.rb             |   30 +
 lib/vines/storage.rb                               |  291 ++
 lib/vines/storage/local.rb                         |  151 +
 lib/vines/storage/null.rb                          |   51 +
 lib/vines/storage/sql.rb                           |  346 ++
 lib/vines/store.rb                                 |  152 +
 lib/vines/stream.rb                                |  309 ++
 lib/vines/stream/client.rb                         |   88 +
 lib/vines/stream/client/auth.rb                    |   74 +
 lib/vines/stream/client/auth_restart.rb            |   29 +
 lib/vines/stream/client/bind.rb                    |   64 +
 lib/vines/stream/client/bind_restart.rb            |   30 +
 lib/vines/stream/client/closed.rb                  |   13 +
 lib/vines/stream/client/ready.rb                   |   17 +
 lib/vines/stream/client/session.rb                 |  210 ++
 lib/vines/stream/client/start.rb                   |   27 +
 lib/vines/stream/client/tls.rb                     |   38 +
 lib/vines/stream/component.rb                      |   58 +
 lib/vines/stream/component/handshake.rb            |   26 +
 lib/vines/stream/component/ready.rb                |   23 +
 lib/vines/stream/component/start.rb                |   19 +
 lib/vines/stream/http.rb                           |  185 +
 lib/vines/stream/http/auth.rb                      |   22 +
 lib/vines/stream/http/bind.rb                      |   32 +
 lib/vines/stream/http/bind_restart.rb              |   37 +
 lib/vines/stream/http/ready.rb                     |   29 +
 lib/vines/stream/http/request.rb                   |  193 +
 lib/vines/stream/http/session.rb                   |  128 +
 lib/vines/stream/http/sessions.rb                  |   65 +
 lib/vines/stream/http/start.rb                     |   23 +
 lib/vines/stream/parser.rb                         |   79 +
 lib/vines/stream/sasl.rb                           |  128 +
 lib/vines/stream/server.rb                         |  207 ++
 lib/vines/stream/server/auth.rb                    |   30 +
 lib/vines/stream/server/auth_method.rb             |   66 +
 lib/vines/stream/server/auth_restart.rb            |   39 +
 lib/vines/stream/server/final_restart.rb           |   21 +
 lib/vines/stream/server/outbound/auth.rb           |   65 +
 .../stream/server/outbound/auth_dialback_result.rb |   39 +
 lib/vines/stream/server/outbound/auth_external.rb  |   33 +
 .../stream/server/outbound/auth_external_result.rb |   32 +
 lib/vines/stream/server/outbound/auth_restart.rb   |   27 +
 lib/vines/stream/server/outbound/authoritative.rb  |   48 +
 lib/vines/stream/server/outbound/final_features.rb |   28 +
 lib/vines/stream/server/outbound/final_restart.rb  |   20 +
 lib/vines/stream/server/outbound/start.rb          |   20 +
 lib/vines/stream/server/outbound/tls_result.rb     |   34 +
 lib/vines/stream/server/ready.rb                   |   24 +
 lib/vines/stream/server/start.rb                   |   40 +
 lib/vines/stream/state.rb                          |   46 +
 lib/vines/token_bucket.rb                          |   55 +
 lib/vines/user.rb                                  |  125 +
 lib/vines/version.rb                               |    6 +
 lib/vines/xmpp_server.rb                           |   25 +
 metadata.yml                                       |  456 +++
 test/cluster/publisher_test.rb                     |   57 +
 test/cluster/sessions_test.rb                      |   47 +
 test/cluster/subscriber_test.rb                    |  111 +
 test/config/host_test.rb                           |  358 ++
 test/config/pubsub_test.rb                         |  187 +
 test/config_test.rb                                |  753 ++++
 test/contact_test.rb                               |  102 +
 test/error_test.rb                                 |   58 +
 test/ext/nokogiri.rb                               |   14 +
 test/jid_test.rb                                   |  147 +
 test/kit_test.rb                                   |   31 +
 test/router_test.rb                                |  243 ++
 test/stanza/iq/disco_info_test.rb                  |   80 +
 test/stanza/iq/disco_items_test.rb                 |   49 +
 test/stanza/iq/private_storage_test.rb             |  184 +
 test/stanza/iq/roster_test.rb                      |  229 ++
 test/stanza/iq/session_test.rb                     |   25 +
 test/stanza/iq/vcard_test.rb                       |  146 +
 test/stanza/iq/version_test.rb                     |   64 +
 test/stanza/iq_test.rb                             |   70 +
 test/stanza/message_test.rb                        |  127 +
 test/stanza/presence/probe_test.rb                 |   50 +
 test/stanza/presence/subscribe_test.rb             |   83 +
 test/stanza/pubsub/create_test.rb                  |  116 +
 test/stanza/pubsub/delete_test.rb                  |  169 +
 test/stanza/pubsub/publish_test.rb                 |  309 ++
 test/stanza/pubsub/subscribe_test.rb               |  205 ++
 test/stanza/pubsub/unsubscribe_test.rb             |  148 +
 test/stanza_test.rb                                |   85 +
 test/storage/local_test.rb                         |   59 +
 test/storage/mock_redis.rb                         |   97 +
 test/storage/null_test.rb                          |   29 +
 test/storage/sql_schema.rb                         |  186 +
 test/storage/sql_test.rb                           |  290 ++
 test/storage/storage_tests.rb                      |  182 +
 test/store_test.rb                                 |  164 +
 test/stream/client/auth_test.rb                    |  137 +
 test/stream/client/ready_test.rb                   |   47 +
 test/stream/client/session_test.rb                 |   27 +
 test/stream/component/handshake_test.rb            |   52 +
 test/stream/component/ready_test.rb                |  103 +
 test/stream/component/start_test.rb                |   39 +
 test/stream/http/auth_test.rb                      |   70 +
 test/stream/http/ready_test.rb                     |   86 +
 test/stream/http/request_test.rb                   |  194 +
 test/stream/http/sessions_test.rb                  |   49 +
 test/stream/http/start_test.rb                     |   50 +
 test/stream/parser_test.rb                         |  122 +
 test/stream/sasl_test.rb                           |  195 +
 test/stream/server/auth_method_test.rb             |  124 +
 test/stream/server/auth_test.rb                    |   70 +
 .../server/outbound/auth_dialback_result_test.rb   |   52 +
 test/stream/server/outbound/auth_external_test.rb  |  105 +
 test/stream/server/outbound/auth_restart_test.rb   |   77 +
 test/stream/server/outbound/auth_test.rb           |  113 +
 test/stream/server/outbound/authoritative_test.rb  |   86 +
 test/stream/server/outbound/start_test.rb          |   45 +
 test/stream/server/ready_test.rb                   |  122 +
 test/stream/server/start_test.rb                   |  105 +
 test/test_helper.rb                                |   51 +
 test/token_bucket_test.rb                          |   44 +
 test/user_test.rb                                  |  101 +
 180 files changed, 19886 insertions(+)

diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..e88e5df
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,5 @@
+source "https://rubygems.org"
+
+gemspec
+
+gem 'pronto', :git => 'https://github.com/Zauberstuhl/pronto.git'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..778f659
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-2014 Negative Code
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3f4b086
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+Diaspora XMPP Integration
+=========================
+
+**master** [ ![Build Status](https://travis-ci.org/diaspora/vines.svg?branch=master) ](https://travis-ci.org/diaspora/vines)  
+**develop** [ ![Build Status](https://travis-ci.org/diaspora/vines.svg?branch=develop) ](https://travis-ci.org/diaspora/vines)
+
+This XMPP server was forked from [Negativecode](http://www.getvines.org/)  
+and was slimmed down for [Diaspora](https://diasporafoundation.org) usage only!
+
+**DO NOT** use this vines version unless you know what you're doing!!
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..2b549f9
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,23 @@
+# encoding: UTF-8
+
+require 'rake'
+require 'rake/clean'
+require 'rake/testtask'
+
+CLOBBER.include('pkg')
+
+directory 'pkg'
+
+desc 'Build distributable packages'
+task :build => [:pkg] do
+  system 'gem build vines.gemspec && mv vines-*.gem pkg/'
+end
+
+Rake::TestTask.new(:test) do |test|
+  test.libs << 'test'
+  test.libs << 'test/storage'
+  test.pattern = 'test/**/*_test.rb'
+  test.warning = false
+end
+
+task :default => [:clobber, :test, :build]
diff --git a/bin/vines b/bin/vines
new file mode 100755
index 0000000..dd89747
--- /dev/null
+++ b/bin/vines
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require 'vines'
+Vines::CLI.start
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
new file mode 100644
index 0000000..a6e1b66
Binary files /dev/null and b/checksums.yaml.gz differ
diff --git a/conf/certs/README b/conf/certs/README
new file mode 100644
index 0000000..07dda20
--- /dev/null
+++ b/conf/certs/README
@@ -0,0 +1,39 @@
+The certs/ directory contains the TLS certificates required for encrypting
+client to server and server to server XMPP connections. TLS encryption
+is mandatory for these streams so this directory must be configured properly.
+
+The ca-bundle.crt file contains root Certificate Authority (CA) certificates.
+These are used to validate certificates presented during TLS handshake
+negotiation. The source for this file is the cacert.pem file available
+at http://curl.haxx.se/docs/caextract.html.
+
+Any self-signed CA certificate placed in this directory will be considered
+a trusted certificate. For example, let's say you're running the wonderland.lit
+XMPP server and would like to allow verona.lit to connect a server to server
+stream.  The verona.lit server hasn't purchased a legitimate TLS certificate
+from a CA known in ca-bundle.crt. Instead, they've created a self-signed
+certificate and sent it to you. Place the certificate in this directory
+with a name of verona.lit.crt and it will be trusted. TLS connections from
+verona.lit will now work.
+
+For TLS connections to work for a virtual host, two files are needed in this
+directory: <vhost_domain>.key and <vhost_domain>.crt.  The key file must contain
+a PEM encoded private key.  Do not give this file to anyone.  This is your
+private key so keep it private. The crt file must contain a PEM encoded TLS
+certificate. This contains your public key so feel free to share it with other
+servers.
+
+For example, when you add a new virtual host named wonderland.lit to
+the XMPP server, you need to run the 'vines cert wonderland.lit' command to
+generate the private key file and the self-signed certificate file in this
+directory. After running that command you will have a certs/wonderland.lit.key
+and a certs/wonderland.lit.crt file.
+
+Alternatively, you can purchase a TLS certificate from a CA (e.g. RapidSSL,
+VeriSign, etc.) and place it in this directory. This will avoid the hassles
+of managing self-signed certificates.
+
+Certificates for wildcard domains, like *.wonderland.lit, can be placed in this
+directory with a name of wonderland.lit.crt with a matching wonderland.lit.key
+file. The wildcard files will be used to secure connections to any subdomain
+under wonderland.lit (tea.wonderland.lit, party.wonderland.lit, etc).
diff --git a/conf/certs/ca-bundle.crt b/conf/certs/ca-bundle.crt
new file mode 100644
index 0000000..99b310b
--- /dev/null
+++ b/conf/certs/ca-bundle.crt
@@ -0,0 +1,3895 @@
+##
+## ca-bundle.crt -- Bundle of CA Root Certificates
+##
+## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012
+##
+## This is a bundle of X.509 certificates of public Certificate Authorities
+## (CA). These were automatically extracted from Mozilla's root certificates
+## file (certdata.txt).  This file can be found in the mozilla source tree:
+## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
+##
+## It contains the certificates in PEM format and therefore
+## can be directly used with curl / libcurl / php_curl, or with
+## an Apache+mod_ssl webserver for SSL client authentication.
+## Just configure this file as the SSLCACertificateFile.
+##
+
+# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $
+
+GTE CyberTrust Global Root
+==========================
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz
+MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0
+IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u
+sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql
+HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID
+AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW
+M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF
+NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+
+Thawte Server CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE
+AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j
+b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV
+BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u
+c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG
+A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
+ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl
+/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7
+1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J
+GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ
+GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+
+Thawte Premium Server CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
+AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
+ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
+AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
+VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
+aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
+cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
+aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
+Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
+qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
+SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
+8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
+UCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+
+Equifax Secure CA
+=================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
+ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
+B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
+fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
+8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
+CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
+spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
+zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
+BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
+70+sB3c4
+-----END CERTIFICATE-----
+
+Digital Signature Trust Co. Global CA 1
+=======================================
+-----BEGIN CERTIFICATE-----
+MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
+ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMTAeFw05ODEy
+MTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
+IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQCgbIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJE
+NySZj9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlVSn5JTe2i
+o74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
+BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
+dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
+IoAPMTk5ODEyMTAxODEwMjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQY
+MBaAFGp5fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i+DAM
+BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
+ACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lNQseSJqBcNJo4cvj9axY+IO6CizEq
+kzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4
+RbyhkwS7hp86W0N6w4pl
+-----END CERTIFICATE-----
+
+Digital Signature Trust Co. Global CA 3
+=======================================
+-----BEGIN CERTIFICATE-----
+MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
+ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMjAeFw05ODEy
+MDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
+IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQC/k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGOD
+VvsoLeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3oTQPMx7JS
+xhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
+BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
+dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
+IoAPMTk5ODEyMDkxOTE3MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQY
+MBaAFB6CTShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5WzAM
+BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
+AEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHRxdf0CiUPPXiBng+xZ8SQTGPdXqfi
+up/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVLB3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1
+mPnHfxsb1gYgAlihw6ID
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA
+TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah
+WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf
+Tqj/ZA1k
+-----END CERTIFICATE-----
+
+Verisign Class 1 Public Primary Certification Authority - G2
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgd
+k4xWArzZbxpvUjZudVYKVdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIq
+WpDBucSmFc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQIDAQAB
+MA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0Jh9ZrbWB85a7FkCMM
+XErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2uluIncrKTdcu1OofdPvAbT6shkdHvC
+lUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68DzFc6PLZ
+-----END CERTIFICATE-----
+
+Verisign Class 2 Public Primary Certification Authority - G2
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h
+cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp
+Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1
+c3QgTmV0d29yazAeFw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h
+cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp
+Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1
+c3QgTmV0d29yazCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjx
+nNuX6Zr8wgQGE75fUsjMHiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRC
+wiNPStjwDqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cCAwEA
+ATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9jinb3/7aHmZuovCfTK
+1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAXrXfMSTWqz9iP0b63GJZHc2pUIjRk
+LbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnInjBJ7xUS0rg==
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G2
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO
+FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71
+lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB
+MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT
+1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD
+Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9
+-----END CERTIFICATE-----
+
+GlobalSign Root CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
+GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
+b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
+VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
+DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
+THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
+Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
+c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
+gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
+Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
+j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
+hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
+X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
+ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
+s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
+S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
+TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
+ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
+YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
+BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
+9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
+01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
+9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+
+ValiCert Class 1 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
+MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
+GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
+DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
+lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
+icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
+Orf1LXLI
+-----END CERTIFICATE-----
+
+ValiCert Class 2 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
+CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
+ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
+SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
+UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
+W9ViH0Pd
+-----END CERTIFICATE-----
+
+RSA Root Certificate 1
+======================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td
+3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H
+BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs
+3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF
+V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r
+on+jjBXu
+-----END CERTIFICATE-----
+
+Verisign Class 1 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAN2E1Lm0+afY8wR4nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/E
+bRrsC+MO8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjVojYJ
+rKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjbPG7PoBMAGrgnoeS+
+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP26KbqxzcSXKMpHgLZ2x87tNcPVkeB
+FQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vrn5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+q2aN17O6x5q25lXQBfGfMY1aqtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/N
+y9Sn2WCVhDr4wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
+ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrspSCAaWihT37h
+a88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4E1Z5T21Q6huwtVexN2ZYI/Pc
+D98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
+-----END CERTIFICATE-----
+
+Verisign Class 2 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29y
+azE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ug
+b25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1
+c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
+aXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArwoNwtUs22e5LeWUJ92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6
+tW8UvxDOJxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUYwZF7
+C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9okoqQHgiBVrKtaaNS
+0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjNqWm6o+sdDZykIKbBoMXRRkwXbdKs
+Zj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/ESrg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0
+JhU8wI1NQ0kdvekhktdmnLfexbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf
+0xwLRtxyID+u7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
+sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RIsH/7NiXaldDx
+JBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTPcjnhsUPgKM+351psE2tJs//j
+GHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1
+EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc
+cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw
+EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj
+055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f
+j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0
+xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa
+t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+
+Verisign Class 4 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS
+tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM
+8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW
+Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX
+Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt
+mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd
+RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG
+UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+
+Entrust.net Secure Server CA
+============================
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
+ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
+A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
+eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
+dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
+aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
+gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
+ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
+CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
+dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
+NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
+HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
+BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
+Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
+n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+
+Entrust.net Premium 2048 Secure Server CA
+=========================================
+-----BEGIN CERTIFICATE-----
+MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
+NzUwNTFaFw0xOTEyMjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo3QwcjARBglghkgBhvhC
+AQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGAvtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdER
+gL7YibkIozH5oSQJFrlwMB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0B
+AQUFAAOCAQEAWUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
+oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQh7A6tcOdBTcS
+o8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18f3v/rxzP5tsHrV7bhZ3QKw0z
+2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfNB/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjX
+OP/swNlQ8C5LWK5Gb9Auw2DaclVyvUxFnmG6v4SBkgPR0ml8xQ==
+-----END CERTIFICATE-----
+
+Baltimore CyberTrust Root
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
+ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
+ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
+SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
+dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
+uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
+UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
+G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
+XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
+l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
+VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
+cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
+hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
+Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
+RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+Equifax Secure Global eBusiness CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp
+bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx
+HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds
+b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV
+PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN
+qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn
+hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
+BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs
+MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN
+I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY
+NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 1
+=============================
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB
+LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE
+ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz
+IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ
+1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a
+IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk
+MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW
+Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF
+AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5
+lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+
+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 2
+=============================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEXMBUGA1UE
+ChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0y
+MB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
+DkVxdWlmYXggU2VjdXJlMSYwJAYDVQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn
+2Z0GvxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/BPO3QSQ5
+BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0CAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUx
+JjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTkwNjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9e
+uSBIplBqy/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAAyGgq3oThr1
+jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia
+78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUm
+V+GRMOrN
+-----END CERTIFICATE-----
+
+AddTrust Low-Value Services Root
+================================
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU
+cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw
+CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO
+ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6
+54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr
+oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1
+Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui
+GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w
+HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT
+RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw
+HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt
+ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph
+iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr
+mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj
+ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+
+AddTrust External Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD
+VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw
+NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU
+cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg
+Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821
++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw
+Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo
+aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy
+2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7
+7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk
+VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl
+j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355
+e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u
+G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+
+AddTrust Public Services Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU
+cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ
+BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l
+dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu
+nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i
+d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG
+Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw
+HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G
+A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G
+A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4
+JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL
++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9
+Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H
+EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+
+AddTrust Qualified Certificates Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU
+cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx
+CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ
+IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx
+64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3
+KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o
+L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR
+wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU
+MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE
+BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y
+azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG
+GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze
+RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB
+iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=
+-----END CERTIFICATE-----
+
+Entrust Root Certification Authority
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
+BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
+b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
+A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
+MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
+MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
+Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
+dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
+A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
+Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
+j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
+rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
+MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
+hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
+Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
+v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
+W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
+tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+RSA Security 2048 v3
+====================
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK
+ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy
+MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb
+BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7
+Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb
+WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH
+KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP
++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/
+MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E
+FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY
+v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj
+0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj
+VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395
+nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA
+pKnXwiJPZ9d37CAFYd4=
+-----END CERTIFICATE-----
+
+GeoTrust Global CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw
+MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo
+BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet
+8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc
+T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU
+vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk
+DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q
+zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4
+d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2
+mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p
+XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm
+Mw==
+-----END CERTIFICATE-----
+
+GeoTrust Global CA 2
+====================
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw
+MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/
+NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k
+LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA
+Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b
+HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH
+K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7
+srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh
+ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL
+OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC
+x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF
+H4z1Ir+rzoPz4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA
+=====================
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1
+MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu
+Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t
+JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e
+RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs
+7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d
+8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V
+qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga
+Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB
+Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu
+KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08
+ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0
+XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB
+hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2
+qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL
+oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK
+xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF
+KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2
+DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK
+xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU
+p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI
+P/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA 2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0
+MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg
+SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0
+DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17
+j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q
+JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a
+QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2
+WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP
+20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn
+ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC
+SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG
+8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2
++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E
+BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ
+4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+
+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq
+A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg
+Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP
+pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d
+FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp
+gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm
+X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+
+UTN-USER First-Network Applications
+===================================
+-----BEGIN CERTIFICATE-----
+MIIEZDCCA0ygAwIBAgIQRL4Mi1AAJLQR0zYwS8AzdzANBgkqhkiG9w0BAQUFADCBozELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzAp
+BgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBBcHBsaWNhdGlvbnMwHhcNOTkwNzA5MTg0ODM5
+WhcNMTkwNzA5MTg1NzQ5WjCBozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5T
+YWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBB
+cHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCz+5Gh5DZVhawGNFug
+mliy+LUPBXeDrjKxdpJo7CNKyXY/45y2N3kDuatpjQclthln5LAbGHNhSuh+zdMvZOOmfAz6F4Cj
+DUeJT1FxL+78P/m4FoCHiZMlIJpDgmkkdihZNaEdwH+DBmQWICzTSaSFtMBhf1EI+GgVkYDLpdXu
+Ozr0hAReYFmnjDRy7rh4xdE7EkpvfmUnuaRVxblvQ6TFHSyZwFKkeEwVs0CYCGtDxgGwenv1axwi
+P8vv/6jQOkt2FZ7S0cYu49tXGzKiuG/ohqY/cKvlcJKrRB5AUPuco2LkbG6gyN7igEL66S/ozjIE
+j3yNtxyjNTwV3Z7DrpelAgMBAAGjgZEwgY4wCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8w
+HQYDVR0OBBYEFPqGydvguul49Uuo1hXf8NPhahQ8ME8GA1UdHwRIMEYwRKBCoECGPmh0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LU5ldHdvcmtBcHBsaWNhdGlvbnMuY3JsMA0G
+CSqGSIb3DQEBBQUAA4IBAQCk8yXM0dSRgyLQzDKrm5ZONJFUICU0YV8qAhXhi6r/fWRRzwr/vH3Y
+IWp4yy9Rb/hCHTO967V7lMPDqaAt39EpHx3+jz+7qEUqf9FuVSTiuwL7MT++6LzsQCv4AdRWOOTK
+RIK1YSAhZ2X28AvnNPilwpyjXEAfhZOVBt5P1CeptqX8Fs1zMT+4ZSfP1FMa8Kxun08FDAOBp4Qp
+xFq9ZFdyrTvPNximmMatBrTcCKME1SmklpoSZ0qMYEWd8SOasACcaLWYUNPvji6SZbFIPiG+FTAq
+DbUMo2s/rn9X9R+WfN9v3YIwLGUbQErNaLly7HF27FSOH4UMAWr6pjisH8SE
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 1
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG
+v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z
+DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh
+sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP
+8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z
+o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf
+GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF
+VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft
+3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g
+Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en
+fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8
+f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO
+qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN
+RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0
+gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn
+6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid
+FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6
+Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj
+B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op
+aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY
+T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p
++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg
+JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy
+zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO
+ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh
+1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf
+GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff
+Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP
+cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk=
+-----END CERTIFICATE-----
+
+Visa eCommerce Root
+===================
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG
+EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug
+QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2
+WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm
+VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL
+F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b
+RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0
+TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI
+/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs
+GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
+MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc
+CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW
+YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz
+zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu
+YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+
+Certum Root CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
+ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
+Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
+by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
+wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
+kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
+89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
+Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
+NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
+GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
+GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
+0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
+qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+
+Comodo AAA Services root
+========================
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
+MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
+c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
+BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
+C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
+i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
+Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
+Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
+Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
+cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
+LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
+7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
+8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
+12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+
+Comodo Secure Services root
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw
+MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu
+Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi
+BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP
+9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc
+rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC
+oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V
+p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E
+FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj
+YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm
+aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm
+4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL
+DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw
+pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H
+RR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+
+Comodo Trusted Services root
+============================
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw
+MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h
+bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw
+IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7
+3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y
+/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6
+juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS
+ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud
+DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp
+ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl
+cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw
+uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA
+BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l
+R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O
+9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+
+QuoVadis Root CA
+================
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
+ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
+MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
+cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
+EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
+J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
+F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
+YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
+AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
+PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
+ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
+MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
+YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
+ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
+Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu
+BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
+FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
+tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
+fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
+LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
+gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
+5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
+5nrQNiOKSnQ2+Q==
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 2
+==================
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
+ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
+XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
+lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
+lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
+lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
+66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
+wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
+D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
+BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
+J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
+DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
+a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
+Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
+UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
+VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
+IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
+WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
+f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
+4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
+VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 3
+==================
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
+OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
+DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
+KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
+DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
+BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
+p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
+nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
+MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
+Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
+uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
+BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
+YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
+BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
+VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
+ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
+AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
+qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
+hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
+POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
+Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
+8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
+bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
+g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
+vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
+qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+
+Security Communication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
+8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
+DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
+5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
+DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
+JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
+0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
+mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
+s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
+6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
+FL39vmwLAw==
+-----END CERTIFICATE-----
+
+Sonera Class 1 Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAxMDQwNjEwNDkxM1oXDTIxMDQw
+NjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
+IENsYXNzMSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H88
+7dF+2rDNbS82rDTG29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9
+EJUkoVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk3w0LBUXl
+0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBLqdReLjVQCfOAl/QMF645
+2F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIINnvmLVz5MxxftLItyM19yejhW1ebZrgUa
+HXVFsculJRwSVzb9IjcCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZT
+iFIwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE9
+28Jj2VuXZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0HDjxV
+yhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VOTzF2nBBhjrZTOqMR
+vq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2UvkVrCqIexVmiUefkl98HVrhq4uz2P
+qYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4wzMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9Z
+IRlXvVWa
+-----END CERTIFICATE-----
+
+Sonera Class 2 Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
+NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
+IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3
+/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
+dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
+f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
+tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
+nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
+XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
+0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
+cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
+Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
+EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH
+llpwrN9M
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA
+=============================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE
+ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w
+HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh
+bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt
+vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P
+jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca
+C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth
+vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6
+22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV
+HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v
+dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN
+BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR
+EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw
+MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y
+nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+
+TDC Internet Root CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE
+ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx
+NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu
+ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j
+xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL
+znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc
+5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6
+otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI
+AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM
+VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM
+MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC
+AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe
+UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G
+CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m
+gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
+2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb
+O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU
+Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l
+-----END CERTIFICATE-----
+
+TDC OCES Root CA
+================
+-----BEGIN CERTIFICATE-----
+MIIFGTCCBAGgAwIBAgIEPki9xDANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJESzEMMAoGA1UE
+ChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTAeFw0wMzAyMTEwODM5MzBaFw0zNzAyMTEwOTA5
+MzBaMDExCzAJBgNVBAYTAkRLMQwwCgYDVQQKEwNUREMxFDASBgNVBAMTC1REQyBPQ0VTIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArGL2YSCyz8DGhdfjeebM7fI5kqSXLmSjhFuH
+nEz9pPPEXyG9VhDr2y5h7JNp46PMvZnDBfwGuMo2HP6QjklMxFaaL1a8z3sM8W9Hpg1DTeLpHTk0
+zY0s2RKY+ePhwUp8hjjEqcRhiNJerxomTdXkoCJHhNlktxmW/OwZ5LKXJk5KTMuPJItUGBxIYXvV
+iGjaXbXqzRowwYCDdlCqT9HU3Tjw7xb04QxQBr/q+3pJoSgrHPb8FTKjdGqPqcNiKXEx5TukYBde
+dObaE+3pHx8b0bJoc8YQNHVGEBDjkAB2QMuLt0MJIf+rTpPGWOmlgtt3xDqZsXKVSQTwtyv6e1mO
+3QIDAQABo4ICNzCCAjMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgewGA1UdIASB
+5DCB4TCB3gYIKoFQgSkBAQEwgdEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2VydGlmaWthdC5k
+ay9yZXBvc2l0b3J5MIGdBggrBgEFBQcCAjCBkDAKFgNUREMwAwIBARqBgUNlcnRpZmlrYXRlciBm
+cmEgZGVubmUgQ0EgdWRzdGVkZXMgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEuMS4xLiBDZXJ0aWZp
+Y2F0ZXMgZnJvbSB0aGlzIENBIGFyZSBpc3N1ZWQgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEuMS4x
+LjARBglghkgBhvhCAQEEBAMCAAcwgYEGA1UdHwR6MHgwSKBGoESkQjBAMQswCQYDVQQGEwJESzEM
+MAoGA1UEChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTENMAsGA1UEAxMEQ1JMMTAsoCqgKIYm
+aHR0cDovL2NybC5vY2VzLmNlcnRpZmlrYXQuZGsvb2Nlcy5jcmwwKwYDVR0QBCQwIoAPMjAwMzAy
+MTEwODM5MzBagQ8yMDM3MDIxMTA5MDkzMFowHwYDVR0jBBgwFoAUYLWF7FZkfhIZJ2cdUBVLc647
++RIwHQYDVR0OBBYEFGC1hexWZH4SGSdnHVAVS3OuO/kSMB0GCSqGSIb2fQdBAAQQMA4bCFY2LjA6
+NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEACromJkbTc6gJ82sLMJn9iuFXehHTuJTXCRBuo7E4
+A9G28kNBKWKnctj7fAXmMXAnVBhOinxO5dHKjHiIzxvTkIvmI/gLDjNDfZziChmPyQE+dF10yYsc
+A+UYyAFMP8uXBV2YcaaYb7Z8vTd/vuGTJW1v8AqtFxjhA7wHKcitJuj4YfD9IQl+mo6paH1IYnK9
+AOoBmbgGglGBTvH1tJFUuSN6AJqfXY3gPGS5GhKSKseCRHI53OI8xthV9RVOyAUO28bQYqbsFbS1
+AoLbrIyigfCbmTH1ICCoiGEKB5+U/NDXG8wuF/MEJ3Zn61SD/aSQfgY9BKNDLdr8C2LqL19iUw==
+-----END CERTIFICATE-----
+
+UTN DATACorp SGC Root CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ
+BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa
+MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w
+HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
+dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys
+raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo
+wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA
+9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv
+33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud
+DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9
+BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD
+LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3
+DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0
+I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx
+EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP
+DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+
+UTN USERFirst Email Root CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0
+BgNVBAMTLVVUTi1VU0VSRmlyc3QtQ2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05
+OTA3MDkxNzI4NTBaFw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQx
+FzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsx
+ITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJz
+dC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWlsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3BYHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIx
+B8dOtINknS4p1aJkxIW9hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8
+om+rWV6lL8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLmSGHG
+TPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM1tZUOt4KpLoDd7Nl
+yP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws6wIDAQABo4G5MIG2MAsGA1UdDwQE
+AwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNV
+HR8EUTBPME2gS6BJhkdodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGll
+bnRBdXRoZW50aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
+AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u7mFVbwQ+zzne
+xRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0xtcgBEXkzYABurorbs6q15L+
+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQrfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarV
+NZ1yQAOJujEdxRBoUp7fooXFXAimeOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZ
+w7JHpsIyYdfHb0gkUSeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
+-----END CERTIFICATE-----
+
+UTN USERFirst Hardware Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd
+BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx
+OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0
+eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
+ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI
+wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd
+tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8
+i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf
+Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw
+gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF
+lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF
+UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF
+BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW
+XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2
+lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn
+iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67
+nfhmqA==
+-----END CERTIFICATE-----
+
+UTN USERFirst Object Root CA
+============================
+-----BEGIN CERTIFICATE-----
+MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAb
+BgNVBAMTFFVUTi1VU0VSRmlyc3QtT2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAz
+NlowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkx
+HjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2Vy
+dHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VR
+loTN2+O5bj4x2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ
+w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vu
+lBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehbkkj7
+RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUCAwEAAaOBrzCBrDAL
+BgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8
+ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly
+c3QtT2JqZWN0LmNybDApBgNVHSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQw
+DQYJKoZIhvcNAQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw
+NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXBmMiKVl0+7kNO
+PmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU4U3GDZlDAQ0Slox4nb9QorFE
+qmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK581OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCG
+hU3IfdeLA/5u1fedFqySLKAj5ZyRUh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g=
+-----END CERTIFICATE-----
+
+Camerfirma Chambers of Commerce Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
+NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
+cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
+MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
+AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
+xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
+NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
+DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
+d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
+EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
+cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
+AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
+bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
+VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
+fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
+L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
+UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
+ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
+erfutGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+
+Camerfirma Global Chambersign Root
+==================================
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
+NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
+YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
+MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
+ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
+1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
+by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
+6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
+8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
+BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
+aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
+Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
+aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
+ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
+PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
+gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
+PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
+IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
+t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+
+NetLock Qualified (Class QA) Root
+=================================
+-----BEGIN CERTIFICATE-----
+MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQDEzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVn
+eXpvaSAoQ2xhc3MgUUEpIFRhbnVzaXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0
+bG9jay5odTAeFw0wMzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTER
+MA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNhZ2kgS2Z0
+LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5ldExvY2sgTWlub3NpdGV0
+dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZhbnlraWFkbzEeMBwGCSqGSIb3DQEJARYP
+aW5mb0BuZXRsb2NrLmh1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRV
+CacbvWy5FPSKAtt2/GoqeKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e
+8ia6AFQer7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO53Lhb
+m+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWdvLrqOU+L73Sa58XQ
+0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0lmT+1fMptsK6ZmfoIYOcZwvK9UdPM
+0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4ICwDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
+HQ8BAf8EBAMCAQYwggJ1BglghkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2
+YW55IGEgTmV0TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh
+biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQgZWxla3Ryb25p
+a3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywgdmFsYW1pbnQgZWxmb2dhZGFz
+YW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwg
+YXogQWx0YWxhbm9zIFN6ZXJ6b2Rlc2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kg
+ZWxqYXJhcyBtZWd0ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczov
+L3d3dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0BuZXRsb2Nr
+Lm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0
+aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMg
+YXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0
+IGluZm9AbmV0bG9jay5uZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3
+DQEBBQUAA4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQMznN
+wNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+NFAwLvt/MpqNPfMg
+W/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCRVCHnpgu0mfVRQdzNo0ci2ccBgcTc
+R08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR
+5qq5aKrN9p2QdRLqOBrKROi3macqaJVmlaut74nLYKkGEsaUR+ko
+-----END CERTIFICATE-----
+
+NetLock Notary (Class A) Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI
+EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j
+ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX
+DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH
+EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD
+VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz
+cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM
+D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ
+z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC
+/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7
+tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6
+4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG
+A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC
+Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv
+bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn
+LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0
+ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz
+IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh
+IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu
+b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh
+bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg
+Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp
+bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5
+ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP
+ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB
+CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr
+KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM
+8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+
+NetLock Business (Class B) Root
+===============================
+-----BEGIN CERTIFICATE-----
+MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg
+VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD
+VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv
+bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg
+VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S
+o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr
+1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
+HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ
+RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh
+dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0
+ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv
+c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg
+YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
+c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz
+Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA
+bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl
+IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2
+YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj
+cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM
+43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR
+stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI
+-----END CERTIFICATE-----
+
+NetLock Express (Class C) Root
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j
+ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z
+W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63
+euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw
+DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN
+RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn
+YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB
+IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i
+aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0
+ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
+ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo
+dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y
+emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k
+IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ
+UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg
+YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2
+xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW
+gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A==
+-----END CERTIFICATE-----
+
+XRamp Global CA Root
+====================
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
+BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
+dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
+HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
+U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
+IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
+foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
+zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
+AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
+xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
+oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
+AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
+/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
+nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
+8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+
+Go Daddy Class 2 CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
+VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
+A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
+ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
+qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
+YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
+vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
+BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
+atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
+MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
+PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
+I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
+Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
+vZ8=
+-----END CERTIFICATE-----
+
+Starfield Class 2 CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
+U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
+MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
+A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
+SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
+bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
+JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
+epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
+F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
+MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
+hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
+bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
+afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
+PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
+KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
+QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj
+YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH
+AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw
+Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg
+U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5
+LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh
+cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT
+dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC
+AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh
+3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm
+vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk
+fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3
+fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ
+EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl
+1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/
+lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro
+g14=
+-----END CERTIFICATE-----
+
+Taiwan GRCA
+===========
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG
+EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X
+DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv
+dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN
+w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5
+BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O
+1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO
+htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov
+J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7
+Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t
+B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB
+O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8
+lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV
+HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2
+09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj
+Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2
+Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU
+D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz
+DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk
+Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk
+7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ
+CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy
++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS
+-----END CERTIFICATE-----
+
+Firmaprofesional Root CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT
+GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp
+Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA
+ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL
+MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT
+OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2
+ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V
+j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH
+lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf
+3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8
+NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww
+KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG
+AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD
+ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq
+u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf
+wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm
+7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG
+VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA=
+-----END CERTIFICATE-----
+
+Wells Fargo Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDAxMDExMTY0MTI4WhcNMjEwMTE0MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dl
+bGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEv
+MC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n135zHCLielTWi5MbqNQ1mX
+x3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHESxP9cMIlrCL1dQu3U+SlK93OvRw6esP3
+E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4OJgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5
+OEL8pahbSCOz6+MlsoCultQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4j
+sNtlAHCEAQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMBAAGj
+YTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcBCzAyMDAGCCsGAQUF
+BwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRwb2xpY3kwDQYJKoZIhvcNAQEFBQAD
+ggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrv
+m+0fazbuSCUlFLZWohDo7qd/0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0R
+OhPs7fpvcmR7nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
+x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ33ZwmVxwQ023
+tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
+-----END CERTIFICATE-----
+
+Swisscom Root CA 1
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4
+MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM
+MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF
+NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe
+AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC
+b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn
+7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN
+cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp
+WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5
+haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY
+MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9
+MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn
+jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ
+MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H
+VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl
+vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl
+OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3
+1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq
+nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy
+x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW
+NY6E0F/6MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+
+DigiCert Assured ID Root CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
+MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
+9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
+UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
+/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
+oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
+GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
+66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
+hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
+EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
+SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
+8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+
+DigiCert Global Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
+MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
+TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
+BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
+4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
+7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
+o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
+8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
+BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
+EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
+tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
+UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+
+DigiCert High Assurance EV Root CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
+KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
+MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
+Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
+Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
+OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
+MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
+NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
+h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
+Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
+JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
+V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
+myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
+mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Certplus Class 2 Primary CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE
+BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN
+OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy
+dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR
+5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ
+Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO
+YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e
+e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME
+CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ
+YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t
+L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD
+P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R
+TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+
+7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW
+//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+
+DST Root CA X3
+==============
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
+DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
+cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
+rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
+UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
+xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
+utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
+MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug
+dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
+GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
+RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
+fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+
+DST ACES CA X6
+==============
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT
+MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha
+MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE
+CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI
+DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa
+pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow
+GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy
+MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu
+Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy
+dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU
+CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2
+5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t
+Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs
+vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
+oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 1
+==============================================
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
+MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
+acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
+MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
+TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
+aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
+yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
+Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
+8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
+W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
+BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
+sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
+q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
+nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2
+==============================================
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
+MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
+dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
+A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
+acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
+LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
+x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
+QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
+5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
+AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
+Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
+9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
+UrbnBEI=
+-----END CERTIFICATE-----
+
+SwissSign Platinum CA - G2
+==========================
+-----BEGIN CERTIFICATE-----
+MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UEBhMCQ0gxFTAT
+BgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWduIFBsYXRpbnVtIENBIC0gRzIw
+HhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAwWjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMM
+U3dpc3NTaWduIEFHMSMwIQYDVQQDExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu
+669yIIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2HtnIuJpX+UF
+eNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+6ixuEFGSzH7VozPY1kne
+WCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5objM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIo
+j5+saCB9bzuohTEJfwvH6GXp43gOCWcwizSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/6
+8++QHkwFix7qepF6w9fl+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34T
+aNhxKFrYzt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaPpZjy
+domyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtFKwH3HBqi7Ri6Cr2D
++m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuWae5ogObnmLo2t/5u7Su9IPhlGdpV
+CX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMBAAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCv
+zAeHFUdvOMW0ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
+IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUAA4ICAQAIhab1
+Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0uMoI3LQwnkAHFmtllXcBrqS3
+NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4
+U99REJNi54Av4tHgvI42Rncz7Lj7jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8
+KV2LwUvJ4ooTHbG/u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl
+9x8DYSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1puEa+S1B
+aYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXaicYwu+uPyyIIoK6q8QNs
+OktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbGDI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSY
+Mdp08YSTcU1f+2BY0fvEwW2JorsgH51xkcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAci
+IfNAChs0B0QTwoRqjt8ZWr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
+-----END CERTIFICATE-----
+
+SwissSign Gold CA - G2
+======================
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
+EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
+MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
+c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
+t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
+jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
+vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
+ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
+AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
+jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
+peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
+7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
+GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
+OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
+5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
+44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
+Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
+Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
+mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
+vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
+KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
+NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
+viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+
+SwissSign Silver CA - G2
+========================
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
+BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
+DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
+aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
+N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
+6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
+MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
+qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
+FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
+ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
+celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
+CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
+tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
+4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
+kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
+3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
+/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
+DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
+e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
+WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
+DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
+DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ
+cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN
+b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9
+nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge
+RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt
+tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI
+hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K
+Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN
+NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa
+Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG
+1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+
+thawte Primary Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3
+MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg
+SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv
+KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT
+FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs
+oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ
+1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc
+q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K
+aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p
+afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF
+AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE
+uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89
+jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH
+z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G5
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
+biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
+dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
+j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
+Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
+fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
+SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
+KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
+Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
+ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+
+SecureTrust CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
+dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
+BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
+OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
+DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
+GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
+01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
+ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
+aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
+SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
+mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
+nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+
+Secure Global CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
+bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
+MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
+YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
+bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
+8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
+HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
+0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
+oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
+MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
+CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
+3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+
+COMODO Certification Authority
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
+MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
+T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
+xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
+4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
+1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
+rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
+b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
+AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
+OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
+IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
+-----END CERTIFICATE-----
+
+Network Solutions Certificate Authority
+=======================================
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
+EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
+IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
+MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
+jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
+aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
+crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
+/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
+AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
+bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
+A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
+4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
+GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
+ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+
+WellsSecure Public Root Certificate Authority
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM
+F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw
+NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl
+bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD
+VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1
+iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13
+i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8
+bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB
+K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB
+AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu
+cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm
+lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB
+i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww
+GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI
+K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0
+bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj
+qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es
+E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ
+tylv2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+
+COMODO ECC Certification Authority
+==================================
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
+GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
+4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
+wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
+FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
+U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+IGC/A
+=====
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE
+Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy
+MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI
+EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT
+STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2
+TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW
+So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy
+HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd
+frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ
+tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB
+egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC
+iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK
+q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q
+MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI
+lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF
+0mBWWg==
+-----END CERTIFICATE-----
+
+Security Communication EV RootCA1
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
+BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
+Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
+/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
+WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
+ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
+bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
+9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
+iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
+Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
+mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
+T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+
+OISTE WISeKey Global Root GA CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
+BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
+A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
+bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
+VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
+IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
+IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
+Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
+Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
+d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
+/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
+LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
+MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
+okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
+-----END CERTIFICATE-----
+
+S-TRUST Authentication and Encryption Root CA 2005 PN
+=====================================================
+-----BEGIN CERTIFICATE-----
+MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE
+BhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcpMRIwEAYDVQQHEwlTdHV0dGdh
+cnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fzc2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVT
+LVRSVVNUIEF1dGhlbnRpY2F0aW9uIGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0w
+NTA2MjIwMDAwMDBaFw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFk
+ZW4tV3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMgRGV1dHNj
+aGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJVU1QgQXV0aGVudGljYXRp
+b24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob
+4QSwI7+Vio5bG0F/WsPoTUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXL
+g3KSwlOyggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1Xgqf
+eN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteFhy+S8dF2g08LOlk3
+KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm7QIDAQABo4GSMIGPMBIGA1UdEwEB
+/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJv
+bmxpbmUxLTIwNDgtNTAdBgNVHQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAU
+D8oeXHngovMpttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD
+pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFoLtU96G7m1R08
+P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersFiXOMy6ZNwPv2AtawB6MDwidA
+nwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0yh9WUUpY6RsZxlj33mA6ykaqP2vROJAA5Veit
+F7nTNCtKqUDMFypVZUF0Qn71wK/Ik63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8b
+Hz2eBIPdltkdOpQ=
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE
+BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL
+EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0
+MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz
+dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT
+GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG
+d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N
+oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc
+QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ
+PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb
+MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG
+IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD
+VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3
+LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A
+dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA
+4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg
+AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA
+egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6
+Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO
+PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv
+c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h
+cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw
+IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT
+WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV
+MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER
+MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp
+Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal
+HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT
+nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE
+aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK
+yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB
+S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+
+Certigna
+========
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
+EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
+MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
+Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
+XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
+GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
+ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
+DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
+Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
+tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
+BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
+SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
+hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
+PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
+1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+AC Ra\xC3\xADz Certic\xC3\xA1mara S.A.
+======================================
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT
+AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg
+LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w
+HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+
+U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh
+IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN
+yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU
+2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3
+4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP
+2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm
+8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf
+HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa
+Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK
+5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b
+czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g
+ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF
+BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug
+cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf
+AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX
+EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v
+/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3
+MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4
+3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk
+eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f
+/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h
+RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU
+Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 2 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw
+MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw
+IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2
+xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ
+Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u
+SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G
+dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ
+KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj
+TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP
+JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk
+vQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 3 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw
+MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W
+yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo
+6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ
+uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk
+2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE
+O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8
+yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9
+IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal
+092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc
+5A==
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA I
+=============================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN
+MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg
+VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw
+JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC
+qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv
+xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw
+ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O
+gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j
+BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG
+1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy
+vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3
+ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a
+7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+
+Deutsche Telekom Root CA 2
+==========================
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT
+RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG
+A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5
+MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G
+A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS
+b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5
+bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI
+KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY
+AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK
+Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV
+jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV
+HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr
+E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy
+zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8
+rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G
+dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+
+ComSign CA
+==========
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0MRMwEQYDVQQD
+EwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTMy
+MThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMTCkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNp
+Z24xCzAJBgNVBAYTAklMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49q
+ROR+WCf4C9DklBKK8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTy
+P2Q298CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb2CEJKHxN
+GGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxCejVb7Us6eva1jsz/D3zk
+YDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7KpiXd3DTKaCQeQzC6zJMw9kglcq/QytNuEM
+rkvF7zuZ2SOzW120V+x0cAwqTwIDAQABo4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAy
+oDCgLoYsaHR0cDovL2ZlZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0P
+AQH/BAQDAgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRLAZs+
+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWdfoPPbrxHbvUanlR2
+QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0McXS6hMTXcpuEfDhOZAYnKuGntewI
+mbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb
+/627HOkthIDYIb6FUtnUdLlphbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VG
+zT2ouvDzuFYkRes3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U
+AGegcQCCSA==
+-----END CERTIFICATE-----
+
+ComSign Secured CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE
+AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w
+NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD
+QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs
+49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH
+7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB
+kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1
+9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw
+AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t
+U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA
+j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC
+AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a
+BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp
+FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP
+51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+
+Cybertrust Global Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
+ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
+MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
+ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
+0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
+AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
+89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
+8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
+MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
+A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
+lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
+5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
+hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
+X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+
+ePKI Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
+MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
+MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
+IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
+lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
+qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
+12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
+WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
+lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
+vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
+Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
+MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
+1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
+KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
+xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
+NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
+GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
+xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
+gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
+sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
+BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3
+=============================================================================================================================
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH
+DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q
+aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry
+b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV
+BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg
+S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4
+MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl
+IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF
+n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl
+IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft
+dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl
+cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO
+Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1
+xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR
+6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd
+BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4
+N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT
+y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh
+LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M
+dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+
+Buypass Class 2 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2
+MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M
+cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83
+0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4
+0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R
+uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV
+1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt
+7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2
+fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w
+wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+
+Buypass Class 3 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1
+MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx
+ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0
+n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia
+AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c
+1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7
+pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA
+EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5
+htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj
+el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+
+EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1
+==========================================================================
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg
+QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe
+Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt
+IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by
+X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b
+gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr
+eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ
+TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy
+Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn
+uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI
+qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm
+ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0
+Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW
+Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t
+FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm
+zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k
+XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT
+bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU
+RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK
+1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt
+2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ
+Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9
+AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+
+certSIGN ROOT CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
+VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
+Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
+CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
+JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
+rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
+ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
+0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
+AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
+AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
+SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
+x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
+vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
+TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+CNNIC ROOT
+==========
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE
+ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw
+OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD
+o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz
+VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT
+VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or
+czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK
+y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC
+wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S
+lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5
+Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM
+O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8
+BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2
+G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m
+mxE=
+-----END CERTIFICATE-----
+
+ApplicationCA - Japanese Government
+===================================
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT
+SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw
+MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl
+cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4
+fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN
+wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE
+jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu
+nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU
+WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD
+vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs
+o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g
+/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD
+io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW
+dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G3
+=============================================
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz
+NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo
+YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT
+LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j
+K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE
+c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C
+IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu
+dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr
+2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9
+cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE
+Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s
+t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G2
+===========================
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC
+VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu
+IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg
+Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV
+MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG
+b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt
+IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS
+LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5
+8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN
+G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K
+rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G3
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w
+ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD
+VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG
+A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At
+P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC
++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY
+7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW
+vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ
+KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK
+A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC
+8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm
+er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
+OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
+b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
+KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
+EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
+ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
+npaqBA+K
+-----END CERTIFICATE-----
+
+VeriSign Universal Root Certification Authority
+===============================================
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj
+1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP
+MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
+9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
+AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR
+tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
+CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O
+a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
+Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
+Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
+P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
+wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4
+mJO37M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G4
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3
+b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz
+ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo
+b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8
+Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz
+rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw
+HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u
+Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD
+A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx
+AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+NetLock Arany (Class Gold) Főtanúsítvány
+============================================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
+A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
+dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
+cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
+MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
+ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
+c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
+0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
+/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
+H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
+fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
+neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
+qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
+YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
+NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
+dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA - G2
+==================================
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC
+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ
+5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn
+vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj
+CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil
+e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR
+OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI
+CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65
+48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi
+trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737
+qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB
+AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC
+ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA
+A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz
++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj
+f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN
+kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk
+CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF
+URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb
+CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h
+oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV
+IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm
+66+KAQ==
+-----END CERTIFICATE-----
+
+CA Disig
+========
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK
+QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw
+MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz
+bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm
+GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD
+Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo
+hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt
+ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w
+gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P
+AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz
+aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff
+ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa
+BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t
+WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3
+mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K
+ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA
+4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+
+Juur-SK
+=======
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA
+c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw
+DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG
+SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy
+aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf
+TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC
++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw
+UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa
+Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF
+MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD
+HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh
+AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA
+cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr
+AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw
+cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G
+A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo
+ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL
+abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678
+IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh
+Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2
+yyqcjg==
+-----END CERTIFICATE-----
+
+Hongkong Post Root CA 1
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
+DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
+NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
+IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
+ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
+auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
+qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
+V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
+HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
+h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
+l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
+IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
+T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
+c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
+-----END CERTIFICATE-----
+
+SecureSign RootCA11
+===================
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
+SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
+b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
+KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
+cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
+TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
+wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
+g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
+O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
+bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
+t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
+OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
+bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
+Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
+y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
+lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+
+ACEDICOM Root
+=============
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD
+T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4
+MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG
+A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk
+WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD
+YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew
+MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb
+m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk
+HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT
+xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2
+3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9
+2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq
+TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz
+4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU
+9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg
+aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP
+eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk
+zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1
+ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI
+KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq
+nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE
+I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp
+MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o
+tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+
+Verisign Class 1 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAx
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0fzGVuDLDQ
+VoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHiTkVWaR94AoDa3EeRKbs2
+yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFgVKTk8d6Pa
+XCUDfGD67gmZPCcQcMgMCeazh88K4hiWNWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n
+0a3hUKw8fGJLj7qE1xIVGx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZ
+RjXZ+Hxb
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky
+CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX
+bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/
+D/xwzoiQ
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
+MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
+c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
+BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
+U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
+fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
+0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
+pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
+1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
+AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
+QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
+FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
+lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
+I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
+yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
+LXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi
+===================================================
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz
+ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3
+MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0
+cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u
+aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY
+8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y
+jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI
+JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk
+9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG
+SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d
+F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq
+D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4
+Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R3
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
+iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
+0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
+rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
+OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
+xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
+lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
+EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
+bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
+YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
+kpeDMdmztcpHWD9f
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA III
+===============================
+-----BEGIN CERTIFICATE-----
+MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAe
+Fw0wOTA5MDkwODE1MjdaFw0yOTEyMzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNU
+QyBUcnVzdENlbnRlciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0Ex
+KDAmBgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF5+cvAqBNLaT6hdqbJYUt
+QCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYvDIRlzg9uwliT6CwLOunBjvvya8o84pxO
+juT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8vzArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+Eut
+CHnNaYlAJ/Uqwa1D7KRTyGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1
+M4BDj5yjdipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBhMB8G
+A1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI4jANBgkqhkiG9w0BAQUFAAOCAQEA
+g8ev6n9NCjw5sWi+e22JLumzCecYV42FmhfzdkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+
+KGwWaODIl0YgoGhnYIg5IFHYaAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhK
+BgePxLcHsU0GDeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV
+CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPHLQNjO9Po5KIq
+woIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg==
+-----END CERTIFICATE-----
+
+Autoridad de Certificacion Firmaprofesional CIF A62634068
+=========================================================
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
+EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
+DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
+bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
+ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
+51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
+R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
+T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
+Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
+osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
+crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
+saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
+KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
+6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+Izenpe.com
+==========
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
+EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
+MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
+QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
+03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
+ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
+PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
+OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
+F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
+0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
+leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
+AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
+SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
+NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
+BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
+Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
+kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
+hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
+g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
+aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
+nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
+ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
+Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
+WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+Chambers of Commerce Root - 2008
+================================
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
+Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
+ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
+EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
+cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
+XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
+h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
+ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
+NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
+D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
+lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
+0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
+EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
+G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
+BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
+bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
+bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
+CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
+AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
+wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
+3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
+RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
+M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
+YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
+9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
+zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
+nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
+-----END CERTIFICATE-----
+
+Global Chambersign Root - 2008
+==============================
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
+NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
+Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
+QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
+VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
+XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
+ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
+/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
+TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
+H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
+Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
+HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
+AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
+BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
+BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
+aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
+aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
+1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
+dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
+/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
+ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
+dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
+9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
+foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
+qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
+P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
+c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+
+Go Daddy Root Certificate Authority - G2
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
+MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
+A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
+9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
+fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
+NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
+BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
+vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
+5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
+N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+Starfield Root Certificate Authority - G2
+=========================================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
+eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
+DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
+VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
+W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
+bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
+N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
+ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
+JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
+TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
+4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
+F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
+c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+Starfield Services Root Certificate Authority - G2
+==================================================
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
+IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
+dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
+h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
+hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
+LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
+rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
+SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
+E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
+xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
+YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
+-----END CERTIFICATE-----
+
+AffirmTrust Commercial
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
+MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
+DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
+C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
+BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
+MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
+HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
+hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
+qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
+0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
+sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+AffirmTrust Networking
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
+MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
+Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
+dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
+/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
+h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
+HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
+UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
+12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
+WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
+/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+AffirmTrust Premium
+===================
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
+OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
+dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
+BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
+5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
+GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
+p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
+S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
+6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
+/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
+MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
+6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
+L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
+BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
+IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
+g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
+zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+AffirmTrust Premium ECC
+=======================
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
+BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
+MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
+cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
+N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
+BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
+BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
+57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
+eQ==
+-----END CERTIFICATE-----
+
+Certum Trusted Network CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
+ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
+MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
+ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
+l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
+J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
+fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
+cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
+Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
+DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
+jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
+mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
+Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+Certinomis - Autorité Racine
+=============================
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg
+LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG
+A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw
+JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa
+wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly
+Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw
+2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N
+jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q
+c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC
+lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb
+xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g
+530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna
+4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x
+WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva
+R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40
+nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B
+CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv
+JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE
+qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b
+WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE
+wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/
+vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+
+Root CA Generalitat Valenciana
+==============================
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE
+ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290
+IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3
+WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE
+CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2
+F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B
+ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ
+D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte
+JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB
+AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n
+dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB
+ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl
+AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA
+YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy
+AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt
+AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA
+YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu
+AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA
+OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0
+dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV
+BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S
+b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh
+TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz
+Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63
+NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH
+iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt
++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+
+A-Trust-nQual-03
+================
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE
+Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy
+a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R
+dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw
+RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0
+ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1
+c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA
+zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n
+yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE
+SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4
+iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V
+cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV
+eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40
+ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr
+sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd
+JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6
+ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+
+TWCA Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
+VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
+EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
+IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
+QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
+oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
+4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
+y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
+9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
+mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
+QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
+T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
+Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+Security Communication RootCA2
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
+SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
+aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
+3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
+spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
+EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
+QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
+u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
+3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
+tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
+mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+EC-ACC
+======
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
+BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
+ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
+VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
+CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
+BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
+MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
+SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
+Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
+cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
+w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
+ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
+HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
+E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
+0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
+VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
+Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
+dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
+lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
+Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
+l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
+E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
+5EI=
+-----END CERTIFICATE-----
+
+Hellenic Academic and Research Institutions RootCA 2011
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
+O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
+aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
+AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
+IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
+1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
+71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
+8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
+3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
+MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
+MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
+b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
+XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
+/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
+7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+
+Actalis Authentication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
+BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
+AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
+MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
+IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
+wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
+by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
+zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
+YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
+oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
+EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
+hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
+EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
+jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
+iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
+WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
+JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
+K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
+Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
+4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
+2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
+lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
+OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
+vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+Trustis FPS Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
+EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
+IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
+BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ
+RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
+H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
+cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
+o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
+AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
+BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c
+GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
+yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P
+8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
+l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
+iB6XzCGcKQENZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ
+Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0
+dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu
+c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv
+bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0
+aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t
+L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG
+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5
+fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm
+N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN
+Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T
+tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX
+e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA
+2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs
+HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib
+D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority G2
+===================================
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE
+ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O
+o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG
+4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi
+Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul
+Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs
+O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H
+vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L
+nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS
+FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa
+z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ
+KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk
+J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+
+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG
+/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc
+nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld
+blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc
+l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm
+7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm
+obp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+
+Buypass Class 2 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
+DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
+g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
+9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
+/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
+CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
+awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
+zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
+Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
+Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
+M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
+osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
+aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
+DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
+LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
+oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
+wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
+CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
+rJgWVqA=
+-----END CERTIFICATE-----
+
+Buypass Class 3 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
+DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
+sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
+5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
+7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
+ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
+2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
+/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
+RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
+Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
+j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
+uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
+Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
+ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
+KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
+6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
+UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
+eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
+Cp/HuZc=
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 3
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
+MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
+9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
+NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
+iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
+0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
+AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
+fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
+ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
+P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
+-----END CERTIFICATE-----
+
+EE Certification Centre Root CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy
+dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw
+MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB
+UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy
+ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM
+TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2
+rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw
+93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN
+P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ
+MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
+BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj
+xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM
+lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU
+3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM
+dcGWxZ0=
+-----END CERTIFICATE-----
diff --git a/conf/config.rb b/conf/config.rb
new file mode 100644
index 0000000..91d8453
--- /dev/null
+++ b/conf/config.rb
@@ -0,0 +1,50 @@
+Vines::Config.configure do
+  # Set the logging level to debug, info, warn, error, or fatal. The debug
+  # level logs all XML sent and received by the server.
+  # If you want logging to STDOUT remove the path
+  # e.g. `log 'log/vines.log' do` becomes `log do`
+  log 'log/vines.log' do
+    level :info
+  end
+
+  # Set the directory in which to look for virtual hosts' TLS certificates.
+  # This is optional and defaults to the conf/certs directory created during
+  # `vines init`.
+  certs 'conf/certs'
+
+  # Set the maximum of offline messages stored per user.
+  # If it exceeds, old messages will be deleted.
+  max_offline_msgs 150
+
+  host 'diaspora' do
+    cross_domain_messages true
+    accept_self_signed false
+    force_s2s_encryption false
+    storage 'sql'
+  end
+
+  # Configure the client-to-server port. The max_resources_per_account attribute
+  # limits how many concurrent connections one user can have to the server.
+  client '0.0.0.0', 5222 do
+    max_stanza_size 65536
+    max_resources_per_account 5
+  end
+
+  # Configure the server-to-server port. The max_stanza_size attribute should be
+  # much larger than the setting for client-to-server.
+  server '0.0.0.0', 5269 do
+    max_stanza_size 131072
+    blacklist []
+  end
+
+  # Configure the built-in HTTP server that serves static files and responds to
+  # XEP-0124 BOSH requests. This allows HTTP clients to connect to
+  # the XMPP server.
+  http '0.0.0.0', 5280 do
+    bind '/http-bind'
+    max_stanza_size 65536
+    max_resources_per_account 5
+    root 'public'
+    vroute ''
+  end
+end
diff --git a/lib/vines.rb b/lib/vines.rb
new file mode 100644
index 0000000..139e7ea
--- /dev/null
+++ b/lib/vines.rb
@@ -0,0 +1,216 @@
+# encoding: UTF-8
+
+module Vines
+  NAMESPACES = {
+    :stream           => 'http://etherx.jabber.org/streams'.freeze,
+    :client           => 'jabber:client'.freeze,
+    :server           => 'jabber:server'.freeze,
+    :component        => 'jabber:component:accept'.freeze,
+    :roster           => 'jabber:iq:roster'.freeze,
+    :non_sasl         => 'jabber:iq:auth'.freeze,
+    :storage          => 'jabber:iq:private'.freeze,
+    :version          => 'jabber:iq:version'.freeze,
+    :sasl             => 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze,
+    :tls              => 'urn:ietf:params:xml:ns:xmpp-tls'.freeze,
+    :bind             => 'urn:ietf:params:xml:ns:xmpp-bind'.freeze,
+    :session          => 'urn:ietf:params:xml:ns:xmpp-session'.freeze,
+    :stanzas          => 'urn:ietf:params:xml:ns:xmpp-stanzas'.freeze,
+    :ping             => 'urn:xmpp:ping'.freeze,
+    :delay            => 'urn:xmpp:delay'.freeze,
+    :pubsub           => 'http://jabber.org/protocol/pubsub'.freeze,
+    :pubsub_event     => 'http://jabber.org/protocol/pubsub#event'.freeze,
+    :pubsub_create    => 'http://jabber.org/protocol/pubsub#create-nodes'.freeze,
+    :pubsub_delete    => 'http://jabber.org/protocol/pubsub#delete-nodes'.freeze,
+    :pubsub_instant   => 'http://jabber.org/protocol/pubsub#instant-nodes'.freeze,
+    :pubsub_item_ids  => 'http://jabber.org/protocol/pubsub#item-ids'.freeze,
+    :pubsub_publish   => 'http://jabber.org/protocol/pubsub#publish'.freeze,
+    :pubsub_subscribe => 'http://jabber.org/protocol/pubsub#subscribe'.freeze,
+    :disco_items      => 'http://jabber.org/protocol/disco#items'.freeze,
+    :disco_info       => 'http://jabber.org/protocol/disco#info'.freeze,
+    :http_bind        => 'http://jabber.org/protocol/httpbind'.freeze,
+    :offline          => 'msgoffline'.freeze,
+    :bosh             => 'urn:xmpp:xbosh'.freeze,
+    :vcard            => 'vcard-temp'.freeze,
+    :vcard_update     => 'vcard-temp:x:update'.freeze,
+    :si               => 'http://jabber.org/protocol/si'.freeze,
+    :byte_streams     => 'http://jabber.org/protocol/bytestreams'.freeze,
+    :dialback         => 'urn:xmpp:features:dialback'.freeze,
+    :legacy_dialback  => 'jabber:server:dialback'.freeze
+  }.freeze
+
+  module Log
+    @@logger = nil
+    def log
+      unless @@logger
+        @@logger = Logger.new(STDOUT)
+        @@logger.level = Logger::INFO
+        @@logger.progname = 'vines'
+        @@logger.formatter = Class.new(Logger::Formatter) do
+          def initialize
+            @time = "%Y-%m-%dT%H:%M:%SZ".freeze
+            @fmt  = "[%s] %5s -- %s: %s\n".freeze
+          end
+          def call(severity, time, program, msg)
+            @fmt % [time.utc.strftime(@time), severity, program, msg2str(msg)]
+          end
+        end.new
+      end
+      @@logger
+    end
+  end
+end
+
+%w[
+  active_record
+  base64
+  bcrypt
+  digest/sha1
+  em-hiredis
+  eventmachine
+  fiber
+  fileutils
+  http/parser
+  json
+  logger
+  nokogiri
+  openssl
+  optparse
+  resolv
+  set
+  socket
+  time
+  uri
+  yaml
+
+  vines/cli
+  vines/log
+  vines/jid
+
+  vines/stanza
+  vines/stanza/iq
+  vines/stanza/iq/query
+  vines/stanza/iq/auth
+  vines/stanza/iq/disco_info
+  vines/stanza/iq/disco_items
+  vines/stanza/iq/error
+  vines/stanza/iq/ping
+  vines/stanza/iq/private_storage
+  vines/stanza/iq/result
+  vines/stanza/iq/roster
+  vines/stanza/iq/session
+  vines/stanza/iq/vcard
+  vines/stanza/iq/version
+  vines/stanza/dialback
+  vines/stanza/message
+  vines/stanza/presence
+  vines/stanza/presence/error
+  vines/stanza/presence/probe
+  vines/stanza/presence/subscribe
+  vines/stanza/presence/subscribed
+  vines/stanza/presence/unavailable
+  vines/stanza/presence/unsubscribe
+  vines/stanza/presence/unsubscribed
+  vines/stanza/pubsub
+  vines/stanza/pubsub/create
+  vines/stanza/pubsub/delete
+  vines/stanza/pubsub/publish
+  vines/stanza/pubsub/subscribe
+  vines/stanza/pubsub/unsubscribe
+
+  vines/storage
+  vines/storage/local
+  vines/storage/sql
+  vines/storage/null
+
+  vines/config
+  vines/config/host
+  vines/config/port
+  vines/config/pubsub
+
+  vines/store
+  vines/contact
+  vines/daemon
+  vines/error
+  vines/kit
+  vines/node
+  vines/router
+  vines/token_bucket
+  vines/user
+  vines/version
+  vines/xmpp_server
+
+  vines/cluster
+  vines/cluster/connection
+  vines/cluster/publisher
+  vines/cluster/pubsub
+  vines/cluster/sessions
+  vines/cluster/subscriber
+
+  vines/stream
+  vines/stream/sasl
+  vines/stream/state
+  vines/stream/parser
+
+  vines/stream/client
+  vines/stream/client/session
+  vines/stream/client/start
+  vines/stream/client/tls
+  vines/stream/client/auth_restart
+  vines/stream/client/auth
+  vines/stream/client/bind_restart
+  vines/stream/client/bind
+  vines/stream/client/ready
+  vines/stream/client/closed
+
+  vines/stream/component
+  vines/stream/component/start
+  vines/stream/component/handshake
+  vines/stream/component/ready
+
+  vines/stream/http
+  vines/stream/http/session
+  vines/stream/http/sessions
+  vines/stream/http/request
+  vines/stream/http/start
+  vines/stream/http/auth
+  vines/stream/http/bind_restart
+  vines/stream/http/bind
+  vines/stream/http/ready
+
+  vines/stream/server
+  vines/stream/server/start
+  vines/stream/server/auth_method
+  vines/stream/server/auth_restart
+  vines/stream/server/auth
+  vines/stream/server/final_restart
+  vines/stream/server/ready
+
+  vines/stream/server/outbound/start
+  vines/stream/server/outbound/auth
+  vines/stream/server/outbound/tls_result
+  vines/stream/server/outbound/authoritative
+  vines/stream/server/outbound/auth_restart
+  vines/stream/server/outbound/auth_external
+  vines/stream/server/outbound/auth_external_result
+  vines/stream/server/outbound/auth_dialback_result
+  vines/stream/server/outbound/final_restart
+  vines/stream/server/outbound/final_features
+
+  vines/command/cert
+  vines/command/restart
+  vines/command/start
+  vines/command/stop
+].each {|f| require f }
+
+# Try loading diaspora configuration
+%w[
+  config/application.rb
+  config/load_config.rb
+  config/initializers/devise.rb
+].each {|c|
+  begin
+    require "#{Dir.pwd}/#{c}"
+  rescue LoadError
+    puts "Was not able to load #{c}! This not a standalone version. You should use it only in a diaspora environment."
+  end
+}
diff --git a/lib/vines/cli.rb b/lib/vines/cli.rb
new file mode 100644
index 0000000..c5c9029
--- /dev/null
+++ b/lib/vines/cli.rb
@@ -0,0 +1,103 @@
+module Vines
+  # The command line application that's invoked by the `vines` binary included
+  # in the gem. Parses the command line arguments to create a new server
+  # directory, and starts and stops the server.
+  class CLI
+    COMMANDS = %w[start stop restart cert]
+
+    def self.start
+      self.new.start
+    end
+
+    # Run the command line application to parse arguments and run sub-commands.
+    # Exits the process with a non-zero return code to indicate failure.
+    #
+    # Returns nothing.
+    def start
+      opts = parse(ARGV)
+      command = Command.const_get(opts[:command].capitalize).new
+      begin
+        command.run(opts)
+      rescue SystemExit
+        # do nothing
+      end
+    end
+
+    private
+
+    # Parse the command line arguments and run the matching sub-command
+    # (e.g. init, start, stop, etc).
+    #
+    # args - The ARGV array provided by the command line.
+    #
+    # Returns nothing.
+    def parse(args)
+      options = {}
+      parser = OptionParser.new do |opts|
+        opts.banner = "Usage: vines [options] #{COMMANDS.join('|')}"
+
+        opts.separator ""
+        opts.separator "Daemon options:"
+
+        opts.on('-d', '--daemonize', 'Run daemonized in the background') do |daemonize|
+          options[:daemonize] = daemonize
+        end
+
+        options[:log] = 'log/vines.log'
+        opts.on('-l', '--log FILE', 'File to redirect output (default: log/vines.log)') do |log|
+          options[:log] = log
+        end
+
+        options[:pid] = 'pid/vines.pid'
+        opts.on('-P', '--pid FILE', 'File to store PID (default: pid/vines.pid)') do |pid|
+          options[:pid] = pid
+        end
+
+        opts.separator ""
+        opts.separator "Common options:"
+
+        opts.on('-h', '--help', 'Show this message') do |help|
+          options[:help] = help
+        end
+
+        opts.on('-v', '--version', 'Show version') do |version|
+          options[:version] = version
+        end
+      end
+
+      begin
+        parser.parse!(args)
+      rescue
+        puts parser
+        exit(1)
+      end
+
+      if options[:version]
+        puts Vines::VERSION
+        exit
+      end
+
+      if options[:help]
+        puts parser
+        exit
+      end
+
+      command = args.shift
+      unless COMMANDS.include?(command)
+        puts parser
+        exit(1)
+      end
+
+      options.tap do |opts|
+        opts[:args]    = args
+        opts[:command] = command
+        opts[:config]  = File.expand_path("conf/config.rb")
+        opts[:pid]     = File.expand_path(opts[:pid])
+        opts[:log]     = File.expand_path(opts[:log])
+        if defined? AppConfig
+          opts[:config] = "vines/config/diaspora"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster.rb b/lib/vines/cluster.rb
new file mode 100644
index 0000000..29f559e
--- /dev/null
+++ b/lib/vines/cluster.rb
@@ -0,0 +1,246 @@
+# encoding: UTF-8
+
+module Vines
+  # Server instances may be connected to one another in a cluster so they
+  # can host a single chat domain, or set of domains, across many servers,
+  # transparently to users. A redis database is used for the session routing
+  # table, mapping JIDs to their node's location. Redis pubsub channels are
+  # used to communicate amongst nodes.
+  #
+  # Using a shared in-memory cache, like redis, rather than synchronizing the
+  # cache to each node, allows us to add cluster nodes dynamically, without
+  # updating all other nodes' config files. It also greatly reduces the amount
+  # of memory required by the chat server processes.
+  class Cluster
+    include Vines::Log
+
+    attr_reader :id
+
+    %w[host port database password].each do |name|
+      define_method(name) do |*args|
+        if args.first
+          @connection.send("#{name}=", args.first)
+        else
+          @connection.send(name)
+        end
+      end
+    end
+
+    def initialize(config, &block)
+      @config, @id = config, Kit.uuid
+      @connection = Connection.new
+      @sessions = Sessions.new(self)
+      @publisher = Publisher.new(self)
+      @subscriber = Subscriber.new(self)
+      @pubsub = PubSub.new(self)
+      instance_eval(&block)
+    end
+
+    # Join this node to the cluster by broadcasting its state to the
+    # other nodes, subscribing to redis channels, and scheduling periodic
+    # heartbeat broadcasts. This method must be called after initialize
+    # or this node will not be a cluster member.
+    def start
+      @connection.connect
+      @publisher.broadcast(:online)
+      @subscriber.subscribe
+
+      EM.add_periodic_timer(1) { heartbeat }
+
+      at_exit do
+        @publisher.broadcast(:offline)
+        @sessions.delete_all(@id)
+      end
+    end
+
+    # Returns any streams hosted at remote nodes for these JIDs. The streams act
+    # like normal EM::Connections, but are actually proxies that route stanzas
+    # over redis pubsub channels to remote nodes.
+    def remote_sessions(*jids)
+      @sessions.find(*jids).map do |session|
+        StreamProxy.new(self, session)
+      end
+    end
+
+    # Persist the user's session to the shared redis cache so that other cluster
+    # nodes can locate the node hosting this user's connection and route messages
+    # to them.
+    def save_session(jid, attrs)
+      @sessions.save(jid, attrs)
+    end
+
+    # Remove this user from the cluster routing table so that no further stanzas
+    # may be routed to them. This must be called when the user's session is
+    # terminated, either by logout or stream disconnect.
+    def delete_session(jid)
+      @sessions.delete(jid)
+    end
+
+    # Remove all user sessions from the routing table associated with the
+    # given node ID. Cluster nodes call this themselves during normal shutdown.
+    # However, if a node dies without being properly shutdown, the other nodes
+    # will cleanup its sessions when they detect the node is offline.
+    def delete_sessions(node)
+      @sessions.delete_all(node)
+    end
+
+    # Notify the session store that this node is still alive. The node
+    # broadcasts its current time, so all cluster members' clocks don't
+    # necessarily need to be in sync.
+    def poke(node, time)
+      @sessions.poke(node, time)
+    end
+
+    # Send the stanza to the node hosting the user's session. The stanza is
+    # published to the channel to which the remote node is listening for
+    # messages.
+    def route(stanza, node)
+      @publisher.route(stanza, node)
+    end
+
+    # Notify the remote node that the user's roster has changed and it should
+    # reload the user from storage.
+    def update_user(jid, node)
+      @publisher.update_user(jid, node)
+    end
+
+    # Return the shared redis connection for most queries to use.
+    def connection
+      @connection.connect
+    end
+
+    # Create a new redis connection.
+    def connect
+      @connection.create
+    end
+
+    # Turn an asynchronous redis query into a blocking call by pausing the
+    # fiber in which this code is running. Return the result of the query
+    # from this method, rather than passing it to a callback block.
+    def query(name, *args)
+      fiber, yielding = Fiber.current, true
+      req = connection.send(name, *args)
+      req.errback  { fiber.resume rescue yielding = false }
+      req.callback {|response| fiber.resume(response) }
+      Fiber.yield if yielding
+    end
+
+    # Return the connected streams for this user, without any proxy streams
+    # to remote cluster nodes (locally connected streams only).
+    def connected_resources(jid)
+      @config.router.connected_resources(jid, jid, false)
+    end
+
+    # Return the Storage implementation for this domain or nil if the
+    # domain is not hosted here.
+    def storage(domain)
+      @config.storage(domain)
+    end
+
+    # Create a pubsub topic (a.k.a. node), in the given domain, to which
+    # messages may be published. The domain argument will be one of the
+    # configured pubsub subdomains in conf/config.rb (e.g. games.wonderland.lit,
+    # topics.wonderland.lit, etc).
+    def add_pubsub_node(domain, node)
+      @pubsub.add_node(domain, node)
+    end
+
+    # Remove a pubsub topic so messages may no longer be broadcast to it.
+    def delete_pubsub_node(domain, node)
+      @pubsub.delete_node(domain, node)
+    end
+
+    # Subscribe the JID to the pubsub topic so it will receive any messages
+    # published to it.
+    def subscribe_pubsub(domain, node, jid)
+      @pubsub.subscribe(domain, node, jid)
+    end
+
+    # Unsubscribe the JID from the pubsub topic, deregistering its interest
+    # in receiving any messages published to it.
+    def unsubscribe_pubsub(domain, node, jid)
+      @pubsub.unsubscribe(domain, node, jid)
+    end
+
+    # Unsubscribe the JID from all pubsub topics. This is useful when the
+    # JID's session ends by logout or disconnect.
+    def unsubscribe_all_pubsub(domain, jid)
+      @pubsub.unsubscribe_all(domain, jid)
+    end
+
+    # Return true if the pubsub topic exists and messages may be published to it.
+    def pubsub_node?(domain, node)
+      @pubsub.node?(domain, node)
+    end
+
+    # Return true if the JID is a registered subscriber to the pubsub topic and
+    # messages published to it should be routed to the JID.
+    def pubsub_subscribed?(domain, node, jid)
+      @pubsub.subscribed?(domain, node, jid)
+    end
+
+    # Return a list of JIDs subscribed to the pubsub topic.
+    def pubsub_subscribers(domain, node)
+      @pubsub.subscribers(domain, node)
+    end
+
+    private
+
+    # Call this method once per second to broadcast this node's heartbeat and
+    # expire stale user sessions. This method must not raise exceptions or the
+    # timer will stop.
+    def heartbeat
+      @publisher.broadcast(:heartbeat)
+      @sessions.expire
+    rescue => e
+      log.error("Cluster session cleanup failed: #{e}")
+    end
+
+    # StreamProxy behaves like an EM::Connection so that stanzas may be sent to
+    # remote nodes just as they are to locally connected streams. The rest of the
+    # system doesn't know or care that these "streams" send their traffic over
+    # redis pubsub channels.
+    class StreamProxy
+      attr_reader :user
+
+      def initialize(cluster, session)
+        @cluster, @user = cluster, UserProxy.new(cluster, session)
+        @node, @available, @interested, @presence =
+          session.values_at('node', 'available', 'interested', 'presence')
+
+        unless @presence.nil? || @presence.empty?
+          @presence = Nokogiri::XML(@presence).root rescue nil
+        end
+      end
+
+      def available?
+        @available
+      end
+
+      def interested?
+        @interested
+      end
+
+      def last_broadcast_presence
+        @presence
+      end
+
+      def write(stanza)
+        @cluster.route(stanza, @node)
+      end
+    end
+
+    # Proxy User#update_from calls to remote cluster nodes over redis
+    # pubsub channels.
+    class UserProxy < User
+      def initialize(cluster, session)
+        super(jid: session['jid'])
+        @cluster, @node = cluster, session['node']
+      end
+
+      def update_from(user)
+        @cluster.update_user(@jid.bare, @node)
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster/connection.rb b/lib/vines/cluster/connection.rb
new file mode 100644
index 0000000..84d8dca
--- /dev/null
+++ b/lib/vines/cluster/connection.rb
@@ -0,0 +1,26 @@
+# encoding: UTF-8
+
+module Vines
+  class Cluster
+    # Create and cache a redis database connection.
+    class Connection
+      attr_accessor :host, :port, :database, :password
+
+      def initialize
+        @redis, @host, @port, @database, @password = nil, nil, nil, nil, nil
+      end
+
+      # Return a shared redis connection.
+      def connect
+        @redis ||= create
+      end
+
+      # Return a new redis connection.
+      def create
+        conn = EM::Hiredis::Client.new(@host, @port, @password, @database)
+        conn.connect
+        conn
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster/publisher.rb b/lib/vines/cluster/publisher.rb
new file mode 100644
index 0000000..83731ad
--- /dev/null
+++ b/lib/vines/cluster/publisher.rb
@@ -0,0 +1,55 @@
+# encoding: UTF-8
+
+module Vines
+  class Cluster
+    # Broadcast messages to other cluster nodes via redis pubsub channels. All
+    # members subscribe to a channel for heartbeats, online, and offline
+    # messages from other nodes. This allows new nodes to be added to the
+    # cluster dynamically, without configuring all other nodes.
+    class Publisher
+      include Vines::Log
+
+      ALL, STANZA, USER = %w[cluster:nodes:all stanza user].map {|s| s.freeze }
+
+      def initialize(cluster)
+        @cluster = cluster
+      end
+
+      # Publish a :heartbeat, :online, or :offline message to the nodes:all
+      # broadcast channel.
+      def broadcast(type)
+        redis.publish(ALL, {
+          from: @cluster.id,
+          type: type,
+          time: Time.now.to_i
+        }.to_json)
+      end
+
+      # Send the stanza to the node hosting the user's session. The stanza is
+      # published to the channel to which the remote node is listening for
+      # messages.
+      def route(stanza, node)
+        log.debug { "Sent cluster stanza: %s -> %s\n%s\n" % [@cluster.id, node, stanza] }
+        redis.publish("cluster:nodes:#{node}", {
+          from: @cluster.id,
+          type: STANZA,
+          stanza: stanza.to_s
+        }.to_json)
+      end
+
+      # Notify the remote node that the user's roster has changed and it should
+      # reload the user from storage.
+      def update_user(jid, node)
+        redis.publish("cluster:nodes:#{node}", {
+          from: @cluster.id,
+          type: USER,
+          jid: jid.to_s
+        }.to_json)
+      end
+
+      def redis
+        @cluster.connection
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster/pubsub.rb b/lib/vines/cluster/pubsub.rb
new file mode 100644
index 0000000..2e14679
--- /dev/null
+++ b/lib/vines/cluster/pubsub.rb
@@ -0,0 +1,92 @@
+# encoding: UTF-8
+
+module Vines
+  class Cluster
+    # Manages the pubsub topic list and subscribers stored in redis. When a
+    # message is published to a topic, the receiving cluster node broadcasts
+    # the message to all subscribers at all other cluster nodes.
+    class PubSub
+      def initialize(cluster)
+        @cluster = cluster
+      end
+
+      # Create a pubsub topic (a.k.a. node), in the given domain, to which
+      # messages may be published. The domain argument will be one of the
+      # configured pubsub subdomains in conf/config.rb (e.g. games.wonderland.lit,
+      # topics.wonderland.lit, etc).
+      def add_node(domain, node)
+        redis.sadd("pubsub:#{domain}:nodes", node)
+      end
+
+      # Remove a pubsub topic so messages may no longer be broadcast to it.
+      def delete_node(domain, node)
+        redis.smembers("pubsub:#{domain}:subscribers_#{node}") do |subscribers|
+          redis.multi
+          subscribers.each do |jid|
+            redis.srem("pubsub:#{domain}:subscriptions_#{jid}", node)
+          end
+          redis.del("pubsub:#{domain}:subscribers_#{node}")
+          redis.srem("pubsub:#{domain}:nodes", node)
+          redis.exec
+        end
+      end
+
+      # Subscribe the JID to the pubsub topic so it will receive any messages
+      # published to it.
+      def subscribe(domain, node, jid)
+        jid = JID.new(jid)
+        redis.multi
+        redis.sadd("pubsub:#{domain}:subscribers_#{node}", jid.to_s)
+        redis.sadd("pubsub:#{domain}:subscriptions_#{jid}", node)
+        redis.exec
+      end
+
+      # Unsubscribe the JID from the pubsub topic, deregistering its interest
+      # in receiving any messages published to it.
+      def unsubscribe(domain, node, jid)
+        jid = JID.new(jid)
+        redis.multi
+        redis.srem("pubsub:#{domain}:subscribers_#{node}", jid.to_s)
+        redis.srem("pubsub:#{domain}:subscriptions_#{jid}", node)
+        redis.exec
+        redis.scard("pubsub:#{domain}:subscribers_#{node}") do |count|
+          delete_node(domain, node) if count == 0
+        end
+      end
+
+      # Unsubscribe the JID from all pubsub topics. This is useful when the
+      # JID's session ends by logout or disconnect.
+      def unsubscribe_all(domain, jid)
+        jid = JID.new(jid)
+        redis.smembers("pubsub:#{domain}:subscriptions_#{jid}") do |nodes|
+          nodes.each do |node|
+            unsubscribe(domain, node, jid)
+          end
+        end
+      end
+
+      # Return true if the pubsub topic exists and messages may be published to it.
+      def node?(domain, node)
+        @cluster.query(:sismember, "pubsub:#{domain}:nodes", node) == 1
+      end
+
+      # Return true if the JID is a registered subscriber to the pubsub topic and
+      # messages published to it should be routed to the JID.
+      def subscribed?(domain, node, jid)
+        jid = JID.new(jid)
+        @cluster.query(:sismember, "pubsub:#{domain}:subscribers_#{node}", jid.to_s) == 1
+      end
+
+      # Return a list of JIDs subscribed to the pubsub topic.
+      def subscribers(domain, node)
+        @cluster.query(:smembers, "pubsub:#{domain}:subscribers_#{node}")
+      end
+
+      private
+
+      def redis
+        @cluster.connection
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster/sessions.rb b/lib/vines/cluster/sessions.rb
new file mode 100644
index 0000000..a343667
--- /dev/null
+++ b/lib/vines/cluster/sessions.rb
@@ -0,0 +1,125 @@
+# encoding: UTF-8
+
+module Vines
+  class Cluster
+    # Manages the cluster node list and user session routing table stored in
+    # redis. All cluster nodes share this in-memory database to quickly discover
+    # the node hosting a particular user session. Once a session is located,
+    # stanzas can be routed to that node via the +Publisher+.
+    class Sessions
+      include Vines::Log
+
+      NODES = 'cluster:nodes'.freeze
+
+      def initialize(cluster)
+        @cluster, @nodes = cluster, {}
+      end
+
+      # Return the sessions for these JIDs. If a bare JID is used, all sessions
+      # for that user will be returned. If a full JID is used, the session for
+      # that single connected stream is returned.
+      def find(*jids)
+        jids.flatten.map do |jid|
+          jid = JID.new(jid)
+          jid.bare? ? user_sessions(jid) : user_session(jid)
+        end.compact.flatten
+      end
+
+      # Persist the user's session to the shared redis cache so that other cluster
+      # nodes can locate the node hosting this user's connection and route messages
+      # to them.
+      def save(jid, attrs)
+        jid = JID.new(jid)
+        session = {node: @cluster.id}.merge(attrs)
+        redis.multi
+        redis.hset("sessions:#{jid.bare}", jid.resource, session.to_json)
+        redis.sadd("cluster:nodes:#{@cluster.id}", jid.to_s)
+        redis.exec
+      end
+
+      # Remove this user from the cluster routing table so that no further stanzas
+      # may be routed to them. This must be called when the user's session is
+      # terminated, either by logout or stream disconnect.
+      def delete(jid)
+        jid = JID.new(jid)
+        redis.hget("sessions:#{jid.bare}", jid.resource) do |response|
+          if doc = JSON.parse(response) rescue nil
+            redis.multi
+            redis.hdel("sessions:#{jid.bare}", jid.resource)
+            redis.srem("cluster:nodes:#{doc['node']}", jid.to_s)
+            redis.exec
+          end
+        end
+      end
+
+      # Remove all user sessions from the routing table associated with the
+      # given node ID. Cluster nodes call this themselves during normal shutdown.
+      # However, if a node dies without being properly shutdown, the other nodes
+      # will cleanup its sessions when they detect the node is offline.
+      def delete_all(node)
+        @nodes.delete(node)
+        redis.smembers("cluster:nodes:#{node}") do |jids|
+          redis.multi
+          redis.del("cluster:nodes:#{node}")
+          redis.hdel(NODES, node)
+          jids.each do |jid|
+            jid = JID.new(jid)
+            redis.hdel("sessions:#{jid.bare}", jid.resource)
+          end
+          redis.exec
+        end
+      end
+
+      # Cluster nodes broadcast a heartbeat to other members every second. If we
+      # haven't heard from a node in five seconds, assume it's offline and cleanup
+      # its session cache for it. Nodes may die abrubtly, without a chance to clear
+      # their sessions, so other members cleanup for them.
+      def expire
+        redis.hset(NODES, @cluster.id, Time.now.to_i)
+        redis.hgetall(NODES) do |response|
+          now = Time.now
+          expired = Hash[*response].select do |node, active|
+            offset = @nodes[node] || 0
+            (now - offset) - Time.at(active.to_i) > 5
+          end.keys
+          expired.each {|node| delete_all(node) }
+        end
+      end
+
+      # Notify the session store that this node is still alive. The node
+      # broadcasts its current time, so all cluster members' clocks don't
+      # necessarily need to be in sync.
+      def poke(node, time)
+        offset = Time.now.to_i - time
+        @nodes[node] = offset
+      end
+
+      private
+
+      # Return all remote sessions for this user's bare JID.
+      def user_sessions(jid)
+        response = @cluster.query(:hgetall, "sessions:#{jid.bare}") || []
+        Hash[*response].map do |resource, json|
+          if session = JSON.parse(json) rescue nil
+            session['jid'] = JID.new(jid.node, jid.domain, resource).to_s
+          end
+          session
+        end.compact.reject {|session| session['node'] == @cluster.id }
+      end
+
+      # Return the remote session for this full JID or nil if not found.
+      def user_session(jid)
+        response = @cluster.query(:hget, "sessions:#{jid.bare}", jid.resource)
+        return unless response
+        session = JSON.parse(response) rescue nil
+        return if session.nil? || session['node'] == @cluster.id
+        session['jid'] = jid.to_s
+        session
+      end
+
+      def redis
+        @cluster.connection
+      end
+    end
+  end
+end
diff --git a/lib/vines/cluster/subscriber.rb b/lib/vines/cluster/subscriber.rb
new file mode 100644
index 0000000..0d7845a
--- /dev/null
+++ b/lib/vines/cluster/subscriber.rb
@@ -0,0 +1,133 @@
+# encoding: UTF-8
+
+module Vines
+  class Cluster
+    # Subscribes to the redis `nodes:all` broadcast channel to listen for
+    # heartbeats from other cluster members. Also subscribes to a channel
+    # exclusively for this particular node, listening for stanzas routed to us
+    # from other nodes.
+    class Subscriber
+      include Vines::Log
+
+      ALL, FROM, HEARTBEAT, OFFLINE, ONLINE, STANZA, TIME, TO, TYPE, USER =
+        %w[cluster:nodes:all from heartbeat offline online stanza time to type user].map {|s| s.freeze }
+
+      def initialize(cluster)
+        @cluster = cluster
+        @channel = "cluster:nodes:#{@cluster.id}"
+        @messages = EM::Queue.new
+        process_messages
+      end
+
+      # Create a new redis connection and subscribe to the nodes:all broadcast
+      # channel as well as the channel for this cluster node. Redis connections
+      # in subscribe mode cannot be used for other key/value operations.
+      #
+      # Returns nothing.
+      def subscribe
+        conn = @cluster.connect
+        conn.subscribe(ALL)
+        conn.subscribe(@channel)
+        conn.on(:message) do |channel, message|
+          @messages.push([channel, message])
+        end
+      end
+
+      private
+
+      # Recursively process incoming messages from the queue, guaranteeing they
+      # are processed in the order they are received.
+      #
+      # Returns nothing.
+      def process_messages
+        @messages.pop do |channel, message|
+          Fiber.new do
+            on_message(channel, message)
+            process_messages
+          end.resume
+        end
+      end
+
+      # Process messages as they arrive on the pubsub channels to which we're
+      # subscribed.
+      #
+      # channel - The String channel name on which the message was received.
+      # message - The JSON formatted message String.
+      #
+      # Returns nothing.
+      def on_message(channel, message)
+        doc = JSON.parse(message)
+        case channel
+        when ALL      then to_all(doc)
+        when @channel then to_node(doc)
+        end
+      rescue => e
+        log.error("Cluster subscription message failed: #{e}")
+      end
+
+      # Process a message sent to the `nodes:all` broadcast channel. In the case
+      # of node heartbeats, we update the last time we heard from this node so
+      # we can cleanup its session if it goes offline.
+      #
+      # message - The parsed Hash of received message data.
+      #
+      # Returns nothing.
+      def to_all(message)
+        case message[TYPE]
+        when ONLINE, HEARTBEAT
+          @cluster.poke(message[FROM], message[TIME])
+        when OFFLINE
+          @cluster.delete_sessions(message[FROM])
+        end
+      end
+
+      # Process a message published to this node's channel. Messages sent to
+      # this channel are stanzas that need to be routed to connections attached
+      # to this node.
+      #
+      # message - The parsed Hash of received message data.
+      #
+      # Returns nothing.
+      def to_node(message)
+        case message[TYPE]
+        when STANZA then route_stanza(message)
+        when USER   then update_user(message)
+        end
+      end
+
+      # Send the stanza, from a remote cluster node, to locally connected
+      # streams for the destination user.
+      #
+      # message - The parsed Hash of received message data.
+      #
+      # Returns nothing.
+      def route_stanza(message)
+        node = Nokogiri::XML(message[STANZA]).root rescue nil
+        return unless node
+        log.debug { "Received cluster stanza: %s -> %s\n%s\n" % [message[FROM], @cluster.id, node] }
+        if node[TO]
+          @cluster.connected_resources(node[TO]).each do |recipient|
+            recipient.write(node)
+          end
+        else
+          log.warn("Cluster stanza missing address:\n#{node}")
+        end
+      end
+
+      # Update the roster information, that's cached in locally connected
+      # streams, for this user.
+      #
+      # message - The parsed Hash of received message data.
+      #
+      # Returns nothing.
+      def update_user(message)
+        jid = JID.new(message['jid']).bare
+        if user = @cluster.storage(jid.domain).find_user(jid)
+          @cluster.connected_resources(jid).each do |stream|
+            stream.user.update_from(user)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/command/cert.rb b/lib/vines/command/cert.rb
new file mode 100644
index 0000000..7bcbc16
--- /dev/null
+++ b/lib/vines/command/cert.rb
@@ -0,0 +1,50 @@
+# encoding: UTF-8
+
+module Vines
+  module Command
+    class Cert
+      def run(opts)
+        raise 'vines cert <domain>' unless opts[:args].size == 1
+        require opts[:config]
+        create_cert(opts[:args].first, Config.instance.certs)
+      end
+
+      def create_cert(domain, dir)
+        domain = domain.downcase
+        key = OpenSSL::PKey::RSA.generate(2048)
+        ca = OpenSSL::X509::Name.parse("/C=US/ST=Colorado/L=Denver/O=Vines XMPP Server/CN=#{domain}")
+        cert = OpenSSL::X509::Certificate.new
+        cert.version = 2
+        cert.subject = ca
+        cert.issuer = ca
+        cert.serial = Time.now.to_i
+        cert.public_key = key.public_key
+        cert.not_before = Time.now - (24 * 60 * 60)
+        cert.not_after = Time.now + (365 * 24 * 60 * 60)
+
+        factory = OpenSSL::X509::ExtensionFactory.new
+        factory.subject_certificate = cert
+        factory.issuer_certificate = cert
+        cert.extensions = [
+          %w[basicConstraints CA:TRUE],
+          %w[subjectKeyIdentifier hash],
+          %w[subjectAltName] << [domain, hostname].map {|n| "DNS:#{n}" }.join(',')
+        ].map {|k, v| factory.create_ext(k, v) }
+
+        cert.sign(key, OpenSSL::Digest::SHA1.new)
+
+        {'key' => key, 'crt' => cert}.each_pair do |ext, o|
+          name = File.join(dir, "#{domain}.#{ext}")
+          File.open(name, 'w:utf-8') {|f| f.write(o.to_pem) }
+          File.chmod(0600, name) if ext == 'key'
+        end
+      end
+
+      private
+
+      def hostname
+        Socket.gethostbyname(Socket.gethostname).first.downcase
+      end
+    end
+  end
+end
diff --git a/lib/vines/command/restart.rb b/lib/vines/command/restart.rb
new file mode 100644
index 0000000..f7f2530
--- /dev/null
+++ b/lib/vines/command/restart.rb
@@ -0,0 +1,12 @@
+# encoding: UTF-8
+
+module Vines
+  module Command
+    class Restart
+      def run(opts)
+        Stop.new.run(opts)
+        Start.new.run(opts)
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/vines/command/start.rb b/lib/vines/command/start.rb
new file mode 100644
index 0000000..6ab3615
--- /dev/null
+++ b/lib/vines/command/start.rb
@@ -0,0 +1,28 @@
+# encoding: UTF-8
+
+module Vines
+  module Command
+    class Start
+      def run(opts)
+        raise 'vines [--pid FILE] start' unless opts[:args].size == 0
+        require opts[:config]
+        server = XmppServer.new(Config.instance)
+        daemonize(opts) if opts[:daemonize]
+        server.start
+      end
+
+      private
+
+      def daemonize(opts)
+        daemon = Daemon.new(:pid => opts[:pid], :stdout => opts[:log],
+          :stderr => opts[:log])
+        if daemon.running?
+          raise "Vines is running as process #{daemon.pid}"
+        else
+          puts "Vines has started"
+          daemon.start
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/command/stop.rb b/lib/vines/command/stop.rb
new file mode 100644
index 0000000..1474b4f
--- /dev/null
+++ b/lib/vines/command/stop.rb
@@ -0,0 +1,18 @@
+# encoding: UTF-8
+
+module Vines
+  module Command
+    class Stop
+      def run(opts)
+        raise 'vines [--pid FILE] stop' unless opts[:args].size == 0
+        daemon = Daemon.new(:pid => opts[:pid])
+        if daemon.running?
+          daemon.stop
+          puts 'Vines has been shutdown'
+        else
+          puts 'Vines is not running'
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/vines/config.rb b/lib/vines/config.rb
new file mode 100644
index 0000000..a43c6b9
--- /dev/null
+++ b/lib/vines/config.rb
@@ -0,0 +1,236 @@
+# encoding: UTF-8
+
+module Vines
+
+  # A Config object is passed to the stream handlers to give them access
+  # to server configuration information like virtual host names, storage
+  # systems, etc. This class provides the DSL methods used in the
+  # conf/config.rb file.
+  class Config
+    LOG_LEVELS = %w[debug info warn error fatal].freeze
+
+    attr_reader :router
+
+    @@instance = nil
+    def self.configure(&block)
+      @@instance = self.new(&block)
+    end
+
+    def self.instance
+      @@instance
+    end
+
+    def initialize(&block)
+      @max_offline_msgs = 150
+      @certs = File.expand_path('conf/certs')
+      @vhosts, @ports, @cluster = {}, {}, nil
+      @null = Storage::Null.new
+      @router = Router.new(self)
+      instance_eval(&block)
+      raise "must define at least one virtual host" if @vhosts.empty?
+    end
+
+    def certs(dir=nil)
+      dir ? @certs = File.expand_path(dir) : @certs
+    end
+
+    def max_offline_msgs(count=nil)
+      count ? @max_offline_msgs = count : @max_offline_msgs
+    end
+
+    def host(*names, &block)
+      names = names.flatten.map {|name| name.downcase }
+      dupes = names.uniq.size != names.size || (@vhosts.keys & names).any?
+      raise "one host definition per domain allowed" if dupes
+      names.each do |name|
+        @vhosts[name] = Host.new(self, name, &block)
+      end
+    end
+
+    def diaspora_domain
+      AppConfig.environment.url
+        .gsub(/^http(s){0,1}:\/\/|\/$/, '')
+        .to_s rescue "localhost"
+    end
+
+    %w[client server http component].each do |name|
+      define_method(name) do |*args, &block|
+        port = Vines::Config.const_get("#{name.capitalize}Port")
+        raise "one #{name} port definition allowed" if @ports[name.to_sym]
+        @ports[name.to_sym] = port.new(self, *args) do
+          instance_eval(&block) if block
+        end
+      end
+    end
+
+    def cluster(&block)
+      return @cluster unless block
+      raise "one cluster definition allowed" if @cluster
+      @cluster = Cluster.new(self, &block)
+    end
+
+    def log(file=nil, &block)
+      unless file.nil?
+        unless File.exists?(file)
+          File.new(file, 'w') rescue raise "log directory doesn't exists"
+        end
+
+        if File.exists?(file)
+          Vines::Log.set_log_file(file)
+        end
+      end
+      instance_eval(&block) if block  
+    end
+
+    def level(level)
+      const = Logger.const_get(level.to_s.upcase) rescue nil
+      unless LOG_LEVELS.include?(level.to_s) && const
+        raise "log level must be one of: #{LOG_LEVELS.join(', ')}"
+      end
+      Class.new.extend(Vines::Log).log.level = const
+    end
+
+    def ports
+      @ports.values
+    end
+
+    # Return true if the domain is virtual hosted by this server.
+    def vhost?(domain)
+      !!vhost(domain)
+    end
+
+    # Return the Host config object for this domain if it's hosted by this
+    # server.
+    def vhost(domain)
+      @vhosts[domain.to_s]
+    end
+
+    # Returns the storage system for the domain or a Storage::Null instance if
+    # the domain is not hosted at this server.
+    def storage(domain)
+      host = vhost(domain)
+      host ? host.storage : @null
+    end
+
+    # Returns the PubSub system for the domain or nil if pubsub is not enabled
+    # for this domain.
+    def pubsub(domain)
+      host = @vhosts.values.find {|host| host.pubsub?(domain) }
+      host.pubsubs[domain.to_s] if host
+    end
+
+    # Return true if the domain is a pubsub service hosted at a virtual host
+    # at this server.
+    def pubsub?(domain)
+      @vhosts.values.any? {|host| host.pubsub?(domain) }
+    end
+
+    # Return true if all JIDs belong to components hosted by this server.
+    def component?(*jids)
+      !jids.flatten.index do |jid|
+        !component_password(JID.new(jid).domain)
+      end
+    end
+
+    # Return the password for the component or nil if it's not hosted here.
+    def component_password(domain)
+      host = @vhosts.values.find {|host| host.component?(domain) }
+      host.password(domain) if host
+    end
+
+    # Return true if all of the JIDs are hosted by this server.
+    def local_jid?(*jids)
+      !jids.flatten.index do |jid|
+        !vhost?(JID.new(jid).domain)
+      end
+    end
+
+    # Return true if private XML fragment storage is enabled for this domain.
+    def private_storage?(domain)
+      host = vhost(domain)
+      host.private_storage? if host
+    end
+
+    # Returns true if server-to-server connections are allowed with the
+    # given domain.
+    def s2s?(domain)
+      # Disabled whitelisting to allow anonymous hosts,
+      # otherwise everyone has to add manually all hosts.
+      # Using blacklist in case we have to block a malicious host.
+      @ports[:server] && !@ports[:server].blacklist.include?(domain.to_s)
+    end
+
+    # Return true if the server is a member of a cluster, serving the same
+    # domains from different machines.
+    def cluster?
+      !!@cluster
+    end
+
+    # Retrieve the Port subclass with this name:
+    # [:client, :server, :http, :component]
+    def [](name)
+      @ports[name] or raise ArgumentError.new("no port named #{name}")
+    end
+
+    # Return true if the two JIDs are allowed to send messages to each other.
+    # Both domains must have enabled cross_domain_messages in their config files.
+    def allowed?(to, from)
+      to, from = JID.new(to), JID.new(from)
+      return false                      if to.empty? || from.empty?
+      return true                       if to.domain == from.domain # same domain always allowed
+      return cross_domain?(to, from)    if local_jid?(to, from)     # both virtual hosted here
+      return check_subdomains(to, from) if subdomain?(to, from)     # component/pubsub to component/pubsub
+      return check_subdomain(to, from)  if subdomain?(to)           # to component/pubsub
+      return check_subdomain(from, to)  if subdomain?(from)         # from component/pubsub
+      return cross_domain?(to)          if local_jid?(to)           # from is remote
+      return cross_domain?(from)        if local_jid?(from)         # to is remote
+      return false
+    end
+
+    private
+
+    # Return true if all of the JIDs are some kind of subdomain resource hosted
+    # here (either a component or a pubsub domain).
+    def subdomain?(*jids)
+      !jids.flatten.index do |jid|
+        !component?(jid) && !pubsub?(jid)
+      end
+    end
+
+    # Return true if the third-level subdomain JIDs (components and pubsubs)
+    # are allowed to communicate with each other. For example, a
+    # tea.wonderland.lit component should be allowed to send messages to
+    # pubsub.wonderland.lit because they share the second-level wonderland.lit
+    # domain.
+    def check_subdomains(to, from)
+      sub1, sub2 = strip_domain(to), strip_domain(from)
+      (sub1 == sub2) || cross_domain?(sub1, sub2)
+    end
+
+    # Return true if the third-level subdomain JID (component or pubsub) is
+    # allowed to communicate with the other JID. For example,
+    # pubsub.wonderland.lit should be allowed to send messages to
+    # alice at wonderland.lit because they share the second-level wonderland.lit
+    # domain.
+    def check_subdomain(subdomain, jid)
+      comp = strip_domain(subdomain)
+      return true if comp.domain == jid.domain
+      local_jid?(jid) ? cross_domain?(comp, jid) : cross_domain?(comp)
+    end
+
+    # Return the third-level JID's domain with the first subdomain stripped off
+    # to create a second-level domain. For example, alice at tea.wonderland.lit
+    # returns wonderland.lit.
+    def strip_domain(jid)
+      domain = jid.domain.split('.').drop(1).join('.')
+      JID.new(domain)
+    end
+
+    # Return true if all JIDs are allowed to exchange cross domain messages.
+    def cross_domain?(*jids)
+      !jids.flatten.index do |jid|
+        !vhost(jid.domain).cross_domain_messages?
+      end
+    end
+  end
+end
diff --git a/lib/vines/config/diaspora.rb b/lib/vines/config/diaspora.rb
new file mode 100644
index 0000000..24d7907
--- /dev/null
+++ b/lib/vines/config/diaspora.rb
@@ -0,0 +1,37 @@
+# ############################################################## #
+# Do NOT touch this file you'll find the options in diaspora.yml #
+# ############################################################## #
+
+Vines::Config.configure do
+  log AppConfig.chat.server.log.file.to_s do
+    level AppConfig.chat.server.log.level.to_sym
+  end
+
+  certs AppConfig.chat.server.certs.to_s
+
+  max_offline_msgs AppConfig.chat.server.max_offline_msgs.to_i
+
+  host diaspora_domain do
+    cross_domain_messages AppConfig.chat.server.cross_domain_messages
+    accept_self_signed AppConfig.chat.server.accept_self_signed
+    storage 'sql'
+  end
+
+  client AppConfig.chat.server.c2s.address.to_s, AppConfig.chat.server.c2s.port.to_i do
+    max_stanza_size AppConfig.chat.server.c2s.max_stanza_size.to_i
+    max_resources_per_account AppConfig.chat.server.c2s.max_resources_per_account.to_i
+  end
+
+  server AppConfig.chat.server.s2s.address.to_s, AppConfig.chat.server.s2s.port.to_i do
+    max_stanza_size AppConfig.chat.server.s2s.max_stanza_size.to_i
+    blacklist AppConfig.chat.server.s2s.blacklist.get
+  end
+
+  http AppConfig.chat.server.bosh.address.to_s, AppConfig.chat.server.bosh.port.to_i do
+    bind AppConfig.chat.server.bosh.bind.to_s
+    max_stanza_size AppConfig.chat.server.bosh.max_stanza_size.to_i
+    max_resources_per_account AppConfig.chat.server.bosh.max_resources_per_account.to_i
+    root 'public'
+    vroute ''
+  end
+end
diff --git a/lib/vines/config/host.rb b/lib/vines/config/host.rb
new file mode 100644
index 0000000..92ad26e
--- /dev/null
+++ b/lib/vines/config/host.rb
@@ -0,0 +1,137 @@
+# encoding: UTF-8
+
+module Vines
+  class Config
+
+    # Provides the DSL methods for the virtual host definitions in the
+    # conf/config.rb file. Host instances can be accessed at runtime through
+    # the +Config#vhosts+ method.
+    class Host
+      attr_reader :pubsubs
+
+      def initialize(config, name, &block)
+        @config, @name = config, name.downcase
+        @storage = nil
+        @cross_domain_messages = false
+        @private_storage = false
+        @accept_self_signed = false
+        @force_s2s_encryption = false
+        @components, @pubsubs = {}, {}
+        validate_domain(@name)
+        instance_eval(&block)
+        raise "storage required for #{@name}" unless @storage
+      end
+
+      def storage(name=nil, &block)
+        if name
+          raise "one storage mechanism per host allowed" if @storage
+          @storage = Storage.from_name(name, &block)
+        else
+          @storage
+        end
+      end
+
+      def force_s2s_encryption(enabled)
+        @force_s2s_encryption = !!enabled
+      end
+
+      def force_s2s_encryption?
+        @force_s2s_encryption
+      end
+
+      def accept_self_signed(enabled)
+        @accept_self_signed = !!enabled
+      end
+
+      def accept_self_signed?
+        @accept_self_signed
+      end
+
+      def cross_domain_messages(enabled)
+        @cross_domain_messages = !!enabled
+      end
+
+      def cross_domain_messages?
+        @cross_domain_messages
+      end
+
+      def components(options=nil)
+        return @components unless options
+
+        names = options.keys.map {|domain| "#{domain}.#{@name}".downcase }
+        raise "duplicate component domains not allowed" if dupes?(names, @components.keys)
+        raise "pubsub domains overlap component domains" if dupes?(names, @pubsubs.keys)
+
+        options.each do |domain, password|
+          raise 'component domain required' if (domain || '').to_s.strip.empty?
+          raise 'component password required' if (password || '').strip.empty?
+          name = "#{domain}.#{@name}".downcase
+          raise "components must be one level below their host: #{name}" if domain.to_s.include?('.')
+          validate_domain(name)
+          @components[name] = password
+        end
+      end
+
+      def component?(domain)
+        !!@components[domain.to_s]
+      end
+
+      def password(domain)
+        @components[domain.to_s]
+      end
+
+      def pubsub(*domains)
+        domains.flatten!
+        raise 'define at least one pubsub domain' if domains.empty?
+        names = domains.map {|domain| "#{domain}.#{@name}".downcase }
+        raise "duplicate pubsub domains not allowed" if dupes?(names, @pubsubs.keys)
+        raise "pubsub domains overlap component domains" if dupes?(names, @components.keys)
+        domains.each do |domain|
+          raise 'pubsub domain required' if (domain || '').to_s.strip.empty?
+          name = "#{domain}.#{@name}".downcase
+          raise "pubsub domains must be one level below their host: #{name}" if domain.to_s.include?('.')
+          validate_domain(name)
+          @pubsubs[name] = PubSub.new(@config, name)
+        end
+      end
+
+      def pubsub?(domain)
+        @pubsubs.key?(domain.to_s)
+      end
+
+      # Unsubscribe this JID from all pubsub topics hosted at this virtual host.
+      # This should be called when the user's session ends via logout or
+      # disconnect.
+      def unsubscribe_pubsub(jid)
+        @pubsubs.values.each do |pubsub|
+          pubsub.unsubscribe_all(jid)
+        end
+      end
+
+      def disco_items
+        [@components.keys, @pubsubs.keys].flatten.sort
+      end
+
+      def private_storage(enabled)
+        @private_storage = !!enabled
+      end
+
+      def private_storage?
+        @private_storage
+      end
+
+      private
+
+      # Return true if the arrays contain any duplicate items.
+      def dupes?(a, b)
+        a.uniq.size != a.size || b.uniq.size != b.size || (a & b).any?
+      end
+
+      # Prevent domains in config files that won't form valid JIDs.
+      def validate_domain(name)
+        jid = JID.new(name)
+        raise "incorrect domain: #{name}" if jid.node || jid.resource
+      end
+    end
+  end
+end
diff --git a/lib/vines/config/port.rb b/lib/vines/config/port.rb
new file mode 100644
index 0000000..40f7fad
--- /dev/null
+++ b/lib/vines/config/port.rb
@@ -0,0 +1,132 @@
+# encoding: UTF-8
+
+module Vines
+  class Config
+    class Port
+      include Vines::Log
+
+      attr_reader :config, :stream
+
+      %w[host port].each do |name|
+        define_method(name) do
+          @settings[name.to_sym]
+        end
+      end
+
+      def initialize(config, host, port, &block)
+        @config, @settings = config, {}
+        instance_eval(&block) if block
+        defaults = {:host => host, :port => port,
+          :max_resources_per_account => 5, :max_stanza_size => 128 * 1024}
+        @settings = defaults.merge(@settings)
+      end
+
+      def max_stanza_size(max=nil)
+        if max
+          # rfc 6120 section 13.12
+          @settings[:max_stanza_size] = [10000, max].max
+        else
+          @settings[:max_stanza_size]
+        end
+      end
+
+      def start
+        type = stream.name.split('::').last.downcase
+        log.info("Accepting #{type} connections on #{host}:#{port}")
+        EventMachine::start_server(host, port, stream, config)
+      end
+    end
+
+    class ClientPort < Port
+      def initialize(config, host='0.0.0.0', port=5222, &block)
+        @stream = Vines::Stream::Client
+        super(config, host, port, &block)
+      end
+
+      def max_resources_per_account(max=nil)
+        if max
+          @settings[:max_resources_per_account] = max
+        else
+          @settings[:max_resources_per_account]
+        end
+      end
+
+      def start
+        super
+        config.cluster.start if config.cluster?
+      end
+    end
+
+    class ServerPort < Port
+      def initialize(config, host='0.0.0.0', port=5269, &block)
+        @blacklist, @stream = [], Vines::Stream::Server
+        super(config, host, port, &block)
+      end
+
+      def blacklist(*blacklist)
+        if blacklist.any?
+          @blacklist << blacklist
+          @blacklist.flatten!
+        else
+          @blacklist
+        end
+      end
+    end
+
+    class HttpPort < Port
+      def initialize(config, host='0.0.0.0', port=5280, &block)
+        @stream = Vines::Stream::Http
+        super(config, host, port, &block)
+        defaults = {:root => File.expand_path('web'), :bind => '/xmpp'}
+        @settings = defaults.merge(@settings)
+      end
+
+      def max_resources_per_account(max=nil)
+        if max
+          @settings[:max_resources_per_account] = max
+        else
+          @settings[:max_resources_per_account]
+        end
+      end
+
+      def root(dir=nil)
+        if dir
+          @settings[:root] = File.expand_path(dir)
+        else
+          @settings[:root]
+        end
+      end
+
+      def bind(url=nil)
+        if url
+          @settings[:bind] = url
+        else
+          @settings[:bind]
+        end
+      end
+
+      def vroute(id=nil)
+        if id
+          id = id.to_s.strip
+          @settings[:vroute] = id.empty? ? nil : id
+        else
+          @settings[:vroute]
+        end
+      end
+
+      def start
+        super
+        if config.cluster? && vroute.nil?
+          log.warn("vroute sticky session cookie not set")
+        end
+      end
+    end
+
+    class ComponentPort < Port
+      def initialize(config, host='0.0.0.0', port=5347, &block)
+        @stream = Vines::Stream::Component
+        super(config, host, port, &block)
+      end
+    end
+  end
+end
diff --git a/lib/vines/config/pubsub.rb b/lib/vines/config/pubsub.rb
new file mode 100644
index 0000000..2423629
--- /dev/null
+++ b/lib/vines/config/pubsub.rb
@@ -0,0 +1,108 @@
+# encoding: UTF-8
+
+module Vines
+  class Config
+    # Provides the configuration DSL to conf/config.rb for pubsub subdomains and
+    # exposes the storage and notification systems that the pubsub stanzas need
+    # to process. This class hides the complexity of determining pubsub behavior
+    # in a standalone vs. clustered chat server environment from the stanzas.
+    class PubSub
+      def initialize(config, name)
+        @config, @name = config, name
+        @nodes = {}
+      end
+
+      def add_node(id)
+        if @config.cluster?
+          @config.cluster.add_pubsub_node(@name, id)
+        else
+          @nodes[id] ||= Set.new
+        end
+      end
+
+      def delete_node(id)
+        if @config.cluster?
+          @config.cluster.delete_pubsub_node(@name, id)
+        else
+          @nodes.delete(id)
+        end
+      end
+
+      def subscribe(node, jid)
+        return unless node?(node) && @config.allowed?(jid, @name)
+        if @config.cluster?
+          @config.cluster.subscribe_pubsub(@name, node, jid)
+        else
+          @nodes[node] << JID.new(jid)
+        end
+      end
+
+      def unsubscribe(node, jid)
+        return unless node?(node)
+        if @config.cluster?
+          @config.cluster.unsubscribe_pubsub(@name, node, jid)
+        else
+          @nodes[node].delete(JID.new(jid))
+          delete_node(node) if subscribers(node).empty?
+        end
+      end
+
+      def unsubscribe_all(jid)
+        if @config.cluster?
+          @config.cluster.unsubscribe_all_pubsub(@name, jid)
+        else
+          @nodes.keys.each do |node|
+            unsubscribe(node, jid)
+          end
+        end
+      end
+
+      def node?(node)
+        if @config.cluster?
+          @config.cluster.pubsub_node?(@name, node)
+        else
+          @nodes.key?(node)
+        end
+      end
+
+      def subscribed?(node, jid)
+        return false unless node?(node)
+        if @config.cluster?
+          @config.cluster.pubsub_subscribed?(@name, node, jid)
+        else
+          @nodes[node].include?(JID.new(jid))
+        end
+      end
+
+      def publish(node, stanza)
+        stanza['id'] = Kit.uuid
+        stanza['from'] = @name
+
+        local, remote = subscribers(node).partition {|jid| @config.local_jid?(jid) }
+
+        local.flat_map do |jid|
+          @config.router.connected_resources(jid, @name)
+        end.each do |recipient|
+          stanza['to'] = recipient.user.jid.to_s
+          recipient.write(stanza)
+        end
+
+        remote.each do |jid|
+          el = stanza.clone
+          el['to'] = jid.to_s
+          @config.router.route(el) rescue nil # ignore RemoteServerNotFound
+        end
+      end
+
+      private
+
+      def subscribers(node)
+        if @config.cluster?
+          @config.cluster.pubsub_subscribers(@name, node)
+        else
+          @nodes[node] || []
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/contact.rb b/lib/vines/contact.rb
new file mode 100644
index 0000000..4701da9
--- /dev/null
+++ b/lib/vines/contact.rb
@@ -0,0 +1,115 @@
+# encoding: UTF-8
+
+module Vines
+  class Contact
+    include Comparable
+
+    attr_accessor :name, :subscription, :ask, :groups, :from_diaspora
+    attr_reader :jid
+
+    def initialize(args={})
+      @jid = JID.new(args[:jid]).bare
+      raise ArgumentError, 'invalid jid' if @jid.empty?
+      @name = args[:name]
+      @subscription = args[:subscription] || 'none'
+      @from_diaspora = args[:from_diaspora] || false
+      @ask = args[:ask]
+      @groups = args[:groups] || []
+    end
+
+    def <=>(contact)
+      contact.is_a?(Contact) ? self.jid.to_s <=> contact.jid.to_s : nil
+    end
+
+    alias :eql? :==
+
+    def hash
+      jid.to_s.hash
+    end
+
+    def update_from(contact)
+      @name = contact.name
+      @subscription = contact.subscription
+      @from_diaspora = contact.from_diaspora
+      @ask = contact.ask
+      @groups = contact.groups.clone
+    end
+
+    # Returns true if this contact is in a state that allows the user
+    # to subscribe to their presence updates.
+    def can_subscribe?
+      @ask == 'subscribe' && %w[none from].include?(@subscription)
+    end
+
+    def subscribe_to
+      @subscription = (@subscription == 'none') ? 'to' : 'both'
+      @ask = nil
+    end
+
+    def unsubscribe_to
+      @subscription = (@subscription == 'both') ? 'from' : 'none'
+    end
+
+    def subscribe_from
+      @subscription = (@subscription == 'none') ? 'from' : 'both'
+      @ask = nil
+    end
+
+    def unsubscribe_from
+      @subscription = (@subscription == 'both') ? 'to' : 'none'
+    end
+
+    # Returns true if the user is subscribed to this contact's
+    # presence updates.
+    def subscribed_to?
+      %w[to both].include?(@subscription)
+    end
+
+    # Returns true if the user has a presence subscription from
+    # this contact. The contact is subscribed to this user's presence.
+    def subscribed_from?
+      %w[from both].include?(@subscription)
+    end
+
+    # Returns a hash of this contact's attributes suitable for persisting in
+    # a document store.
+    def to_h
+      {
+        'name' => @name,
+        'subscription' => @subscription,
+        'from_diaspora' => @from_diaspora,
+        'ask' => @ask,
+        'groups' => @groups.sort!
+      }
+    end
+
+    # Write an iq stanza to the recipient stream representing this contact's
+    # current roster item state.
+    def send_roster_push(recipient)
+      doc = Nokogiri::XML::Document.new
+      node = doc.create_element('iq',
+        'id'   => Kit.uuid,
+        'to'   => recipient.user.jid.to_s,
+        'type' => 'set')
+      node << doc.create_element('query', 'xmlns' => NAMESPACES[:roster]) do |query|
+        query << to_roster_xml
+      end
+      recipient.write(node)
+    end
+
+    # Returns this contact as an xmpp <item> element.
+    def to_roster_xml
+      doc = Nokogiri::XML::Document.new
+      doc.create_element('item') do |el|
+        el['ask'] = @ask unless @ask.nil? || @ask.empty?
+        el['jid'] = @jid.bare.to_s
+        el['name'] = @name unless @name.nil? || @name.empty?
+        el['subscription'] = @subscription
+        el['from_diaspora'] = @from_diaspora
+        @groups.sort!.each do |group|
+          el << doc.create_element('group', group)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/daemon.rb b/lib/vines/daemon.rb
new file mode 100644
index 0000000..a666b38
--- /dev/null
+++ b/lib/vines/daemon.rb
@@ -0,0 +1,78 @@
+# encoding: UTF-8
+
+module Vines
+
+  # Fork the current process into the background and manage pid
+  # files so we can kill the process later.
+  class Daemon
+
+    # Configure a new daemon process.  Arguments hash can include the following
+    # keys: :pid (pid file name, required),
+    # :stdin, :stdout, :stderr (default to /dev/null)
+    def initialize(args)
+      @pid = args[:pid]
+      raise ArgumentError.new('pid file is required') unless @pid
+      raise ArgumentError.new('pid must be a file name') if File.directory?(@pid)
+      raise ArgumentError.new('pid file must be writable') unless File.writable?(File.dirname(@pid))
+      @stdin, @stdout, @stderr = [:stdin, :stdout, :stderr].map {|k| args[k] || '/dev/null' }
+    end
+
+    # Fork the current process into the background to start the
+    # daemon. Do nothing if the daemon is already running.
+    def start
+      daemonize unless running?
+    end
+
+    # Use the pid stored in the pid file created from a previous
+    # call to start to send a TERM signal to the process. Do nothing
+    # if the daemon is not running.
+    def stop
+      10.times do
+        break unless running?
+        Process.kill('TERM', pid)
+        sleep(0.1)
+      end
+    end
+
+    # Returns true if the process is running as determined by the numeric
+    # pid stored in the pid file created by a previous call to start.
+    def running?
+      begin
+        pid && Process.kill(0, pid)
+      rescue Errno::ESRCH
+        delete_pid
+        false
+      rescue Errno::EPERM
+        true
+      end
+    end
+
+    # Returns the numeric process ID from the pid file.
+    # If the pid file does not exist, returns nil.
+    def pid
+      File.read(@pid).to_i if File.exists?(@pid) 
+    end
+
+    private
+
+    def delete_pid
+      File.delete(@pid) if File.exists?(@pid)
+    end
+
+    # Fork process into background twice to release it from
+    # the controlling tty. Point open file descriptors shared
+    # with the parent process to separate destinations (e.g. /dev/null).
+    def daemonize
+      exit if fork
+      Process.setsid
+      exit if fork
+      Dir.chdir('/')
+      $stdin.reopen(@stdin)
+      $stdout.reopen(@stdout, 'a').sync = true
+      $stderr.reopen(@stderr, 'a').sync = true
+      File.open(@pid, 'w') {|f| f.write(Process.pid) }
+      at_exit { delete_pid }
+      trap('TERM') { exit }
+    end
+  end
+end
diff --git a/lib/vines/error.rb b/lib/vines/error.rb
new file mode 100644
index 0000000..2098ed0
--- /dev/null
+++ b/lib/vines/error.rb
@@ -0,0 +1,150 @@
+# encoding: UTF-8
+
+module Vines
+  class XmppError < StandardError
+    include Nokogiri::XML
+
+    # Returns the XML element name based on the exception class name.
+    # For example, Vines::BadFormat becomes bad-format.
+    def element_name
+      name = self.class.name.split('::').last
+      name.gsub(/([A-Z])/, '-\1').downcase[1..-1]
+    end
+  end
+
+  class SaslError < XmppError
+    NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
+
+    def initialize(text=nil)
+      @text = text
+    end
+
+    def to_xml
+      doc = Document.new
+      doc.create_element('failure') do |node|
+        node.add_namespace(nil, NAMESPACE)
+        node << doc.create_element(element_name)
+        if @text
+          node << doc.create_element('text') do |text|
+            text['xml:lang'] = 'en'
+            text.content = @text
+          end
+        end
+      end.to_xml(:indent => 0).gsub(/\n/, '')
+    end
+  end
+
+  class StreamError < XmppError
+    NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-streams'.freeze
+
+    def initialize(text=nil)
+      @text = text
+    end
+
+    def to_xml
+      doc = Document.new
+      doc.create_element('stream:error') do |el|
+        el << doc.create_element(element_name, 'xmlns' => NAMESPACE)
+        if @text
+          el << doc.create_element('text', @text, 'xmlns' => NAMESPACE, 'xml:lang' => 'en')
+        end
+      end.to_xml(:indent => 0).gsub(/\n/, '')
+    end
+  end
+
+  class StanzaError < XmppError
+    TYPES = %w[auth cancel continue modify wait].freeze
+    KINDS = %w[message presence iq].freeze
+    NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-stanzas'.freeze
+
+    def initialize(el, type, text=nil)
+      raise "type must be one of: %s"   % TYPES.join(', ') unless TYPES.include?(type)
+      raise "stanza must be one of: %s" % KINDS.join(', ') unless KINDS.include?(el.name)
+      @stanza_kind, @type, @text = el.name, type, text
+      @id, @from, @to = %w[id from to].map {|a| el[a] }
+    end
+
+    def to_xml
+      doc = Document.new
+      doc.create_element(@stanza_kind) do |el|
+        el['from'] = @to   if @to
+        el['id']   = @id   if @id
+        el['to']   = @from if @from
+        el['type'] = 'error'
+        el << doc.create_element('error', 'type' => @type) do |error|
+          error << doc.create_element(element_name, 'xmlns' => NAMESPACE)
+          if @text
+            error << doc.create_element('text', @text, 'xmlns' => NAMESPACE, 'xml:lang' => 'en')
+          end
+        end
+      end.to_xml(:indent => 0).gsub(/\n/, '')
+    end
+  end
+
+  module SaslErrors
+    class Aborted < SaslError; end
+    class AccountDisabled < SaslError; end
+    class CredentialsExpired < SaslError; end
+    class EncryptionRequired < SaslError; end
+    class IncorrectEncoding < SaslError; end
+    class InvalidAuthzid < SaslError; end
+    class InvalidMechanism < SaslError; end
+    class MalformedRequest < SaslError; end
+    class MechanismTooWeak < SaslError; end
+    class NotAuthorized < SaslError; end
+    class TemporaryAuthFailure < SaslError; end
+  end
+
+  module StreamErrors
+    class BadFormat < StreamError; end
+    class BadNamespacePrefix < StreamError; end
+    class Conflict < StreamError; end
+    class ConnectionTimeout < StreamError; end
+    class HostGone < StreamError; end
+    class HostUnknown < StreamError; end
+    class ImproperAddressing < StreamError; end
+    class InternalServerError < StreamError; end
+    class InvalidFrom < StreamError; end
+    class InvalidNamespace < StreamError; end
+    class InvalidXml < StreamError; end
+    class NotAuthorized < StreamError; end
+    class NotWellFormed < StreamError; end
+    class PolicyViolation < StreamError; end
+    class RemoteConnectionFailed < StreamError; end
+    class Reset < StreamError; end
+    class ResourceConstraint < StreamError; end
+    class RestrictedXml < StreamError; end
+    class SeeOtherHost < StreamError; end
+    class SystemShutdown < StreamError; end
+    class UndefinedCondition < StreamError; end
+    class UnsupportedEncoding < StreamError; end
+    class UnsupportedFeature < StreamError; end
+    class UnsupportedStanzaType < StreamError; end
+    class UnsupportedVersion < StreamError; end
+  end
+
+  module StanzaErrors
+    class BadRequest < StanzaError; end
+    class Conflict < StanzaError; end
+    class FeatureNotImplemented < StanzaError; end
+    class Forbidden < StanzaError; end
+    class Gone < StanzaError; end
+    class InternalServerError < StanzaError; end
+    class ItemNotFound < StanzaError; end
+    class JidMalformed < StanzaError; end
+    class NotAcceptable < StanzaError; end
+    class NotAllowed < StanzaError; end
+    class NotAuthorized < StanzaError; end
+    class PolicyViolation < StanzaError; end
+    class RecipientUnavailable < StanzaError; end
+    class Redirect < StanzaError; end
+    class RegistrationRequired < StanzaError; end
+    class RemoteServerNotFound < StanzaError; end
+    class RemoteServerTimeout < StanzaError; end
+    class ResourceConstraint < StanzaError; end
+    class ServiceUnavailable < StanzaError; end
+    class SubscriptionRequired < StanzaError; end
+    class UndefinedCondition < StanzaError; end
+    class UnexpectedRequest < StanzaError; end
+  end
+end
diff --git a/lib/vines/jid.rb b/lib/vines/jid.rb
new file mode 100644
index 0000000..b84c00c
--- /dev/null
+++ b/lib/vines/jid.rb
@@ -0,0 +1,95 @@
+# encoding: UTF-8
+
+module Vines
+  class JID
+    include Comparable
+
+    PATTERN = /\A(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?\Z/.freeze
+
+    # http://tools.ietf.org/html/rfc6122#appendix-A
+    NODE_PREP = /[[:cntrl:] "&'\/:<>@]/.freeze
+
+    # http://tools.ietf.org/html/rfc3454#appendix-C
+    NAME_PREP = /[[:cntrl:] ]/.freeze
+
+    # http://tools.ietf.org/html/rfc6122#appendix-B
+    RESOURCE_PREP = /[[:cntrl:]]/.freeze
+
+    attr_reader :node, :domain, :resource
+    attr_writer :resource
+
+    def self.new(node, domain=nil, resource=nil)
+      node.is_a?(JID) ? node : super
+    end
+
+    def initialize(node, domain=nil, resource=nil)
+      @node, @domain, @resource = node, domain, resource
+
+      if @domain.nil? && @resource.nil?
+        @node, @domain, @resource = @node.to_s.scan(PATTERN).first
+      end
+      [@node, @domain].each {|part| part.downcase! if part }
+
+      validate
+    end
+
+    # Strip the resource part from this JID and return it as a new
+    # JID object. The new JID contains only the optional node part
+    # and the required domain part from the original. This JID remains
+    # unchanged.
+    def bare
+      JID.new(@node, @domain)
+    end
+
+    # Return true if this is a bare JID without a resource part.
+    def bare?
+      @resource.nil?
+    end
+
+    # Return true if this is a domain-only JID without a node or resource part.
+    def domain?
+      !empty? && to_s == @domain
+    end
+
+    # Return true if this JID is equal to the empty string ''. That is, it's
+    # missing the node, domain, and resource parts that form a valid JID. It
+    # makes for easier error handling to be able to create JID objects from
+    # strings and then check if they're empty rather than nil.
+    def empty?
+      to_s == ''
+    end
+
+    def <=>(jid)
+      self.to_s <=> jid.to_s
+    end
+
+    def eql?(jid)
+      jid.is_a?(JID) && self == jid
+    end
+
+    def hash
+      self.to_s.hash
+    end
+
+    def to_s
+      s = @domain
+      s = "#{@node}@#{s}" if @node
+      s = "#{s}/#{@resource}" if @resource
+      s
+    end
+
+    private
+
+    def validate
+      [@node, @domain, @resource].each do |part|
+        raise ArgumentError, 'jid too long' if (part || '').size > 1023
+      end
+      raise ArgumentError, 'empty node' if @node && @node.strip.empty?
+      raise ArgumentError, 'node contains invalid characters' if @node && @node =~ NODE_PREP
+      raise ArgumentError, 'empty resource' if @resource && @resource.strip.empty?
+      raise ArgumentError, 'resource contains invalid characters' if @resource && @resource =~ RESOURCE_PREP
+      raise ArgumentError, 'empty domain' if @domain == '' && (@node || @resource)
+      raise ArgumentError, 'domain contains invalid characters' if @domain && @domain =~ NAME_PREP
+    end
+  end
+end
diff --git a/lib/vines/kit.rb b/lib/vines/kit.rb
new file mode 100644
index 0000000..33b71c4
--- /dev/null
+++ b/lib/vines/kit.rb
@@ -0,0 +1,30 @@
+# encoding: UTF-8
+
+module Vines
+  # A module for utility methods with no better home.
+  module Kit
+    # Create a hex-encoded, SHA-512 HMAC of the data, using the secret key.
+    def self.hmac(key, data)
+      digest = OpenSSL::Digest.new("sha512")
+      OpenSSL::HMAC.hexdigest(digest, key, data)
+    end
+
+    # Generates a random uuid per rfc 4122 that's useful for including in
+    # stream, iq, and other xmpp stanzas.
+    def self.uuid
+      SecureRandom.uuid
+    end
+
+    # Generates a random 128 character authentication token.
+    def self.auth_token
+      SecureRandom.hex(64)
+    end
+
+    # Generate a HMAC for dialback as recommended in XEP-0185
+    def self.dialback_key(key, receiving, originating, id)
+      digest = OpenSSL::Digest.new('sha256')
+      data = "#{receiving} #{originating} #{id}"
+      OpenSSL::HMAC.hexdigest(digest, digest.hexdigest(key), data)
+    end
+  end
+end
diff --git a/lib/vines/log.rb b/lib/vines/log.rb
new file mode 100644
index 0000000..8041c29
--- /dev/null
+++ b/lib/vines/log.rb
@@ -0,0 +1,28 @@
+# encoding: UTF-8
+
+module Vines
+  module Log
+    @@logger = nil
+    def log
+      unless @@logger
+        @@logger = Logger.new(STDOUT)
+        @@logger.level = Logger::INFO
+        @@logger.progname = 'vines'
+        @@logger.formatter = Class.new(Logger::Formatter) do
+          def initialize
+            @time = "%Y-%m-%dT%H:%M:%SZ".freeze
+            @fmt  = "[%s] %5s -- %s: %s\n".freeze
+          end
+          def call(severity, time, program, msg)
+            @fmt % [time.utc.strftime(@time), severity, program, msg2str(msg)]
+          end
+        end.new
+      end
+      @@logger
+    end
+
+    def self.set_log_file(file)
+      @@logger = Logger.new(file)
+    end
+  end
+end
diff --git a/lib/vines/node.rb b/lib/vines/node.rb
new file mode 100644
index 0000000..2cc4f3d
--- /dev/null
+++ b/lib/vines/node.rb
@@ -0,0 +1,31 @@
+module Vines
+  # Utility functions to work with nodes
+  module Node
+
+    STREAM = 'stream'.freeze
+    BODY   = 'body'.freeze
+
+    module_function
+
+    # Check if node starts a new stream
+    def stream?(node)
+      node.name == STREAM && namespace(node) == NAMESPACES[:stream]
+    end
+
+    # Check if BOSH body
+    def body?(node)
+      node.name == BODY && namespace(node) == NAMESPACES[:http_bind]
+    end
+
+    # Get the namespace
+    def namespace(node)
+      namespace = node.namespace
+      namespace && namespace.href
+    end
+
+    # Convert to stanza
+    def to_stanza(node, stream)
+      Stanza.from_node(node, stream)
+    end
+  end
+end
diff --git a/lib/vines/router.rb b/lib/vines/router.rb
new file mode 100644
index 0000000..b8ab653
--- /dev/null
+++ b/lib/vines/router.rb
@@ -0,0 +1,184 @@
+# encoding: UTF-8
+
+module Vines
+  # The router tracks all stream connections to the server for all clients,
+  # servers, and components. It sends stanzas to the correct stream based on
+  # the 'to' attribute. Router is a singleton, shared by all streams, that must
+  # be accessed with +Config#router+.
+  class Router
+    EMPTY = [].freeze
+
+    STREAM_TYPES = [:client, :server, :component].freeze
+
+    def initialize(config)
+      @config = config
+      @clients, @servers, @components = {}, [], []
+      @pending = Hash.new {|h,k| h[k] = [] }
+    end
+
+    # Returns streams for all connected resources for this JID. A resource is
+    # considered connected after it has completed authentication and resource
+    # binding.
+    def connected_resources(jid, from, proxies=true)
+      jid, from = JID.new(jid), JID.new(from)
+      return [] unless @config.allowed?(jid, from)
+
+      local = @clients[jid.bare] || EMPTY
+      local = local.select {|stream| stream.user.jid == jid } unless jid.bare?
+      remote = proxies ? proxies(jid) : EMPTY
+      [local, remote].flatten
+    end
+
+    # Returns streams for all available resources for this JID. A resource is
+    # marked available after it sends initial presence.
+    def available_resources(*jids, from)
+      clients(jids, from) do |stream|
+        stream.available?
+      end
+    end
+
+    # Returns streams for all interested resources for this JID. A resource is
+    # marked interested after it requests the roster.
+    def interested_resources(*jids, from)
+      clients(jids, from) do |stream|
+        stream.interested?
+      end
+    end
+
+    # Add the connection to the routing table. The connection must return
+    # :client, :server, or :component from its +stream_type+ method so the
+    # router can properly route stanzas to the stream.
+    def <<(stream)
+      case stream_type(stream)
+      when :client then
+        return unless stream.connected?
+        jid = stream.user.jid.bare
+        @clients[jid] ||= []
+        @clients[jid] << stream
+      when :server then @servers << stream
+      when :component then @components << stream
+      end
+    end
+
+    # Remove the connection from the routing table.
+    def delete(stream)
+      case stream_type(stream)
+      when :client then
+        return unless stream.connected?
+        jid = stream.user.jid.bare
+        streams = @clients[jid] || []
+        streams.delete(stream)
+        @clients.delete(jid) if streams.empty?
+      when :server then @servers.delete(stream)
+      when :component then @components.delete(stream)
+      end
+    end
+
+    # Send the stanza to the appropriate remote server-to-server stream
+    # or an external component stream.
+    def route(stanza)
+      to, from = %w[to from].map {|attr| JID.new(stanza[attr]) }
+      return unless @config.allowed?(to, from)
+      key = [to.domain, from.domain]
+
+      if stream = connection_to(to, from)
+        stream.write(stanza)
+      elsif @pending.key?(key)
+        @pending[key] << stanza
+      elsif @config.s2s?(to.domain)
+        @pending[key] << stanza
+        Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream|
+          stream ? send_pending(key, stream) : return_pending(key)
+          @pending.delete(key)
+        end
+      else
+        raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel')
+      end
+    end
+
+    # Return stream by id
+    def stream_by_id(id)
+      (@servers+ at clients.values.flatten+@components).find {|stream| stream.id == id }
+    end
+
+    # Returns the total number of streams connected to the server.
+    def size
+      clients = @clients.values.inject(0) {|sum, arr| sum + arr.size }
+      clients + @servers.size + @components.size
+    end
+
+    private
+
+    # Write all pending stanzas for this domain to the stream. Called after a
+    # s2s stream has successfully connected and we need to dequeue all stanzas
+    # we received while waiting for the connection to finish.
+    def send_pending(key, stream)
+      @pending[key].each do |stanza|
+        stream.write(stanza)
+      end
+    end
+
+    # Return all pending stanzas to their senders as remote-server-not-found
+    # errors. Called after a s2s stream has failed to connect.
+    def return_pending(key)
+      @pending[key].each do |stanza|
+        to, from = JID.new(stanza['to']), JID.new(stanza['from'])
+        xml = StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel').to_xml
+        if @config.component?(from)
+          connection_to(from, to).write(xml) rescue nil
+        else
+          connected_resources(from, to).each {|c| c.write(xml) }
+        end
+      end
+    end
+
+    # Return the client streams to which the from address is allowed to
+    # contact. Apply the filter block to each stream to narrow the results
+    # before returning the streams.
+    def clients(jids, from, &filter)
+      jids = filter_allowed(jids, from)
+      local = @clients.values_at(*jids).compact.flatten.select(&filter)
+      proxies = proxies(*jids).select(&filter)
+      [local, proxies].flatten
+    end
+
+    # Return the bare JIDs from the list that are allowed to talk to
+    # the +from+ JID.
+    def filter_allowed(jids, from)
+      from = JID.new(from)
+      jids.flatten.map {|jid| JID.new(jid).bare }
+        .select {|jid| @config.allowed?(jid, from) }
+    end
+
+    def proxies(*jids)
+      return EMPTY unless @config.cluster?
+      @config.cluster.remote_sessions(*jids)
+    end
+
+    def connection_to(to, from)
+      component_stream(to) || server_stream(to, from)
+    end
+
+    def component_stream(to)
+      @components.select do |stream|
+        stream.ready? && stream.remote_domain == to.domain
+      end.sample
+    end
+
+    def server_stream(to, from)
+      @servers.select do |stream|
+        stream.ready? &&
+          stream.remote_domain == to.domain &&
+            stream.domain == from.domain
+      end.sample
+    end
+
+    def stream_type(connection)
+      connection.stream_type.tap do |type|
+        unless STREAM_TYPES.include?(type)
+          raise ArgumentError, "unexpected stream type: #{type}"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza.rb b/lib/vines/stanza.rb
new file mode 100644
index 0000000..bc426b8
--- /dev/null
+++ b/lib/vines/stanza.rb
@@ -0,0 +1,175 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    include Nokogiri::XML
+
+    attr_reader :stream
+
+    EMPTY = ''.freeze
+    FROM, MESSAGE, TO = %w[from message to].map {|s| s.freeze }
+    ROUTABLE_STANZAS  = %w[message iq presence].freeze
+
+    @@types = {}
+
+    def self.register(xpath, ns={})
+      @@types[[xpath, ns]] = self
+    end
+
+    def self.from_node(node, stream)
+      # optimize common case
+      return Message.new(node, stream) if node.name == MESSAGE
+      found = @@types.select {|pair, v| node.xpath(*pair).any? }
+        .sort {|a, b| b[0][0].length - a[0][0].length }.first
+      found ? found[1].new(node, stream) : nil
+    end
+
+    def initialize(node, stream)
+      @node, @stream = node, stream
+    end
+
+    # Send the stanza to all recipients, stamping it with from and
+    # to addresses first.
+    def broadcast(recipients)
+      @node[FROM] = stream.user.jid.to_s
+      recipients.each do |recipient|
+        @node[TO] = recipient.user.jid.to_s
+        recipient.write(@node)
+      end
+    end
+
+    # Returns true if this stanza should be processed locally. Returns false
+    # if it's destined for a remote domain or external component.
+    def local?
+      return true unless ROUTABLE_STANZAS.include?(@node.name)
+      to = JID.new(@node[TO])
+      to.empty? || local_jid?(to)
+    end
+
+    def local_jid?(*jids)
+      stream.config.local_jid?(*jids)
+    end
+
+    # Return true if this stanza is addressed to a pubsub subdomain hosted
+    # at this server. This helps differentiate between IQ stanzas addressed
+    # to the server and stanzas addressed to pubsub domains, both of which must
+    # be handled locally and not routed.
+    def to_pubsub_domain?
+      stream.config.pubsub?(validate_to)
+    end
+
+    def route
+      stream.router.route(@node)
+    end
+
+    def router
+      stream.router
+    end
+
+    def storage(domain=stream.domain)
+      stream.storage(domain)
+    end
+
+    def process
+      raise 'subclass must implement'
+    end
+
+    # Broadcast unavailable presence from the user's available resources to the
+    # recipient's available resources. Route the stanza to a remote server if
+    # the recipient isn't hosted locally.
+    def send_unavailable(from, to)
+      available = router.available_resources(from, to)
+      stanzas = available.map {|stream| unavailable(stream.user.jid) }
+      broadcast_to_available_resources(stanzas, to)
+    end
+
+    # Return an unavailable presence stanza addressed from the given JID.
+    def unavailable(from)
+      doc = Document.new
+      doc.create_element('presence',
+        'from' => from.to_s,
+        'id'   => Kit.uuid,
+        'type' => 'unavailable')
+    end
+
+    # Return nil if this stanza has no 'to' attribute. Return a Vines::JID
+    # if it contains a valid 'to' attribute.  Raise a JidMalformed error if
+    # the JID is invalid.
+    def validate_to
+      validate_address(TO)
+    end
+
+    # Return nil if this stanza has no 'from' attribute. Return a Vines::JID
+    # if it contains a valid 'from' attribute.  Raise a JidMalformed error if
+    # the JID is invalid.
+    def validate_from
+      validate_address(FROM)
+    end
+
+    def method_missing(method, *args, &block)
+      @node.send(method, *args, &block)
+    end
+
+    private
+
+    # Send the stanzas to the destination JID, routing to a s2s stream
+    # if the address is remote. This method properly stamps the to address
+    # on each stanza before it's sent. The caller must set the from address.
+    def broadcast_to_available_resources(stanzas, to)
+      return if send_to_remote(stanzas, to)
+      send_to_recipients(stanzas, stream.available_resources(to))
+    end
+
+    # Send the stanzas to the destination JID, routing to a s2s stream
+    # if the address is remote. This method properly stamps the to address
+    # on each stanza before it's sent. The caller must set the from address.
+    def broadcast_to_interested_resources(stanzas, to)
+      return if send_to_remote(stanzas, to)
+      send_to_recipients(stanzas, stream.interested_resources(to))
+    end
+
+    # Route the stanzas to a remote server, stamping a bare JID as the
+    # to address. Bare JIDs are required for presence subscription stanzas
+    # sent to the remote contact's server. Return true if the stanzas were
+    # routed, false if they must be delivered locally.
+    def send_to_remote(stanzas, to)
+      return false if local_jid?(to)
+      to = JID.new(to)
+      stanzas.each do |el|
+        el[TO] = to.bare.to_s
+        router.route(el)
+      end
+      true
+    end
+
+    # Send the stanzas to the local recipient streams, stamping a full JID as
+    # the to address. It's important to use full JIDs, even when sending to
+    # local clients, because the stanzas may be routed to other cluster nodes
+    # for delivery. We need the receiving cluster node to send the stanza just
+    # to this full JID, not to lookup all JIDs for this user.
+    def send_to_recipients(stanzas, recipients)
+      recipients.each do |recipient|
+        stanzas.each do |el|
+          el[TO] = recipient.user.jid.to_s
+          recipient.write(el)
+        end
+      end
+    end
+
+    # Return true if the to and from JIDs are allowed to communicate with one
+    # another based on the cross_domain_messages setting in conf/config.rb. If
+    # a domain's users are isolated to sending messages only within their own
+    # domain, pubsub stanzas must not be processed from remote JIDs.
+    def allowed?
+      stream.config.allowed?(validate_to || stream.domain, stream.user.jid)
+    end
+
+    def validate_address(attr)
+      jid = (self[attr] || EMPTY)
+      return if jid.empty?
+      JID.new(jid)
+    rescue
+      raise StanzaErrors::JidMalformed.new(self, 'modify')
+    end
+  end
+end
diff --git a/lib/vines/stanza/dialback.rb b/lib/vines/stanza/dialback.rb
new file mode 100644
index 0000000..ff51ef9
--- /dev/null
+++ b/lib/vines/stanza/dialback.rb
@@ -0,0 +1,28 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Dialback < Stanza
+      VALID_TYPE, INVALID_TYPE = %w[valid invalid].map {|t| t.freeze }
+      NS = NAMESPACES[:legacy_dialback]
+
+      register "/db:verify", 'db' => NS
+
+      def process
+        id, from, to = %w[id from to].map {|a| @node[a] }
+        key = @node.text
+
+        outbound_stream = router.stream_by_id(id)
+        unless outbound_stream && outbound_stream.state.is_a?(Stream::Server::Outbound::AuthDialbackResult)
+          @stream.write(%Q{<db:verify from="#{to}" to="#{from}" id="#{id}" type="error"><error type="cancel"><item-not-found xmlns="#{NAMESPACES[:stanzas]}"/></error></db:verify>})
+          return
+        end
+
+        secret = outbound_stream.state.dialback_secret
+        type = Kit.dialback_key(secret, from, to, id) == key ? VALID_TYPE : INVALID_TYPE
+        @stream.write(%Q{<db:verify from="#{to}" to="#{from}" id="#{id}" type="#{type}"/>})
+        @stream.close_connection_after_writing
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq.rb b/lib/vines/stanza/iq.rb
new file mode 100644
index 0000000..3e4e21d
--- /dev/null
+++ b/lib/vines/stanza/iq.rb
@@ -0,0 +1,48 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq < Stanza
+      register "/iq"
+
+      VALID_TYPES = %w[get set result error].freeze
+
+      VALID_TYPES.each do |type|
+        define_method "#{type}?" do
+          self['type'] == type
+        end
+      end
+
+      def process
+        if self['id'] && VALID_TYPES.include?(self['type'])
+          route_iq or raise StanzaErrors::FeatureNotImplemented.new(@node, 'cancel')
+        else
+          raise StanzaErrors::BadRequest.new(@node, 'modify')
+        end
+      end
+
+      def to_result
+        doc = Document.new
+        doc.create_element('iq',
+          'from' => validate_to || stream.domain,
+          'id'   => self['id'],
+          'to'   => stream.user.jid,
+          'type' => 'result')
+      end
+
+      private
+
+      # Return false if this IQ stanza is addressed to the server, or a pubsub
+      # service hosted here, and must be handled locally. Return true if the
+      # stanza must not be handled locally and has been routed to the appropriate
+      # component, s2s, or c2s stream.
+      def route_iq
+        to = validate_to
+        return false if to.nil? || stream.config.vhost?(to) || to_pubsub_domain?
+        self['from'] = stream.user.jid.to_s
+        local? ? broadcast(stream.connected_resources(to)) : route
+        true
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/auth.rb b/lib/vines/stanza/iq/auth.rb
new file mode 100644
index 0000000..d854bc1
--- /dev/null
+++ b/lib/vines/stanza/iq/auth.rb
@@ -0,0 +1,18 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Auth < Query
+        register "/iq[@id and @type='get']/ns:query", 'ns' => NAMESPACES[:non_sasl]
+
+        def process
+          # XEP-0078 says we MUST send a service-unavailable error
+          # here, but Adium 1.4.1 won't login if we do that, so just
+          # swallow this stanza.
+          # raise StanzaErrors::ServiceUnavailable.new(@node, 'cancel')
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/disco_info.rb b/lib/vines/stanza/iq/disco_info.rb
new file mode 100644
index 0000000..0073df6
--- /dev/null
+++ b/lib/vines/stanza/iq/disco_info.rb
@@ -0,0 +1,45 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class DiscoInfo < Query
+        NS = NAMESPACES[:disco_info]
+
+        register "/iq[@id and @type='get']/ns:query", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          result = to_result.tap do |el|
+            el << el.document.create_element('query') do |query|
+              query.default_namespace = NS
+              if to_pubsub_domain?
+                identity(query, 'pubsub', 'service')
+                pubsub = [:pubsub_create, :pubsub_delete, :pubsub_instant, :pubsub_item_ids, :pubsub_publish, :pubsub_subscribe]
+                features(query, :disco_info, :ping, :pubsub, *pubsub)
+              else
+                identity(query, 'server', 'im')
+                features = [:disco_info, :disco_items, :offline, :ping, :vcard, :version]
+                features << :storage if stream.config.private_storage?(validate_to || stream.domain)
+                features(query, features)
+              end
+            end
+          end
+          stream.write(result)
+        end
+
+        private
+
+        def identity(query, category, type)
+          query << query.document.create_element('identity', 'category' => category, 'type' => type)
+        end
+
+        def features(query, *features)
+          features.flatten.each do |feature|
+            query << query.document.create_element('feature', 'var' => NAMESPACES[feature])
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/disco_items.rb b/lib/vines/stanza/iq/disco_items.rb
new file mode 100644
index 0000000..8d95615
--- /dev/null
+++ b/lib/vines/stanza/iq/disco_items.rb
@@ -0,0 +1,29 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class DiscoItems < Query
+        NS = NAMESPACES[:disco_items]
+
+        register "/iq[@id and @type='get']/ns:query", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          result = to_result.tap do |el|
+            el << el.document.create_element('query') do |query|
+              query.default_namespace = NS
+              unless to_pubsub_domain?
+                to = (validate_to || stream.domain).to_s
+                stream.config.vhost(to).disco_items.each do |domain|
+                  query << el.document.create_element('item', 'jid' => domain)
+                end
+              end
+            end
+          end
+          stream.write(result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/error.rb b/lib/vines/stanza/iq/error.rb
new file mode 100644
index 0000000..8530c5c
--- /dev/null
+++ b/lib/vines/stanza/iq/error.rb
@@ -0,0 +1,16 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Error < Iq
+        register "/iq[@id and @type='error']"
+
+        def process
+          return if route_iq
+          # do nothing
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/ping.rb b/lib/vines/stanza/iq/ping.rb
new file mode 100644
index 0000000..6584617
--- /dev/null
+++ b/lib/vines/stanza/iq/ping.rb
@@ -0,0 +1,16 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Ping < Iq
+        register "/iq[@id and @type='get']/ns:ping", 'ns' => NAMESPACES[:ping]
+
+        def process
+          return if route_iq || !allowed?
+          stream.write(to_result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/private_storage.rb b/lib/vines/stanza/iq/private_storage.rb
new file mode 100644
index 0000000..6a2aadb
--- /dev/null
+++ b/lib/vines/stanza/iq/private_storage.rb
@@ -0,0 +1,83 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      # Implements the Private Storage feature defined in XEP-0049. Clients are
+      # allowed to save arbitrary XML documents on the server, identified by
+      # element name and namespace.
+      class PrivateStorage < Query
+        NS = NAMESPACES[:storage]
+
+        register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
+
+        def process
+          validate_to_address
+          validate_storage_enabled
+          validate_children_size
+          validate_namespaces
+          get? ? retrieve_fragment : update_fragment
+        end
+
+        private
+
+        def retrieve_fragment
+          found = storage.find_fragment(stream.user.jid, elements.first.elements.first)
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless found
+
+          result = to_result do |node|
+            node << node.document.create_element('query') do |query|
+              query.default_namespace = NS
+              query << found
+            end
+          end
+          stream.write(result)
+        end
+
+        def update_fragment
+          elements.first.elements.each do |node|
+            storage.save_fragment(stream.user.jid, node)
+          end
+          stream.write(to_result)
+        end
+
+        private
+
+        def to_result
+          super.tap do |node|
+            node['from'] = stream.user.jid.to_s
+            yield node if block_given?
+          end
+        end
+
+        def validate_children_size
+          size = elements.first.elements.size
+          if (get? && size != 1) || (set? && size == 0)
+            raise StanzaErrors::NotAcceptable.new(self, 'modify')
+          end
+        end
+
+        def validate_to_address
+          to = validate_to
+          unless to.nil? || to == stream.user.jid.bare
+            raise StanzaErrors::Forbidden.new(self, 'cancel')
+          end
+        end
+
+        def validate_storage_enabled
+          unless stream.config.private_storage?(stream.domain)
+            raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
+          end
+        end
+
+        def validate_namespaces
+          elements.first.elements.each do |node|
+            if node.namespace.nil? || NAMESPACES.values.include?(node.namespace.href)
+              raise StanzaErrors::NotAcceptable.new(self, 'modify')
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/query.rb b/lib/vines/stanza/iq/query.rb
new file mode 100644
index 0000000..d741129
--- /dev/null
+++ b/lib/vines/stanza/iq/query.rb
@@ -0,0 +1,10 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Query < Iq
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/result.rb b/lib/vines/stanza/iq/result.rb
new file mode 100644
index 0000000..341c594
--- /dev/null
+++ b/lib/vines/stanza/iq/result.rb
@@ -0,0 +1,16 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Result < Iq
+        register "/iq[@id and @type='result']"
+
+        def process
+          return if route_iq
+          # do nothing
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/roster.rb b/lib/vines/stanza/iq/roster.rb
new file mode 100644
index 0000000..21eae66
--- /dev/null
+++ b/lib/vines/stanza/iq/roster.rb
@@ -0,0 +1,140 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Roster < Query
+        NS = NAMESPACES[:roster]
+
+        register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
+
+        def process
+          validate_to_address
+          get? ? roster_query : update_roster
+        end
+
+        private
+
+        # Send an iq result stanza containing roster items to the user in
+        # response to their roster get request. Requesting the roster makes
+        # this stream an "interested resource" that can now receive roster
+        # updates.
+        def roster_query
+          stream.requested_roster!
+          stream.write(stream.user.to_roster_xml(self['id']))
+        end
+
+        # Roster sets must have no 'to' address or be addressed to the same
+        # JID that sent the stanza. RFC 6121 sections 2.1.5 and 2.3.3.
+        def validate_to_address
+          to = validate_to
+          unless to.nil? || to.bare == stream.user.jid.bare
+            raise StanzaErrors::Forbidden.new(self, 'auth')
+          end
+        end
+
+        # Add, update, or delete the roster item contained in the iq set
+        # stanza received from the client. RFC 6121 sections 2.3, 2.4, 2.5.
+        def update_roster
+          items = self.xpath('ns:query/ns:item', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if items.size != 1
+          item = items.first
+
+          jid = JID.new(item['jid']) rescue (raise StanzaErrors::JidMalformed.new(self, 'modify'))
+          raise StanzaErrors::BadRequest.new(self, 'modify') if jid.empty? || !jid.bare?
+
+          if item['subscription'] == 'remove'
+            remove_contact(jid)
+            return
+          end
+
+          raise StanzaErrors::NotAllowed.new(self, 'modify') if jid == stream.user.jid.bare
+          groups = item.xpath('ns:group', 'ns' => NS).map {|g| g.text.strip }
+          raise StanzaErrors::BadRequest.new(self, 'modify') if groups.uniq!
+          raise StanzaErrors::NotAcceptable.new(self, 'modify') if groups.include?('')
+
+          contact = stream.user.contact(jid)
+          unless contact
+            contact = Contact.new(jid: jid)
+            stream.user.roster << contact
+          end
+          contact.name = item['name']
+          contact.groups = groups
+          storage.save_user(stream.user)
+          stream.update_user_streams(stream.user)
+          send_result_iq
+          push_roster_updates(stream.user.jid, contact)
+        end
+
+        # Remove the contact with this JID from the user's roster and send
+        # roster pushes to the user's interested resources. This is triggered
+        # by receiving an iq set with an item element like
+        # <item jid="alice at wonderland.lit" subscription="remove"/>. RFC 6121
+        # section 2.5.
+        def remove_contact(jid)
+          contact = stream.user.contact(jid)
+          raise StanzaErrors::ItemNotFound.new(self, 'modify') unless contact
+          if local_jid?(contact.jid)
+            user = storage(contact.jid.domain).find_user(contact.jid)
+          end
+
+          if user && user.contact(stream.user.jid)
+            user.contact(stream.user.jid).subscription = 'none'
+            user.contact(stream.user.jid).ask = nil
+          end
+          stream.user.remove_contact(contact.jid)
+          [user, stream.user].compact.each do |save|
+            storage(save.jid.domain).save_user(save)
+            stream.update_user_streams(save)
+          end
+
+          send_result_iq
+          push_roster_updates(stream.user.jid,
+            Contact.new(jid: contact.jid, subscription: 'remove'))
+
+          if local_jid?(contact.jid)
+            send_unavailable(stream.user.jid, contact.jid) if contact.subscribed_from?
+            send_unsubscribe(contact)
+            if user && user.contact(stream.user.jid)
+              push_roster_updates(contact.jid, user.contact(stream.user.jid))
+            end
+          else
+            send_unsubscribe(contact)
+          end
+        end
+
+        # Notify the contact that it's been removed from the user's roster
+        # and no longer has any presence relationship with the user.
+        def send_unsubscribe(contact)
+          presence = [%w[to unsubscribe], %w[from unsubscribed]].map do |meth, type|
+            presence(contact.jid, type) if contact.send("subscribed_#{meth}?")
+          end.compact
+          broadcast_to_interested_resources(presence, contact.jid)
+        end
+
+        def presence(to, type)
+          doc = Document.new
+          doc.create_element('presence',
+            'from' => stream.user.jid.bare.to_s,
+            'id'   => Kit.uuid,
+            'to'   => to.to_s,
+            'type' => type)
+        end
+
+        # Send an iq set stanza to the user's interested resources, letting them
+        # know their roster has been updated.
+        def push_roster_updates(to, contact)
+          stream.interested_resources(to).each do |recipient|
+            contact.send_roster_push(recipient)
+          end
+        end
+
+        def send_result_iq
+          node = to_result
+          node.remove_attribute('from')
+          stream.write(node)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/session.rb b/lib/vines/stanza/iq/session.rb
new file mode 100644
index 0000000..b51fc04
--- /dev/null
+++ b/lib/vines/stanza/iq/session.rb
@@ -0,0 +1,17 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      # Session support is deprecated, but Adium requires it, so reply with an
+      # iq result stanza.
+      class Session < Iq
+        register "/iq[@id and @type='set']/ns:session", 'ns' => NAMESPACES[:session]
+
+        def process
+          stream.write(to_result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/vcard.rb b/lib/vines/stanza/iq/vcard.rb
new file mode 100644
index 0000000..11c558c
--- /dev/null
+++ b/lib/vines/stanza/iq/vcard.rb
@@ -0,0 +1,56 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Vcard < Iq
+        NS = NAMESPACES[:vcard]
+
+        register "/iq[@id and @type='get' or @type='set']/ns:vCard", 'ns' => NS
+
+        def process
+          return unless allowed?
+          if local?
+            get? ? vcard_query : vcard_update
+          else
+            self['from'] = stream.user.jid.to_s
+            route
+          end
+        end
+
+        private
+
+        def vcard_query
+          to = validate_to
+          jid = to ? to.bare : stream.user.jid.bare
+          card = storage(jid.domain).find_vcard(jid)
+
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless card
+
+          doc = Document.new
+          result = doc.create_element('iq') do |node|
+            node['from'] = jid.to_s unless jid == stream.user.jid.bare
+            node['id']   = self['id']
+            node['to']   = stream.user.jid.to_s
+            node['type'] = 'result'
+            node << card
+          end
+          stream.write(result)
+        end
+
+        def vcard_update
+          to = validate_to
+          unless to.nil? || to == stream.user.jid.bare
+            raise StanzaErrors::Forbidden.new(self, 'auth')
+          end
+
+          storage.save_vcard(stream.user.jid, elements.first)
+
+          result = to_result
+          result.remove_attribute('from')
+          stream.write(result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/iq/version.rb b/lib/vines/stanza/iq/version.rb
new file mode 100644
index 0000000..1da59f9
--- /dev/null
+++ b/lib/vines/stanza/iq/version.rb
@@ -0,0 +1,25 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Iq
+      class Version < Query
+        NS = NAMESPACES[:version]
+
+        register "/iq[@id and @type='get']/ns:query", 'ns' => NS
+
+        def process
+          return if route_iq || to_pubsub_domain? || !allowed?
+          result = to_result.tap do |node|
+            node << node.document.create_element('query') do |query|
+              query.default_namespace = NS
+              query << node.document.create_element('name', 'Vines')
+              query << node.document.create_element('version', VERSION)
+            end
+          end
+          stream.write(result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/message.rb b/lib/vines/stanza/message.rb
new file mode 100644
index 0000000..97dced9
--- /dev/null
+++ b/lib/vines/stanza/message.rb
@@ -0,0 +1,43 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Message < Stanza
+      register "/message"
+
+      TYPE, FROM  = %w[type from].map {|s| s.freeze }
+      VALID_TYPES = %w[chat error groupchat headline normal].freeze
+
+      VALID_TYPES.each do |type|
+        define_method "#{type}?" do
+          self[TYPE] == type
+        end
+      end
+
+      def process
+        unless self[TYPE].nil? || VALID_TYPES.include?(self[TYPE])
+          raise StanzaErrors::BadRequest.new(self, 'modify')
+        end
+
+        if local?
+          to = validate_to || stream.user.jid.bare
+          recipients = stream.connected_resources(to)
+          if recipients.empty?
+            if user = storage(to.domain).find_user(to)
+              if Config.instance.max_offline_msgs > 0 && self[TYPE].match(/(chat|normal)/i)
+                storage(to.domain).save_message(stream.user.jid.bare.to_s, to.to_s, @node.text)
+              else
+                raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
+              end
+            end
+          else
+            broadcast(recipients)
+          end
+        else
+          self[FROM] = stream.user.jid.to_s
+          route
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence.rb b/lib/vines/stanza/presence.rb
new file mode 100644
index 0000000..db68f10
--- /dev/null
+++ b/lib/vines/stanza/presence.rb
@@ -0,0 +1,182 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence < Stanza
+      register "/presence"
+
+      VALID_TYPES = %w[subscribe subscribed unsubscribe unsubscribed unavailable probe error].freeze
+
+      VALID_TYPES.each do |type|
+        define_method "#{type}?" do
+          self['type'] == type
+        end
+      end
+
+      def process
+        stream.last_broadcast_presence = @node.clone unless validate_to
+        unless self['type'].nil?
+          raise StanzaErrors::BadRequest.new(self, 'modify')
+        end
+        if Config.instance.max_offline_msgs > 0 && !validate_to
+          check_offline_messages(stream.last_broadcast_presence)
+        end
+        dir = outbound? ? 'outbound' : 'inbound'
+        method("#{dir}_broadcast_presence").call
+      end
+
+      def check_offline_messages(presence)
+        priority = presence.xpath("//priority").text.to_i rescue nil
+        if priority != nil && priority >= 0
+          jid = stream.user.jid.to_s
+          storage.find_messages(jid).each do |id, m|
+            stamp = Time.parse(m[:created_at].to_s)
+            doc = Nokogiri::XML::Builder.new
+            doc.message(:type => "chat", :from => m[:from], :to => m[:to]) do |msg|
+              msg.send(:"body", m[:message])
+              msg.send(:"delay", "Offline Storage",
+                       :xmlns => NAMESPACES[:delay],
+                       :from => m[:from],
+                       :stamp => stamp.iso8601)
+            end
+            xml = doc.to_xml :save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
+            stream.write(xml)
+            # after delivering it we should
+            # delete the message from database
+            storage.destroy_message(id)
+          end
+        end
+      end
+
+      def outbound?
+        !inbound?
+      end
+
+      def inbound?
+        stream.class == Vines::Stream::Server ||
+        stream.class == Vines::Stream::Component
+      end
+
+      def outbound_broadcast_presence
+        self['from'] = stream.user.jid.to_s
+        to = validate_to
+        type = (self['type'] || '').strip
+        initial = to.nil? && type.empty? && !stream.available?
+
+        recipients = if to.nil?
+          stream.available_subscribers
+        else
+          stream.user.subscribed_from?(to) ? stream.available_resources(to) : []
+        end
+
+        # NOTE overriding vCard information is not concurring
+        # with XEP-153 due the fact that the user can only update
+        # his vCard via the Diaspora environment we should act
+        # the same way for the avatar update
+        override_vcard_update
+
+        broadcast(recipients)
+        broadcast(stream.available_resources(stream.user.jid))
+
+        if initial
+          stream.available_subscribed_to_resources.each do |recipient|
+            if recipient.last_broadcast_presence
+              el = recipient.last_broadcast_presence.clone
+              el['to'] = stream.user.jid.to_s
+              el['from'] = recipient.user.jid.to_s
+              stream.write(el)
+            end
+          end
+          stream.remote_subscribed_to_contacts.each do |contact|
+            send_probe(contact.jid.bare)
+          end
+          stream.available!
+        end
+
+        stream.remote_subscribers(to).each do |contact|
+          node = @node.clone
+          node['to'] = contact.jid.bare.to_s
+          router.route(node) rescue nil # ignore RemoteServerNotFound
+        end
+      end
+
+      def inbound_broadcast_presence
+        broadcast(stream.available_resources(validate_to))
+      end
+
+      private
+
+      def send_probe(to)
+        to = JID.new(to)
+        doc = Document.new
+        probe = doc.create_element('presence',
+          'from' => stream.user.jid.bare.to_s,
+          'id'   => Kit.uuid,
+          'to'   => to.bare.to_s,
+          'type' => 'probe')
+        router.route(probe) rescue nil # ignore RemoteServerNotFound
+      end
+
+      def auto_reply_to_subscription_request(from, type)
+        doc = Document.new
+        node = doc.create_element('presence') do |el|
+          el['from'] = from.to_s
+          el['id'] = self['id'] if self['id']
+          el['to'] = stream.user.jid.bare.to_s
+          el['type'] = type
+        end
+        stream.write(node)
+      end
+
+      # Send the contact's roster item to the current user's interested streams.
+      # Roster pushes are required, following presence subscription updates, to
+      # notify the user's clients of the contact's current state.
+      def send_roster_push(to)
+        contact = stream.user.contact(to)
+        stream.interested_resources(stream.user.jid).each do |recipient|
+          contact.send_roster_push(recipient)
+        end
+      end
+
+      # Notify the current user's interested streams of a contact's subscription
+      # state change as a result of receiving a subscribed, unsubscribe, or
+      # unsubscribed presence stanza.
+      def broadcast_subscription_change(contact)
+        stamp_from
+        stream.interested_resources(stamp_to).each do |recipient|
+          @node['to'] = recipient.user.jid.to_s
+          recipient.write(@node)
+          contact.send_roster_push(recipient)
+        end
+      end
+
+      # Validate that the incoming stanza has a 'to' attribute and strip any
+      # resource part from it so it's a bare jid. Return the bare JID object
+      # that was stamped.
+      def stamp_to
+        to = validate_to
+        raise StanzaErrors::BadRequest.new(self, 'modify') unless to
+        to.bare.tap do |bare|
+          self['to'] = bare.to_s
+        end
+      end
+
+      # Presence subscription stanzas must be addressed from the user's bare
+      # JID. Return the user's bare JID object that was stamped.
+      def stamp_from
+        stream.user.jid.bare.tap do |bare|
+          self['from'] = bare.to_s
+        end
+      end
+
+      def override_vcard_update
+        image_path = storage.find_avatar_by_jid(@node['from'])
+        return if image_path.nil?
+        photo_tag = "<photo><EXTVAL>#{image_path}</EXTVAL></photo>"
+        node = @node.xpath("//xmlns:x", 'xmlns' => NAMESPACES[:vcard_update]).first
+        node.remove unless node.blank?
+        @node << "<x xmlns=\"#{NAMESPACES[:vcard_update]}\">#{photo_tag}</x>"
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/error.rb b/lib/vines/stanza/presence/error.rb
new file mode 100644
index 0000000..7df6d64
--- /dev/null
+++ b/lib/vines/stanza/presence/error.rb
@@ -0,0 +1,23 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Error < Presence
+        register "/presence[@type='error']"
+
+        def process
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          # FIXME Implement error handling
+        end
+
+        def process_inbound
+          # FIXME Implement error handling
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/probe.rb b/lib/vines/stanza/presence/probe.rb
new file mode 100644
index 0000000..ea92d48
--- /dev/null
+++ b/lib/vines/stanza/presence/probe.rb
@@ -0,0 +1,37 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Probe < Presence
+        register "/presence[@type='probe']"
+
+        def process
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          self['from'] = stream.user.jid.to_s
+          local? ? process_inbound : route
+        end
+
+        def process_inbound
+          to = validate_to
+          raise StanzaErrors::BadRequest.new(self, 'modify') unless to
+
+          user = storage(to.domain).find_user(to)
+          unless user && user.subscribed_from?(stream.user.jid)
+            auto_reply_to_subscription_request(to.bare, 'unsubscribed')
+          else
+            stream.available_resources(to).each do |recipient|
+              el = recipient.last_broadcast_presence.clone
+              el['from'] = recipient.user.jid.to_s
+              el['to'] = stream.user.jid.to_s
+              stream.write(el)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/subscribe.rb b/lib/vines/stanza/presence/subscribe.rb
new file mode 100644
index 0000000..524aab5
--- /dev/null
+++ b/lib/vines/stanza/presence/subscribe.rb
@@ -0,0 +1,42 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Subscribe < Presence
+        register "/presence[@type='subscribe']"
+
+        def process
+          stamp_from
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          to = stamp_to
+          stream.user.request_subscription(to)
+          storage.save_user(stream.user)
+          stream.update_user_streams(stream.user)
+          local? ? process_inbound : route
+          send_roster_push(to)
+        end
+
+        def process_inbound
+          to = stamp_to
+          contact = storage(to.domain).find_user(to)
+          if contact.nil?
+            auto_reply_to_subscription_request(to, 'unsubscribed')
+          elsif contact.subscribed_from?(stream.user.jid)
+            auto_reply_to_subscription_request(to, 'subscribed')
+          else
+            recipients = stream.available_resources(to)
+            if recipients.empty?
+              # TODO store subscription request per RFC 6121 3.1.3 #4
+            else
+              broadcast_to_available_resources([@node], to)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/subscribed.rb b/lib/vines/stanza/presence/subscribed.rb
new file mode 100644
index 0000000..bc9768d
--- /dev/null
+++ b/lib/vines/stanza/presence/subscribed.rb
@@ -0,0 +1,51 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Subscribed < Presence
+        register "/presence[@type='subscribed']"
+
+        def process
+          stamp_from
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          to = stamp_to
+          stream.user.add_subscription_from(to)
+          storage.save_user(stream.user)
+          stream.update_user_streams(stream.user)
+          local? ? process_inbound : route
+          send_roster_push(to)
+          send_known_presence(to)
+        end
+
+        def process_inbound
+          to = stamp_to
+          user = storage(to.domain).find_user(to)
+          contact = user.contact(stream.user.jid) if user
+          return unless contact && contact.can_subscribe?
+          contact.subscribe_to
+          storage(to.domain).save_user(user)
+          stream.update_user_streams(user)
+          broadcast_subscription_change(contact)
+        end
+
+        private
+
+        # After approving a contact's subscription to this user's presence,
+        # broadcast this user's most recent presence stanzas to the contact.
+        def send_known_presence(to)
+          stanzas = stream.available_resources(stream.user.jid).map do |stream|
+            stream.last_broadcast_presence.clone.tap do |node|
+              node['from'] = stream.user.jid.to_s
+              node['id'] = Kit.uuid
+            end
+          end
+          broadcast_to_available_resources(stanzas, to)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/unavailable.rb b/lib/vines/stanza/presence/unavailable.rb
new file mode 100644
index 0000000..c0b119a
--- /dev/null
+++ b/lib/vines/stanza/presence/unavailable.rb
@@ -0,0 +1,15 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Unavailable < Presence
+        register "/presence[@type='unavailable']"
+
+        def process
+          inbound? ? inbound_broadcast_presence : outbound_broadcast_presence
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/unsubscribe.rb b/lib/vines/stanza/presence/unsubscribe.rb
new file mode 100644
index 0000000..b06d3f6
--- /dev/null
+++ b/lib/vines/stanza/presence/unsubscribe.rb
@@ -0,0 +1,38 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Unsubscribe < Presence
+        register "/presence[@type='unsubscribe']"
+
+        def process
+          stamp_from
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          to = stamp_to
+          return unless stream.user.subscribed_to?(to)
+          stream.user.remove_subscription_to(to)
+          storage.save_user(stream.user)
+          stream.update_user_streams(stream.user)
+          local? ? process_inbound : route
+          send_roster_push(to)
+        end
+
+        def process_inbound
+          to = stamp_to
+          user = storage(to.domain).find_user(to)
+          return unless user && user.subscribed_from?(stream.user.jid)
+          contact = user.contact(stream.user.jid)
+          contact.unsubscribe_from
+          storage(to.domain).save_user(user)
+          stream.update_user_streams(user)
+          broadcast_subscription_change(contact)
+          send_unavailable(to, stream.user.jid.bare)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/presence/unsubscribed.rb b/lib/vines/stanza/presence/unsubscribed.rb
new file mode 100644
index 0000000..94be9a9
--- /dev/null
+++ b/lib/vines/stanza/presence/unsubscribed.rb
@@ -0,0 +1,38 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class Presence
+      class Unsubscribed < Presence
+        register "/presence[@type='unsubscribed']"
+
+        def process
+          stamp_from
+          inbound? ? process_inbound : process_outbound
+        end
+
+        def process_outbound
+          to = stamp_to
+          return unless stream.user.subscribed_from?(to)
+          send_unavailable(stream.user.jid, to)
+          stream.user.remove_subscription_from(to)
+          storage.save_user(stream.user)
+          stream.update_user_streams(stream.user)
+          local? ? process_inbound : route
+          send_roster_push(to)
+        end
+
+        def process_inbound
+          to = stamp_to
+          user = storage(to.domain).find_user(to)
+          return unless user && user.subscribed_to?(stream.user.jid)
+          contact = user.contact(stream.user.jid)
+          contact.unsubscribe_to
+          storage(to.domain).save_user(user)
+          stream.update_user_streams(user)
+          broadcast_subscription_change(contact)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub.rb b/lib/vines/stanza/pubsub.rb
new file mode 100644
index 0000000..b9ff731
--- /dev/null
+++ b/lib/vines/stanza/pubsub.rb
@@ -0,0 +1,22 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub < Iq
+
+      private
+
+      # Return the Config::PubSub system for the domain to which this stanza is
+      # addressed or nil if it's not to a pubsub subdomain.
+      def pubsub
+        stream.config.pubsub(validate_to)
+      end
+
+      # Raise feature-not-implemented if this stanza is addressed to the chat
+      # server itself, rather than a pubsub subdomain.
+      def validate_to_address
+        raise StanzaErrors::FeatureNotImplemented.new(self, 'cancel') unless pubsub
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub/create.rb b/lib/vines/stanza/pubsub/create.rb
new file mode 100644
index 0000000..2e43ea1
--- /dev/null
+++ b/lib/vines/stanza/pubsub/create.rb
@@ -0,0 +1,39 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub
+      class Create < PubSub
+        NS = NAMESPACES[:pubsub]
+
+        register "/iq[@id and @type='set']/ns:pubsub/ns:create", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          validate_to_address
+
+          node = self.xpath('ns:pubsub/ns:create', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
+          node = node.first
+
+          id = (node['node'] || '').strip
+          id = Kit.uuid if id.empty?
+          raise StanzaErrors::Conflict.new(self, 'cancel') if pubsub.node?(id)
+          pubsub.add_node(id)
+          send_result_iq(id)
+        end
+
+        private
+
+        def send_result_iq(id)
+          el = to_result
+          el << el.document.create_element('pubsub') do |node|
+            node.default_namespace = NS
+            node << el.document.create_element('create', 'node' => id)
+          end
+          stream.write(el)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub/delete.rb b/lib/vines/stanza/pubsub/delete.rb
new file mode 100644
index 0000000..9980b15
--- /dev/null
+++ b/lib/vines/stanza/pubsub/delete.rb
@@ -0,0 +1,41 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub
+      class Delete < PubSub
+        NS = NAMESPACES[:pubsub]
+
+        register "/iq[@id and @type='set']/ns:pubsub/ns:delete", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          validate_to_address
+
+          node = self.xpath('ns:pubsub/ns:delete', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
+          node = node.first
+
+          id = node['node']
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
+
+          pubsub.publish(id, message(id))
+          pubsub.delete_node(id)
+          stream.write(to_result)
+        end
+
+        private
+
+        def message(id)
+          doc = Document.new
+          doc.create_element('message') do |node|
+            node << node.document.create_element('event') do |event|
+              event.default_namespace = NAMESPACES[:pubsub_event]
+              event << node.document.create_element('delete', 'node' => id)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub/publish.rb b/lib/vines/stanza/pubsub/publish.rb
new file mode 100644
index 0000000..be97339
--- /dev/null
+++ b/lib/vines/stanza/pubsub/publish.rb
@@ -0,0 +1,66 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub
+      class Publish < PubSub
+        NS = NAMESPACES[:pubsub]
+
+        register "/iq[@id and @type='set']/ns:pubsub/ns:publish", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          validate_to_address
+
+          node = self.xpath('ns:pubsub/ns:publish', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
+          node = node.first
+          id = node['node']
+
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
+
+          item = node.xpath('ns:item', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') unless item.size == 1
+          item = item.first
+          unless item['id']
+            item['id'] = Kit.uuid
+            include_item = true
+          end
+
+          raise StanzaErrors::BadRequest.new(self, 'modify') unless item.elements.size == 1
+          pubsub.publish(id, message(id, item))
+          send_result_iq(id, include_item ? item : nil)
+        end
+
+        private
+
+        def message(node, item)
+          doc = Document.new
+          doc.create_element('message') do |message|
+            message << doc.create_element('event') do |event|
+              event.default_namespace = NAMESPACES[:pubsub_event]
+              event << doc.create_element('items', 'node' => node) do |items|
+                items << doc.create_element('item', 'id' => item['id'], 'publisher' => stream.user.jid.to_s) do |el|
+                  el << item.elements.first
+                end
+              end
+            end
+          end
+        end
+
+        def send_result_iq(node, item)
+          result = to_result
+          if item
+            result << result.document.create_element('pubsub') do |pubsub|
+              pubsub.default_namespace = NS
+              pubsub << result.document.create_element('publish', 'node' => node) do |publish|
+                publish << result.document.create_element('item', 'id' => item['id'])
+              end
+            end
+          end
+          stream.write(result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub/subscribe.rb b/lib/vines/stanza/pubsub/subscribe.rb
new file mode 100644
index 0000000..24934e8
--- /dev/null
+++ b/lib/vines/stanza/pubsub/subscribe.rb
@@ -0,0 +1,44 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub
+      class Subscribe < PubSub
+        NS = NAMESPACES[:pubsub]
+
+        register "/iq[@id and @type='set']/ns:pubsub/ns:subscribe", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          validate_to_address
+
+          node = self.xpath('ns:pubsub/ns:subscribe', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
+          node = node.first
+          id, jid = node['node'], JID.new(node['jid'])
+
+          raise StanzaErrors::BadRequest.new(self, 'modify') unless stream.user.jid.bare == jid.bare
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
+          raise StanzaErrors::PolicyViolation.new(self, 'wait') if pubsub.subscribed?(id, jid)
+
+          pubsub.subscribe(id, jid)
+          send_result_iq(id, jid)
+        end
+
+        private
+
+        def send_result_iq(id, jid)
+          result = to_result
+          result << result.document.create_element('pubsub') do |node|
+            node.default_namespace = NS
+            node << result.document.create_element('subscription',
+              'node' => id,
+              'jid' => jid.to_s,
+              'subscription' => 'subscribed')
+          end
+          stream.write(result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stanza/pubsub/unsubscribe.rb b/lib/vines/stanza/pubsub/unsubscribe.rb
new file mode 100644
index 0000000..bda5887
--- /dev/null
+++ b/lib/vines/stanza/pubsub/unsubscribe.rb
@@ -0,0 +1,30 @@
+# encoding: UTF-8
+
+module Vines
+  class Stanza
+    class PubSub
+      class Unsubscribe < PubSub
+        NS = NAMESPACES[:pubsub]
+
+        register "/iq[@id and @type='set']/ns:pubsub/ns:unsubscribe", 'ns' => NS
+
+        def process
+          return if route_iq || !allowed?
+          validate_to_address
+
+          node = self.xpath('ns:pubsub/ns:unsubscribe', 'ns' => NS)
+          raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
+          node = node.first
+          id, jid = node['node'], JID.new(node['jid'])
+
+          raise StanzaErrors::Forbidden.new(self, 'auth') unless stream.user.jid.bare == jid.bare
+          raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
+          raise StanzaErrors::UnexpectedRequest.new(self, 'cancel') unless pubsub.subscribed?(id, jid)
+
+          pubsub.unsubscribe(id, jid)
+          stream.write(to_result)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/storage.rb b/lib/vines/storage.rb
new file mode 100644
index 0000000..0e09590
--- /dev/null
+++ b/lib/vines/storage.rb
@@ -0,0 +1,291 @@
+# encoding: UTF-8
+
+module Vines
+  class Storage
+    include Vines::Log
+
+    @@nicks = {}
+
+    # Register a nickname that can be used in the config file to specify this
+    # storage implementation.
+    #
+    # name - The String name for this storage backend.
+    #
+    # Returns nothing.
+    def self.register(name)
+      @@nicks[name.to_sym] = self
+    end
+
+    def self.from_name(name, &block)
+      klass = @@nicks[name.to_sym]
+      raise "#{name} storage class not found" unless klass
+      klass.new(&block)
+    end
+
+    # Wrap a blocking IO method in a new method that pushes the original method
+    # onto EventMachine's thread pool using EM#defer. Storage classes implemented
+    # with blocking IO don't need to worry about threading or blocking the
+    # EventMachine reactor thread if they wrap their methods with this one.
+    #
+    # Examples
+    #
+    #   def find_user(jid)
+    #     some_blocking_lookup(jid)
+    #   end
+    #   defer :find_user
+    #
+    # Storage classes that use asynchronous IO (through an EventMachine
+    # enabled library like em-http-request or em-redis) don't need any special
+    # consideration and must not use this method.
+    #
+    # Returns nothing.
+    def self.defer(method)
+      old = instance_method(method)
+      define_method method do |*args|
+        fiber = Fiber.current
+        op = operation { old.bind(self).call(*args) }
+        cb = proc {|result| fiber.resume(result) }
+        EM.defer(op, cb)
+        Fiber.yield
+      end
+    end
+
+    # Wrap a method with Fiber yield and resume logic. The method must yield
+    # its result to a block. This makes it easier to write asynchronous
+    # implementations of `authenticate`, `find_user`, and `save_user` that
+    # block and return a result rather than yielding.
+    #
+    # Examples
+    #
+    #   def find_user(jid)
+    #     http = EM::HttpRequest.new(url).get
+    #     http.callback { yield build_user_from_http_response(http) }
+    #   end
+    #   fiber :find_user
+    #
+    # Because `find_user` has been wrapped in Fiber logic, we can call it
+    # synchronously even though it uses asynchronous EventMachine calls.
+    #
+    #   user = storage.find_user('alice at wonderland.lit')
+    #   puts user.nil?
+    #
+    # Returns nothing.
+    def self.fiber(method)
+      old = instance_method(method)
+      define_method method do |*args|
+        fiber, yielding = Fiber.current, true
+        old.bind(self).call(*args) do |user|
+          fiber.resume(user) rescue yielding = false
+        end
+        Fiber.yield if yielding
+      end
+    end
+
+    # Validate the username and password pair.
+    #
+    # username - The String login JID to verify.
+    # password - The String password the user presented to the server.
+    #
+    # Examples
+    #
+    #   user = storage.authenticate('alice at wonderland.lit', 'secr3t')
+    #   puts user.nil?
+    #
+    # This default implementation validates the password against a bcrypt hash
+    # of the password stored in the database. Sub-classes not using bcrypt
+    # passwords must override this method.
+    #
+    # Returns a Vines::User object on success, nil on failure.
+    def authenticate(username, password)
+      user = find_user(username)
+      hash = BCrypt::Password.new(user.password) rescue nil
+      (hash && hash == password) ? user : nil
+    end
+
+    # Find the user in the storage database by their unique JID.
+    #
+    # jid - The String or JID of the user, possibly nil. This may be either a
+    #       bare JID or full JID. Implementations of this method must convert
+    #       the JID to a bare JID before searching for the user in the database.
+    #
+    # Examples
+    #
+    #   # Bare JID lookup.
+    #   user = storage.find_user('alice at wonderland.lit')
+    #   puts user.nil?
+    #
+    #   # Full JID lookup.
+    #   user = storage.find_user('alice at wonderland.lit/tea')
+    #   puts user.nil?
+    #
+    # Returns the User identified by the JID, nil if not found.
+    def find_user(jid)
+      raise 'subclass must implement'
+    end
+
+    # Persist the user to the database, and return when the save is complete.
+    #
+    # user - The User to persist.
+    #
+    # Examples
+    #
+    #   alice = Vines::User.new(jid: 'alice at wonderland.lit')
+    #   storage.save_user(alice)
+    #   puts 'saved'
+    #
+    # Returns nothing.
+    def save_user(user)
+      raise 'subclass must implement'
+    end
+
+    # Find the user's vcard by their unique JID.
+    #
+    # jid - The String or JID of the user, possibly nil. This may be either a
+    #       bare JID or full JID. Implementations of this method must convert
+    #       the JID to a bare JID before searching for the vcard in the database.
+    #
+    # Examples
+    #
+    #   card = storage.find_vcard('alice at wonderland.lit')
+    #   puts card.nil?
+    #
+    # Returns the vcard's Nokogiri::XML::Node, nil if not found.
+    def find_vcard(jid)
+      raise 'subclass must implement'
+    end
+
+    # Save the vcard to the database, and return when the save is complete.
+    #
+    # jid  - The String or JID of the user, possibly nil. This may be either a
+    #        bare JID or full JID. Implementations of this method must convert
+    #        the JID to a bare JID before saving the vcard.
+    # card - The vcard's Nokogiri::XML::Node.
+    #
+    # Examples
+    #
+    #   card = Nokogiri::XML('<vCard>...</vCard>').root
+    #   storage.save_vcard('alice at wonderland.lit', card)
+    #   puts 'saved'
+    #
+    # Returns nothing.
+    def save_vcard(jid, card)
+      raise 'subclass must implement'
+    end
+
+    # Find the private XML fragment previously stored by the user. Private
+    # XML storage uniquely identifies fragments by JID, root element name,
+    # and root element namespace.
+    #
+    # jid  - The String or JID of the user, possibly nil. This may be either a
+    #        bare JID or full JID. Implementations of this method must convert
+    #        the JID to a bare JID before searching for the fragment in the database.
+    # node - The XML::Node that uniquely identifies the fragment by element
+    #        name and namespace.
+    #
+    # Examples
+    #
+    #   root = Nokogiri::XML('<custom xmlns="urn:custom:ns"/>').root
+    #   fragment = storage.find_fragment('alice at wonderland.lit', root)
+    #   puts fragment.nil?
+    #
+    # Returns the fragment's Nokogiri::XML::Node or nil if not found.
+    def find_fragment(jid, node)
+      raise 'subclass must implement'
+    end
+
+    # Save the XML fragment to the database, and return when the save is complete.
+    #
+    # jid      - The String or JID of the user, possibly nil. This may be
+    #            either a bare JID or full JID. Implementations of this method
+    #            must convert the JID to a bare JID before searching for the
+    #            fragment.
+    # fragment - The XML::Node to save.
+    #
+    # Examples
+    #
+    #   fragment = Nokogiri::XML('<custom xmlns="urn:custom:ns">some data</custom>').root
+    #   storage.save_fragment('alice at wonderland.lit', fragment)
+    #   puts 'saved'
+    #
+    # Returns nothing.
+    def save_fragment(jid, fragment)
+      raise 'subclass must implement'
+    end
+
+    # Check whether offline messages are available for the user
+    # jid      - The String or JID of the user, possibly nil. This may be
+    #            either a bare JID or full JID. Implementations of this method
+    #            must convert the JID to a bare JID before searching for
+    #            offline messages.
+    #
+    # Returns hash
+    def find_messages(jid)
+      raise 'subclass must implement'
+    end
+
+    # Save the offline message to the database, and return when the save is complete.
+    #
+    # from      - The String or JID of the user.
+    # to        - The String or JID of the user.
+    # message   - The message you want to store.
+    #
+    # Returns nothing.
+    def save_message(from, to, message)
+      raise 'subclass must implement'
+    end
+
+    # Delete a offline message from database.
+    #
+    # id       - The identifier of the offline message
+    #
+    # Returns nothing.
+    def destroy_message(id)
+      raise 'subclass must implement'
+    end
+
+    # Retrieve the avatar url by jid.
+    #
+    # jid      - The String or JID of the user.
+    #
+    # Returns string
+    def find_avatar_by_jid(jid)
+      raise 'subclass must implement'
+    end
+
+    private
+
+    # Determine if any of the arguments are nil or empty strings.
+    #
+    # Examples
+    #
+    #   username, password = 'alice at wonderland.lit', ''
+    #   empty?(username, password) #=> true
+    #
+    # Returns true if any of the arguments are nil or empty strings.
+    def empty?(*args)
+      args.flatten.any? {|arg| (arg || '').strip.empty? }
+    end
+
+    # Create a proc suitable for running on the EM.defer thread pool, that
+    # traps and logs any errors thrown by the provided block.
+    #
+    # block - The block to wrap in error handling.
+    #
+    # Examples
+    #
+    #   op = operation { do_something_on_thread_pool() }
+    #   EM.defer(op)
+    #
+    # Returns a Proc.
+    def operation
+      proc do
+        begin
+          yield
+        rescue => e
+          log.error("Thread pool operation failed: #{e.message}")
+          nil
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/storage/local.rb b/lib/vines/storage/local.rb
new file mode 100644
index 0000000..c0e47ea
--- /dev/null
+++ b/lib/vines/storage/local.rb
@@ -0,0 +1,151 @@
+# encoding: UTF-8
+
+module Vines
+  class Storage
+
+    # A storage implementation that persists data to YAML files on the
+    # local file system.
+    class Local < Storage
+      register :fs
+
+      def initialize(&block)
+        @dir = nil
+        instance_eval(&block)
+        unless @dir && File.directory?(@dir) && File.writable?(@dir)
+          raise 'Must provide a writable storage directory'
+        end
+
+        %w[user vcard fragment].each do |sub|
+          sub = File.expand_path(sub, @dir)
+          Dir.mkdir(sub, 0700) unless File.exists?(sub)
+        end
+      end
+
+      def dir(dir=nil)
+        dir ? @dir = File.expand_path(dir) : @dir
+      end
+
+      def find_user(jid)
+        jid = JID.new(jid).bare.to_s
+        file = "user/#{jid}" unless jid.empty?
+        record = YAML.load(read(file)) rescue nil
+        return User.new(jid: jid).tap do |user|
+          user.name, user.password = record.values_at('name', 'password')
+          (record['roster'] || {}).each_pair do |jid, props|
+            user.roster << Contact.new(
+              jid: jid,
+              name: props['name'],
+              subscription: props['subscription'],
+              ask: props['ask'],
+              groups: props['groups'] || [])
+          end
+        end if record
+      end
+
+      def save_user(user)
+        record = {'name' => user.name, 'password' => user.password, 'roster' => {}}
+        user.roster.each do |contact|
+          record['roster'][contact.jid.bare.to_s] = contact.to_h
+        end
+        save("user/#{user.jid.bare}", YAML.dump(record))
+      end
+
+      def find_vcard(jid)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        file = "vcard/#{jid}"
+        Nokogiri::XML(read(file)).root rescue nil
+      end
+
+      def save_vcard(jid, card)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        save("vcard/#{jid}", card.to_xml)
+      end
+
+      def find_fragment(jid, node)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        file = 'fragment/%s' % fragment_id(jid, node)
+        Nokogiri::XML(read(file)).root rescue nil
+      end
+
+      def save_fragment(jid, node)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        file = 'fragment/%s' % fragment_id(jid, node)
+        save(file, node.to_xml)
+      end
+
+      def find_messages(jid)
+        {}
+      end
+
+      def save_message(from, to, message)
+        # do nothing
+      end
+
+      def destroy_message(id)
+        # do nothing
+      end
+
+      private
+
+      # Resolves a relative file name into an absolute path inside the
+      # storage directory.
+      #
+      # file - A fully-qualified or relative file name String.
+      #
+      # Returns the fully-qualified file path String.
+      #
+      # Raises RuntimeError if the resolved path is outside of the storage
+      # directory. This prevents directory path traversals with maliciously
+      # crafted JIDs.
+      def absolute_path(file)
+        File.expand_path(file, @dir).tap do |absolute|
+          parent = File.dirname(File.dirname(absolute))
+          raise 'path traversal' unless parent == @dir
+        end
+      end
+
+      # Read the file from the filesystem and return its contents as a String.
+      # All files are assumed to be encoded as UTF-8.
+      #
+      # file - A fully-qualified or relative file name String.
+      #
+      # Returns the file content as a UTF-8 encoded String.
+      def read(file)
+        file = absolute_path(file)
+        File.read(file, encoding: 'utf-8')
+      end
+
+      # Write the content to the file. Make sure to consistently encode files
+      # we read and write as UTF-8.
+      #
+      # file    - A fully-qualified or relative file name String.
+      # content - The String to write.
+      #
+      # Returns nothing.
+      def save(file, content)
+        file = absolute_path(file)
+        File.open(file, 'w:utf-8') {|f| f.write(content) }
+        File.chmod(0600, file)
+      end
+
+      # Generates a unique file id for the user's private XML fragment.
+      #
+      # Private XML fragment storage needs to uniquely identify fragment files
+      # on disk. We combine the user's JID with a SHA-1 hash of the element's
+      # name and namespace to avoid special characters in the file name.
+      #
+      # jid  - A bare JID identifying the user who owns this fragment.
+      # node - A Nokogiri::XML::Node for the XML to be stored.
+      #
+      # Returns an id String suitable for use in a file name.
+      def fragment_id(jid, node)
+        id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
+        "#{jid}-#{id}"
+      end
+    end
+  end
+end
diff --git a/lib/vines/storage/null.rb b/lib/vines/storage/null.rb
new file mode 100644
index 0000000..d06ce59
--- /dev/null
+++ b/lib/vines/storage/null.rb
@@ -0,0 +1,51 @@
+# encoding: UTF-8
+
+module Vines
+  class Storage
+    # A storage implementation that does not persist data to any form of storage.
+    # When looking up the storage object for a domain, it's easier to treat a
+    # missing domain with a Null storage than checking for nil.
+    #
+    # For example, presence subscription stanzas sent to a pubsub subdomain
+    # have no storage. Rather than checking for nil storage or pubsub addresses,
+    # it's easier to treat stanzas to pubsub domains as Null storage that never
+    # finds or saves users and their rosters.
+    class Null < Storage
+      def find_user(jid)
+        nil
+      end
+
+      def save_user(user)
+        # do nothing
+      end
+
+      def find_vcard(jid)
+        nil
+      end
+
+      def save_vcard(jid, card)
+        # do nothing
+      end
+
+      def find_fragment(jid, node)
+        nil
+      end
+
+      def save_fragment(jid, node)
+        # do nothing
+      end
+
+      def find_messages(jid)
+        {}
+      end
+
+      def save_message(from, to, message)
+        # do nothing
+      end
+
+      def destroy_message(id)
+        # do nothing
+      end
+    end
+  end
+end
diff --git a/lib/vines/storage/sql.rb b/lib/vines/storage/sql.rb
new file mode 100644
index 0000000..1ea0047
--- /dev/null
+++ b/lib/vines/storage/sql.rb
@@ -0,0 +1,346 @@
+# encoding: UTF-8
+
+module Vines
+  class Storage
+    class Sql < Storage
+      include Vines::Log
+
+      register :sql
+
+      class Profile < ActiveRecord::Base
+        belongs_to :person
+      end
+      class Person < ActiveRecord::Base
+        has_one :profile
+
+        def local?
+          !self.owner_id.nil?
+        end
+
+        def name(opts = {})
+          self.profile.first_name.blank? && self.profile.last_name.blank? ?
+            self.diaspora_handle : "#{self.profile.first_name.to_s.strip} #{self.profile.last_name.to_s.strip}".strip
+        end
+      end
+
+      class Aspect < ActiveRecord::Base
+        belongs_to :users
+
+        has_many :aspect_memberships
+        has_many :contacts
+      end
+
+      class AspectMembership < ActiveRecord::Base
+        belongs_to :aspect
+        belongs_to :contact
+
+        has_one :users, :through => :contact
+        has_one :person, :through => :contact
+      end
+
+      class Contact < ActiveRecord::Base
+        scope :chat_enabled, -> {
+          joins(:aspects)
+          .where("aspects.chat_enabled = ?", true)
+          .group("person_id, contacts.id")
+        }
+
+        belongs_to :users
+        belongs_to :person
+
+        has_many :aspect_memberships
+        has_many :aspects, :through => :aspect_memberships
+      end
+
+      class User < ActiveRecord::Base
+        has_many :contacts
+        has_many :chat_contacts, :dependent => :destroy
+        has_many :fragments, :dependent => :delete_all
+
+        has_one :person, :foreign_key => :owner_id
+      end
+
+      class ChatOfflineMessage < ActiveRecord::Base; end
+
+      class ChatContact < ActiveRecord::Base
+        belongs_to :users
+      end
+
+      class ChatFragment < ActiveRecord::Base
+        belongs_to :users
+      end
+
+      # Wrap the method with ActiveRecord connection pool logic, so we properly
+      # return connections to the pool when we're finished with them. This also
+      # defers the original method by pushing it onto the EM thread pool because
+      # ActiveRecord uses blocking IO.
+      def self.with_connection(method, args={})
+        deferrable = args.key?(:defer) ? args[:defer] : true
+        old = instance_method(method)
+        define_method method do |*args|
+          ActiveRecord::Base.connection_pool.with_connection do
+            old.bind(self).call(*args)
+          end
+        end
+        defer(method) if deferrable
+      end
+
+      def initialize(&block)
+        @config = {}
+        unless defined? Rails
+          raise "You configured diaspora-sql adapter without Diaspora environment"
+        end
+
+        config = Rails.application.config.database_configuration[Rails.env]
+        %w[adapter database host port username password].each do |key|
+          @config[key.to_sym] = config[key]
+        end
+
+        required = [:adapter, :database]
+        required << [:host, :port] unless @config[:adapter] == 'sqlite3'
+        required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
+        [:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
+        establish_connection
+      end
+
+      def find_user(jid)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        xuser = user_by_jid(jid)
+        return Vines::User.new(jid: jid).tap do |user|
+          user.name, user.password, user.token =
+            xuser.username,
+            xuser.encrypted_password,
+            xuser.authentication_token
+
+          # add diaspora contacts
+          xuser.contacts.chat_enabled.each do |contact|
+            handle = contact.person.diaspora_handle
+            profile = contact.person.profile
+            name = "#{profile.first_name} #{profile.last_name}"
+            name = handle.gsub(/\@.*?$/, '') if name.strip.empty?
+            ask, subscription, groups = get_diaspora_flags(contact)
+            user.roster << Vines::Contact.new(
+              jid: handle,
+              name: name,
+              subscription: subscription,
+              from_diaspora: true,
+              groups: groups,
+              ask: ask)
+          end
+
+          # add external contacts
+          xuser.chat_contacts.each do |contact|
+            user.roster << Vines::Contact.new(
+              jid: contact.jid,
+              name: contact.name,
+              subscription: contact.subscription,
+              groups: get_external_groups,
+              ask: contact.ask)
+          end
+        end if xuser
+      end
+      with_connection :find_user
+
+      def authenticate(username, password)
+        user = find_user(username)
+
+        pepper = "#{password}#{Devise.pepper}" rescue password
+        dbhash = BCrypt::Password.new(user.password) rescue nil
+        hash = BCrypt::Engine.hash_secret(pepper, dbhash.salt) rescue nil
+
+        userAuth = ((hash && dbhash) && hash == dbhash)
+        tokenAuth = ((password && user) && password == user.token)
+        (tokenAuth || userAuth)? user : nil
+      end
+
+      def save_user(user)
+        # it is not possible to register an account via xmpp server
+        xuser = user_by_jid(user.jid) || return
+
+        # remove deleted contacts from roster
+        xuser.chat_contacts.delete(xuser.chat_contacts.select do |contact|
+          !user.contact?(contact.jid)
+        end)
+
+        # update contacts
+        xuser.chat_contacts.each do |contact|
+          fresh = user.contact(contact.jid)
+          contact.update_attributes(
+            name: fresh.name,
+            ask: fresh.ask,
+            subscription: fresh.subscription)
+        end
+
+        # add new contacts to roster
+        jids = xuser.chat_contacts.map {|c|
+          c.jid if (c.user_id == xuser.id)
+        }.compact
+        user.roster.select {|contact|
+          unless contact.from_diaspora
+            xuser.chat_contacts.build(
+              user_id: xuser.id,
+              jid: contact.jid.bare.to_s,
+              name: contact.name,
+              ask: contact.ask,
+              subscription: contact.subscription) unless jids.include?(contact.jid.bare.to_s)
+          end
+        }
+        xuser.save
+      end
+      with_connection :save_user
+
+      def find_vcard(jid)
+        jid = JID.new(jid).bare.to_s
+        return nil if jid.empty?
+        person = Sql::Person.find_by_diaspora_handle(jid)
+        return nil unless person.nil? || person.local?
+
+        build_vcard(person)
+      end
+      with_connection :find_vcard
+
+      def save_vcard(jid, card)
+        # NOTE this is not supported. If you'd like to change your
+        # vcard details you can edit it via diaspora-web-interface
+        nil
+      end
+      with_connection :save_vcard
+
+      def find_messages(jid)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        results = Hash.new
+        Sql::ChatOfflineMessage.where(:to => jid).each do |r|
+          results[r.id] = {
+            :from => r.from,
+            :to => r.to,
+            :message => r.message,
+            :created_at => r.created_at
+          }
+        end
+        return results
+      end
+      with_connection :find_messages
+
+      def save_message(from, to, msg)
+        return if from.empty? || to.empty? || msg.empty?
+        com = Sql::ChatOfflineMessage
+        current = com.count(:to => to)
+        unless current < Config.instance.max_offline_msgs
+          com.where(:to => to)
+             .order(created_at: :asc)
+             .first
+             .delete
+        end
+        com.create(:from => from, :to => to, :message => msg)
+      end
+      with_connection :save_message
+
+      def destroy_message(id)
+        id = id.to_i rescue nil
+        return if id.nil?
+        Sql::ChatOfflineMessage.find(id).destroy
+      end
+      with_connection :destroy_message
+
+      def find_fragment(jid, node)
+        jid = JID.new(jid).bare.to_s
+        return if jid.empty?
+        if fragment = fragment_by_jid(jid, node)
+          Nokogiri::XML(fragment.xml).root rescue nil
+        end
+      end
+      with_connection :find_fragment
+
+      def save_fragment(jid, node)
+        jid = JID.new(jid).bare.to_s
+        fragment = fragment_by_jid(jid, node) ||
+        Sql::ChatFragment.new(
+          user: user_by_jid(jid),
+          root: node.name,
+          namespace: node.namespace.href)
+        fragment.xml = node.to_xml
+        fragment.save
+      end
+      with_connection :save_fragment
+
+      def find_avatar_by_jid(jid)
+        jid = JID.new(jid).bare.to_s
+        return nil if jid.empty?
+
+        person = Sql::Person.find_by_diaspora_handle(jid)
+        return nil if person.nil?
+        return nil if person.profile.nil?
+        return nil unless person.local?
+        person.profile.image_url
+      end
+      with_connection :find_avatar_by_jid
+
+      private
+        def establish_connection
+          ActiveRecord::Base.logger = log # using vines logger
+          ActiveRecord::Base.establish_connection(@config)
+        end
+
+        def user_by_jid(jid)
+          name = JID.new(jid).node
+          Sql::User.find_by_username(name)
+        end
+
+        def get_external_groups
+          # TODO Make the group name configurable by the user
+          # https://github.com/diaspora/vines/issues/39
+          group_name = "External XMPP Contacts"
+          matches = Sql::Aspect.where(:name => group_name).count
+          if matches > 0
+            group_name = "#{group_name} (#{matches + 1})"
+          end
+          [ group_name ]
+        end
+
+        def fragment_by_jid(jid, node)
+          jid = JID.new(jid).bare.to_s
+          clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
+          Sql::ChatFragment.where(clause, jid, node.name, node.namespace.href).first
+        end
+
+        def build_vcard(person)
+          builder = Nokogiri::XML::Builder.new
+          builder.vCard('xmlns' => 'vcard-temp') do |xml|
+            xml.send(:"FN", person.name) if person.name
+            xml.send(:"N") do |sub|
+              sub.send(:"FAMILY", person.profile.last_name) if person.profile.last_name
+              sub.send(:"GIVEN", person.profile.first_name) if person.profile.first_name
+            end if (person.profile.last_name? || person.profile.first_name?)
+            xml.send(:"URL", person.url) if person.url
+            xml.send(:"PHOTO") do |sub|
+              sub.send(:"EXTVAL", person.profile.image_url)
+            end if person.profile.image_url
+          end
+
+          builder.to_xml :save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
+        end
+
+        def get_diaspora_flags(contact)
+          groups = Array.new
+          ask, subscription = 'none', 'none'
+          contact.aspects.each do |aspect|
+            groups.push(aspect.name)
+          end
+
+          if contact.sharing && contact.receiving
+            subscription = 'both'
+          elsif contact.sharing && !contact.receiving
+            ask = 'suscribe'
+            subscription = 'from'
+          elsif !contact.sharing && contact.receiving
+            subscription = 'to'
+          else
+            ask = 'suscribe'
+          end
+          return ask, subscription, groups
+        end
+    end
+  end
+end
diff --git a/lib/vines/store.rb b/lib/vines/store.rb
new file mode 100644
index 0000000..6b1fdb6
--- /dev/null
+++ b/lib/vines/store.rb
@@ -0,0 +1,152 @@
+# encoding: UTF-8
+
+module Vines
+  # An X509 certificate store that validates certificate trust chains.
+  # This uses the conf/certs/*.crt files as the list of trusted root
+  # CA certificates.
+  class Store
+    include Vines::Log
+    @@sources = nil
+
+    # Create a certificate store to read certificate files from the given
+    # directory.
+    #
+    # dir - The String directory name (absolute or relative).
+    def initialize(dir)
+      @dir = File.expand_path(dir)
+      @store = OpenSSL::X509::Store.new
+      certs.each {|cert| append(cert) }
+    end
+
+    # Return true if the certificate is signed by a CA certificate in the
+    # store. If the certificate can be trusted, it's added to the store so
+    # it can be used to trust other certs.
+    #
+    # pem - The PEM encoded certificate String.
+    #
+    # Returns true if the certificate is trusted.
+    def trusted?(pem)
+      if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
+        @store.verify(cert).tap do |trusted|
+          append(cert) if trusted
+        end
+      end
+    end
+
+    # Return true if the domain name matches one of the names in the
+    # certificate. In other words, is the certificate provided to us really
+    # for the domain to which we think we're connected?
+    #
+    # pem    - The PEM encoded certificate String.
+    # domain - The domain name String.
+    #
+    # Returns true if the certificate was issued for the domain.
+    def domain?(pem, domain)
+      if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
+        OpenSSL::SSL.verify_certificate_identity(cert, domain) rescue false
+      end
+    end
+
+    # Return the trusted root CA certificates installed in conf/certs. These
+    # certificates are used to start the trust chain needed to validate certs
+    # we receive from clients and servers.
+    #
+    # Returns an Array of OpenSSL::X509::Certificate objects.
+    def certs
+      @@sources ||= begin
+        pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
+        files = Dir[File.join(@dir, '*.crt')]
+        if defined?(AppConfig)
+          chain = AppConfig.environment.certificate_authorities.get
+          files << chain unless chain.nil?
+        end
+        pairs = files.map do |name|
+          begin
+            File.open(name, "r:UTF-8") do |f|
+              pems = f.read.scan(pattern)
+              certs = pems.map {|pem| OpenSSL::X509::Certificate.new(pem) }
+              certs.reject! {|cert| cert.not_after < Time.now }
+              [name, certs]
+            end
+          rescue ArgumentError => e
+            log.error("Skipping '#{name}' cause of '#{e.message.to_s}'! "+
+                      "Checkout https://wiki.diasporafoundation.org/Vines#FAQ "+
+                      "for further instructions.")
+          end
+        end
+        Hash[pairs.compact]
+      end
+      @@sources.values.flatten
+    end
+
+    # Returns a pair of file names containing the public key certificate
+    # and matching private key for the given domain. This supports using
+    # wildcard certificate files to serve several subdomains.
+    #
+    # Finding the certificate and private key file for a domain follows these steps:
+    #
+    # - Look for <domain>.crt and <domain>.key files in the conf/certs
+    #   directory. If found, return those file names, otherwise . . .
+    #
+    # - Inspect all conf/certs/*.crt files for certificates that contain the
+    #   domain name either as the subject common name (CN) or as a DNS
+    #   subjectAltName. The corresponding private key must be in a file of the
+    #   same name as the certificate's, but with a .key extension.
+    #
+    # So in the simplest configuration, the tea.wonderland.lit encryption files
+    # would be named:
+    #
+    # - conf/certs/tea.wonderland.lit.crt
+    # - conf/certs/tea.wonderland.lit.key
+    #
+    # However, in the case of a wildcard certificate for *.wonderland.lit,
+    # the files would be:
+    #
+    # - conf/certs/wonderland.lit.crt
+    # - conf/certs/wonderland.lit.key
+    #
+    # These same two files would be returned for the subdomains of:
+    #
+    # - tea.wonderland.lit
+    # - crumpets.wonderland.lit
+    # - etc.
+    #
+    # domain - The String domain name.
+    #
+    # Returns a two element String array for the certificate and private key
+    #   file names or nil if not found.
+    def files_for_domain(domain)
+      crt = File.expand_path("#{domain}.crt", @dir)
+      key = File.expand_path("#{domain}.key", @dir)
+      return [crt, key] if File.exists?(crt) && File.exists?(key)
+
+      # Might be a wildcard cert file.
+      @@sources.each do |file, certs|
+        certs.each do |cert|
+          if OpenSSL::SSL.verify_certificate_identity(cert, domain)
+            key = file.chomp(File.extname(file)) + '.key'
+            return [file, key] if File.exists?(file) && File.exists?(key)
+          end
+        end
+      end
+      log.error("Your're using vines without a certificate! "+
+                "Checkout https://wiki.diasporafoundation.org/Vines#Certificates "+
+                "for further instructions.")
+      nil
+    end
+
+    private
+
+    # Add a trusted certificate to the store, suppressing any OpenSSL errors
+    # caused by the certificate already being stored.
+    #
+    # cert - The OpenSSL::X509::Certificate to add.
+    #
+    # Returns nothing.
+    def append(cert)
+      @store.add_cert(cert)
+    rescue OpenSSL::X509::StoreError
+      # Already added to store.
+    end
+  end
+end
diff --git a/lib/vines/stream.rb b/lib/vines/stream.rb
new file mode 100644
index 0000000..bd322bd
--- /dev/null
+++ b/lib/vines/stream.rb
@@ -0,0 +1,309 @@
+# encoding: UTF-8
+
+module Vines
+  # The base class for various XMPP streams (c2s, s2s, component, http),
+  # containing behavior common to all streams like rate limiting, stanza
+  # parsing, and stream error handling.
+  class Stream < EventMachine::Connection
+    include Vines::Log
+
+    ERROR = 'error'.freeze
+    STREAM = 'stream'.freeze
+    PAD   = 20
+
+    attr_reader   :config, :domain, :id, :state
+    attr_accessor :user
+
+    def initialize(config)
+      @config = config
+    end
+
+    # Initialize the stream after its connection to the server has completed.
+    # EventMachine calls this method when an incoming connection is accepted
+    # into the event loop.
+    #
+    # Returns nothing.
+    def post_init
+      @remote_addr, @local_addr = addresses
+      @user, @closed, @stanza_size = nil, false, 0
+      @bucket = TokenBucket.new(100, 10)
+      @store = Store.new(@config.certs)
+      @nodes = EM::Queue.new
+      process_node_queue
+      create_parser
+      log.info { "%s %21s -> %s" %
+        ['Stream connected:'.ljust(PAD), @remote_addr, @local_addr] }
+    end
+
+    # Initialize a new XML parser for this connection. This is called when the
+    # stream is first connected as well as for stream restarts during
+    # negotiation. Subclasses can override this method to provide a different
+    # type of parser (e.g. HTTP).
+    #
+    # Returns nothing.
+    def create_parser
+      @parser = Parser.new.tap do |parser|
+        parser.stream_open {|node| @nodes.push(node) }
+        parser.stream_close { close_connection }
+        parser.stanza {|node| @nodes.push(node) }
+      end
+    end
+
+    # Advance the state machine into the `Closed` state so any remaining queued
+    # nodes are not processed while we're waiting for EM to actually close the
+    # connection.
+    #
+    # Returns nothing.
+    def close_connection(after_writing=false)
+      super
+      @closed = true
+      advance(Client::Closed.new(self))
+    end
+
+    # Read bytes off the stream and feed them into the XML parser. EventMachine
+    # is responsible for calling this method on its event loop as connections
+    # become readable.
+    #
+    # data - The byte String sent to the server from the client, hopefully XML.
+    #
+    # Returns nothing.
+    def receive_data(data)
+      return if @closed
+      @stanza_size += data.bytesize
+      if @stanza_size < max_stanza_size
+        @parser << data rescue error(StreamErrors::NotWellFormed.new)
+      else
+        error(StreamErrors::PolicyViolation.new('max stanza size reached'))
+      end
+    end
+
+    # Reset the connection's XML parser when a new <stream:stream> header
+    # is received.
+    #
+    # Returns nothing.
+    def reset
+      create_parser
+    end
+
+    # Returns the storage system for the domain. If no domain is given,
+    # the stream's storage mechanism is returned.
+    def storage(domain=nil)
+      @config.storage(domain || self.domain)
+    end
+
+    # Returns the Config::Host virtual host for the stream's domain.
+    def vhost
+      @config.vhost(domain)
+    end
+
+    # Reload the user's information into their active connections. Call this
+    # after storage.save_user() to sync the new user state with their other
+    # connections.
+    #
+    # user - The User whose connection info needs refreshing.
+    #
+    # Returns nothing.
+    def update_user_streams(user)
+      connected_resources(user.jid.bare).each do |stream|
+        stream.user.update_from(user)
+      end
+    end
+
+    def connected_resources(jid)
+      router.connected_resources(jid, user.jid)
+    end
+
+    def available_resources(*jid)
+      router.available_resources(*jid, user.jid)
+    end
+
+    def interested_resources(*jid)
+      router.interested_resources(*jid, user.jid)
+    end
+
+    def ssl_verify_peer(pem)
+      # Skip verifying if user accept self-signed certificates
+      return true if self.vhost.accept_self_signed?
+      # EM is supposed to close the connection when this returns false,
+      # but it only does that for inbound connections, not when we
+      # make a connection to another server.
+      @store.trusted?(pem).tap do |trusted|
+        close_connection unless trusted
+      end
+    end
+
+    def cert_domain_matches?(domain)
+      @store.domain?(get_peer_cert, domain)
+    end
+
+    # Send the data over the wire to this client.
+    #
+    # data - The XML String or XML::Node to write to the socket.
+    #
+    # Returns nothing.
+    def write(data)
+      log_node(data, :out)
+      if data.respond_to?(:to_xml)
+        data = data.to_xml(:indent => 0)
+      end
+      send_data(data)
+    end
+
+    def encrypt
+      cert, key = @store.files_for_domain(domain)
+      start_tls(cert_chain_file: cert, private_key_file: key, verify_peer: true)
+    end
+
+    # Returns true if the TLS certificate and private key files for this domain
+    # exist and can be used to encrypt this stream.
+    def encrypt?
+      !@store.files_for_domain(domain).nil?
+    end
+
+    def unbind
+      router.delete(self)
+      log.info { "%s %21s -> %s" %
+        ['Stream disconnected:'.ljust(PAD), @remote_addr, @local_addr] }
+      log.info { "Streams connected: #{router.size}" }
+    end
+
+    # Advance the stream's state machine to the new state. XML nodes received
+    # by the stream will be passed to this state's `node` method.
+    #
+    # state - The Stream::State to process the stanzas next.
+    #
+    # Returns the new Stream::State.
+    def advance(state)
+      @state = state
+    end
+
+    # Stream level errors close the stream while stanza and SASL errors are
+    # written to the client and leave the stream open. All exceptions should
+    # pass through this method for consistent handling.
+    #
+    # e - The StandardError, usually XmppError, that occurred.
+    #
+    # Returns nothing.
+    def error(e)
+      case e
+      when SaslError, StanzaError
+        write(e.to_xml)
+      when StreamError
+        send_stream_error(e)
+        close_stream
+      else
+        log.error(e)
+        send_stream_error(StreamErrors::InternalServerError.new)
+        close_stream
+      end
+    end
+
+    def router
+      @config.router
+    end
+
+    private
+
+    # Determine the remote and local socket addresses used by this connection.
+    #
+    # Returns a two-element Array of String addresses.
+    def addresses
+      [get_peername, get_sockname].map do |addr|
+        addr ? Socket.unpack_sockaddr_in(addr)[0, 2].reverse.join(':') : 'unknown'
+      end
+    end
+
+    # Write the StreamError's xml to the stream. Subclasses can override
+    # this method with custom error writing behavior.
+    #
+    # A call to `close_stream` should follow this method. Stream level errors
+    # are fatal to the connection.
+    #
+    # e - The StreamError that caused the connection to close.
+    #
+    # Returns nothing.
+    def send_stream_error(e)
+      write(e.to_xml)
+    end
+
+    # Write a closing stream tag and close the connection. Subclasses can
+    # override this method for custom close behavior.
+    #
+    # Returns nothing.
+    def close_stream
+      write('</stream:stream>')
+      close_connection_after_writing
+    end
+
+    def error?(node)
+      ns = node.namespace ? node.namespace.href : nil
+      node.name == ERROR && ns == NAMESPACES[:stream]
+    end
+
+    # Schedule a queue pop on the EM thread to handle the next element. This
+    # guarantees all stanzas received on this stream are processed in order.
+    #
+    #   http://tools.ietf.org/html/rfc6120#section-10.1
+    #
+    # Once a node is processed, this method recursively schedules itself to pop
+    # the next node and so on. A single call to this method effectively begins
+    # an asynchronous node processing loop.
+    #
+    # Returns nothing.
+    def process_node_queue
+      @nodes.pop do |node|
+        Fiber.new do
+          process_node(node)
+          process_node_queue
+        end.resume unless @closed
+      end
+    end
+
+    def process_node(node)
+      log_node(node, :in)
+      @stanza_size = 0
+      enforce_rate_limit
+      if error?(node)
+        close_stream
+      else
+        update_stream_id(node)
+        state.node(node)
+      end
+    rescue => e
+      error(e)
+    end
+
+    def enforce_rate_limit
+      unless @bucket.take(1)
+        raise StreamErrors::PolicyViolation.new('rate limit exceeded')
+      end
+    end
+
+    def log_node(node, direction)
+      return unless log.debug?
+      from, to = @remote_addr, @local_addr
+      from, to = to, from if direction == :out
+      label = (direction == :out) ? 'Sent' : 'Received'
+      log.debug("%s %21s -> %s\n%s\n" %
+        ["#{label} stanza:".ljust(PAD), from, to, node])
+    end
+
+    # Determine if this is a valid domain-only JID that can be used in
+    # stream initiation stanza headers.
+    #
+    # jid - The String or JID to verify (e.g. 'wonderland.lit').
+    #
+    # Return true if the jid is domain-only.
+    def valid_address?(jid)
+      JID.new(jid).domain? rescue false
+    end
+
+    def update_stream_id(id_or_node)
+      if id_or_node.is_a? String
+        @id = id_or_node.freeze
+      elsif Node.stream?(id_or_node) # move stream? method somewhere else?
+        @id = id_or_node['id'].freeze
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client.rb b/lib/vines/stream/client.rb
new file mode 100644
index 0000000..ead9709
--- /dev/null
+++ b/lib/vines/stream/client.rb
@@ -0,0 +1,88 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    # Implements the XMPP protocol for client-to-server (c2s) streams. This
+    # serves connected streams using the jabber:client namespace.
+    class Client < Stream
+      MECHANISMS = %w[PLAIN].freeze
+
+      def initialize(config)
+        super
+        @session = Client::Session.new(self)
+      end
+
+      # Delegate behavior to the session that's storing our stream state.
+      def method_missing(name, *args)
+        @session.send(name, *args)
+      end
+
+      %w[advance domain state user user=].each do |name|
+        define_method name do |*args|
+          @session.send(name, *args)
+        end
+      end
+
+      %w[max_stanza_size max_resources_per_account].each do |name|
+        define_method name do |*args|
+          config[:client].send(name, *args)
+        end
+      end
+
+      # Return an array of allowed authentication mechanisms advertised as
+      # client stream features.
+      def authentication_mechanisms
+        MECHANISMS
+      end
+
+      def ssl_handshake_completed
+        if get_peer_cert
+          close_connection unless cert_domain_matches?(@session.domain)
+        end
+      end
+
+      def unbind
+        @session.unbind!(self)
+        super
+      end
+
+      def start(node)
+        to, from = %w[to from].map {|a| node[a] }
+        @session.domain = to unless @session.domain
+        send_stream_header(from)
+        raise StreamErrors::NotAuthorized if domain_change?(to)
+        raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
+        raise StreamErrors::ImproperAddressing unless valid_address?(@session.domain)
+        raise StreamErrors::HostUnknown unless config.vhost?(@session.domain)
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:client]
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
+      end
+
+      private
+
+      # The `to` domain address set on the initial stream header must not change
+      # during stream restarts. This prevents a user from authenticating in one
+      # domain, then using a stream in a different domain.
+      #
+      # to - The String domain JID to verify (e.g. 'wonderland.lit').
+      #
+      # Returns true if the client connection is misbehaving and should be closed.
+      def domain_change?(to)
+        to != @session.domain
+      end
+
+      def send_stream_header(to)
+        attrs = {
+          'xmlns'        => NAMESPACES[:client],
+          'xmlns:stream' => NAMESPACES[:stream],
+          'xml:lang'     => 'en',
+          'id'           => Kit.uuid,
+          'from'         => @session.domain,
+          'version'      => '1.0'
+        }
+        attrs['to'] = to if to
+        write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/auth.rb b/lib/vines/stream/client/auth.rb
new file mode 100644
index 0000000..cdc2419
--- /dev/null
+++ b/lib/vines/stream/client/auth.rb
@@ -0,0 +1,74 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class Auth < State
+        NS        = NAMESPACES[:sasl]
+        MECHANISM = 'mechanism'.freeze
+        AUTH      = 'auth'.freeze
+        PLAIN     = 'PLAIN'.freeze
+        EXTERNAL  = 'EXTERNAL'.freeze
+        SUCCESS   = %Q{<success xmlns="#{NS}"/>}.freeze
+        MAX_AUTH_ATTEMPTS = 3
+
+        def initialize(stream, success=BindRestart)
+          super
+          @attempts = 0
+          @sasl = SASL.new(stream)
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless auth?(node)
+          if node.text.empty?
+            send_auth_fail(SaslErrors::MalformedRequest.new)
+          elsif stream.authentication_mechanisms.include?(node[MECHANISM])
+            case node[MECHANISM]
+            when PLAIN    then plain_auth(node)
+            when EXTERNAL then external_auth(node)
+            end
+          else
+            send_auth_fail(SaslErrors::InvalidMechanism.new)
+          end
+        end
+
+        private
+
+        def auth?(node)
+          node.name == AUTH && namespace(node) == NS
+        end
+
+        def plain_auth(node)
+          stream.user = @sasl.plain_auth(node.text)
+          send_auth_success
+        rescue => e
+          send_auth_fail(e)
+        end
+
+        def external_auth(node)
+          @sasl.external_auth(node.text)
+          send_auth_success
+        rescue => e
+          send_auth_fail(e)
+          stream.write('</stream:stream>')
+          stream.close_connection_after_writing
+        end
+
+        def send_auth_success
+          stream.write(SUCCESS)
+          stream.reset
+          advance
+        end
+
+        def send_auth_fail(condition)
+          @attempts += 1
+          if @attempts >= MAX_AUTH_ATTEMPTS
+            stream.error(StreamErrors::PolicyViolation.new("max authentication attempts exceeded"))
+          else
+            stream.error(condition)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/auth_restart.rb b/lib/vines/stream/client/auth_restart.rb
new file mode 100644
index 0000000..c53f53e
--- /dev/null
+++ b/lib/vines/stream/client/auth_restart.rb
@@ -0,0 +1,29 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class AuthRestart < State
+        def initialize(stream, success=Auth)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          doc = Document.new
+          features = doc.create_element('stream:features') do |el|
+            el << doc.create_element('mechanisms') do |parent|
+              parent.default_namespace = NAMESPACES[:sasl]
+              stream.authentication_mechanisms.each do |name|
+                parent << doc.create_element('mechanism', name)
+              end
+            end
+          end
+          stream.write(features)
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/bind.rb b/lib/vines/stream/client/bind.rb
new file mode 100644
index 0000000..c90a952
--- /dev/null
+++ b/lib/vines/stream/client/bind.rb
@@ -0,0 +1,64 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class Bind < State
+        NS = NAMESPACES[:bind]
+        MAX_ATTEMPTS = 5
+
+        def initialize(stream, success=Ready)
+          super
+          @attempts = 0
+        end
+
+        def node(node)
+          @attempts += 1
+          raise StreamErrors::NotAuthorized unless bind?(node)
+          raise StreamErrors::PolicyViolation.new('max bind attempts reached') if @attempts > MAX_ATTEMPTS
+          raise StanzaErrors::ResourceConstraint.new(node, 'wait') if resource_limit_reached?
+
+          stream.bind!(resource(node))
+          doc = Document.new
+          result = doc.create_element('iq', 'id' => node['id'], 'type' => 'result') do |el|
+            el << doc.create_element('bind') do |bind|
+              bind.default_namespace = NS
+              bind << doc.create_element('jid', stream.user.jid.to_s)
+            end
+          end
+          stream.write(result)
+          advance
+        end
+
+        private
+
+        def bind?(node)
+          node.name == 'iq' && node['type'] == 'set' && node.xpath('ns:bind', 'ns' => NS).any?
+        end
+
+        def resource(node)
+          el = node.xpath('ns:bind/ns:resource', 'ns' => NS).first
+          resource = el ? el.text.strip : ''
+          generate = resource.empty? || !resource_valid?(resource) || resource_used?(resource)
+          generate ? Kit.uuid : resource
+        end
+
+        def resource_limit_reached?
+          used = stream.connected_resources(stream.user.jid.bare).size
+          used >= stream.max_resources_per_account
+        end
+
+        def resource_used?(resource)
+          stream.available_resources(stream.user.jid).any? do |c|
+            c.user.jid.resource == resource
+          end
+        end
+
+        def resource_valid?(resource)
+          jid = stream.user.jid
+          JID.new(jid.node, jid.domain, resource) rescue false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/bind_restart.rb b/lib/vines/stream/client/bind_restart.rb
new file mode 100644
index 0000000..3aa6445
--- /dev/null
+++ b/lib/vines/stream/client/bind_restart.rb
@@ -0,0 +1,30 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class BindRestart < State
+        def initialize(stream, success=Bind)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          doc = Document.new
+          features = doc.create_element('stream:features') do |el|
+            # Session support is deprecated, but like we do it for Adium
+            # in the iq-session-stanza we have to serve the feature for Xabber.
+            # Otherwise it will disconnect after authentication!
+            el << doc.create_element('session', 'xmlns' => NAMESPACES[:session]) do |session|
+              session << doc.create_element('optional')
+            end
+            el << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
+          end
+          stream.write(features)
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/closed.rb b/lib/vines/stream/client/closed.rb
new file mode 100644
index 0000000..3882ecf
--- /dev/null
+++ b/lib/vines/stream/client/closed.rb
@@ -0,0 +1,13 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class Closed < State
+        def node(node)
+          # ignore data received after close_connection
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/ready.rb b/lib/vines/stream/client/ready.rb
new file mode 100644
index 0000000..8dd5d82
--- /dev/null
+++ b/lib/vines/stream/client/ready.rb
@@ -0,0 +1,17 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class Ready < State
+        def node(node)
+          stanza = to_stanza(node)
+          raise StreamErrors::UnsupportedStanzaType unless stanza
+          stanza.validate_to
+          stanza.validate_from
+          stanza.process
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/session.rb b/lib/vines/stream/client/session.rb
new file mode 100644
index 0000000..ac8456b
--- /dev/null
+++ b/lib/vines/stream/client/session.rb
@@ -0,0 +1,210 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      # A Session tracks the state of a client stream over its lifetime from
+      # negotiation to processing stanzas to shutdown. By disconnecting the
+      # stream's state from the stream, we can allow multiple TCP connections
+      # to access one logical session (e.g. HTTP streams).
+      class Session
+        include Comparable
+
+        attr_accessor :domain, :user
+        attr_reader   :id, :last_broadcast_presence, :state
+
+        def initialize(stream)
+          @stream = stream
+          @id = Kit.uuid
+          @config = stream.config
+          @state = Client::Start.new(stream)
+          @available = false
+          @domain = nil
+          @last_broadcast_presence = nil
+          @requested_roster = false
+          @unbound = false
+          @user = nil
+        end
+
+        def <=>(session)
+          session.is_a?(Session) ? self.id <=> session.id : nil
+        end
+
+        alias :eql? :==
+
+        def hash
+          @id.hash
+        end
+
+        def advance(state)
+          @state = state
+        end
+
+        # Returns true if this client has properly authenticated with
+        # the server.
+        def authenticated?
+          !@user.nil?
+        end
+
+        # Notify the session that the client has sent an initial presence
+        # broadcast and is now considered to be an "available" resource.
+        # Available resources are sent presence subscription stanzas.
+        def available!
+          @available = true
+          save_to_cluster
+        end
+
+        # An available resource has sent initial presence and can
+        # receive presence subscription requests.
+        def available?
+          @available && connected?
+        end
+
+        # Complete resource binding with the given resource name, provided by the
+        # client or generated by the server. Once resource binding is completed,
+        # the stream is considered to be "connected" and ready for traffic.
+        def bind!(resource)
+          @user.jid.resource = resource
+          router << self
+          save_to_cluster
+        end
+
+        # A connected resource has authenticated and bound a resource
+        # identifier.
+        def connected?
+          !@unbound && authenticated? && !@user.jid.bare?
+        end
+
+        # An interested resource has requested its roster and can
+        # receive roster pushes.
+        def interested?
+          @requested_roster && connected?
+        end
+
+        def last_broadcast_presence=(node)
+          @last_broadcast_presence = node
+          save_to_cluster
+        end
+
+        def ready?
+          @state.class == Client::Ready
+        end
+
+        # Notify the session that the client has requested its roster and is now
+        # considered to be an "interested" resource. Interested resources are sent
+        # roster pushes when changes are made to their contacts.
+        def requested_roster!
+          @requested_roster = true
+          save_to_cluster
+        end
+
+        def stream_type
+          :client
+        end
+
+        def write(data)
+          @stream.write(data)
+        end
+
+        # Called by the stream when it's disconnected from the client. The stream
+        # passes itself to this method in case multiple streams are accessing this
+        # session (e.g. BOSH/HTTP).
+        def unbind!(stream)
+          router.delete(self)
+          delete_from_cluster
+          unsubscribe_pubsub
+          @unbound = true
+          @available = false
+          broadcast_unavailable
+        end
+
+        # Returns streams for available resources to which this user
+        # has successfully subscribed.
+        def available_subscribed_to_resources
+          subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
+          router.available_resources(subscribed, @user.jid)
+        end
+
+        # Returns streams for available resources that are subscribed
+        # to this user's presence updates.
+        def available_subscribers
+          subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
+          router.available_resources(subscribed, @user.jid)
+        end
+
+        # Returns contacts hosted at remote servers to which this user has
+        # successfully subscribed.
+        def remote_subscribed_to_contacts
+          @user.subscribed_to_contacts.reject do |c|
+            @config.local_jid?(c.jid)
+          end
+        end
+
+        # Returns contacts hosted at remote servers that are subscribed
+        # to this user's presence updates.
+        def remote_subscribers(to=nil)
+          jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
+          @user.subscribed_from_contacts.reject do |c|
+            @config.local_jid?(c.jid) || (jid && c.jid.bare != jid)
+          end
+        end
+
+        private
+
+        def broadcast_unavailable
+          return unless authenticated?
+          Fiber.new do
+            broadcast(unavailable, available_subscribers)
+            broadcast(unavailable, router.available_resources(@user.jid, @user.jid))
+            remote_subscribers.each do |contact|
+              node = unavailable
+              node['to'] = contact.jid.bare.to_s
+              router.route(node) rescue nil # ignore RemoteServerNotFound
+            end
+          end.resume
+        end
+
+        def unavailable
+          doc = Nokogiri::XML::Document.new
+          doc.create_element('presence',
+            'from' => @user.jid.to_s,
+            'type' => 'unavailable')
+        end
+
+        def broadcast(stanza, recipients)
+          recipients.each do |recipient|
+            stanza['to'] = recipient.user.jid.to_s
+            recipient.write(stanza)
+          end
+        end
+
+        def router
+          @config.router
+        end
+
+        def save_to_cluster
+          if @config.cluster?
+            @config.cluster.save_session(@user.jid, to_hash)
+          end
+        end
+
+        def delete_from_cluster
+          if connected? && @config.cluster?
+            @config.cluster.delete_session(@user.jid)
+          end
+        end
+
+        def unsubscribe_pubsub
+          if connected?
+            @config.vhost(@user.jid.domain).unsubscribe_pubsub(@user.jid)
+          end
+        end
+
+        def to_hash
+          presence = @last_broadcast_presence ? @last_broadcast_presence.to_s : nil
+          {available: @available, interested: @requested_roster, presence: presence.to_s}
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/start.rb b/lib/vines/stream/client/start.rb
new file mode 100644
index 0000000..6f18c30
--- /dev/null
+++ b/lib/vines/stream/client/start.rb
@@ -0,0 +1,27 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class Start < State
+        def initialize(stream, success=TLS)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          doc = Document.new
+          features = doc.create_element('stream:features') do |el|
+            el << doc.create_element('starttls') do |tls|
+              tls.default_namespace = NAMESPACES[:tls]
+              tls << doc.create_element('required')
+            end
+          end
+          stream.write(features)
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/client/tls.rb b/lib/vines/stream/client/tls.rb
new file mode 100644
index 0000000..bc9c371
--- /dev/null
+++ b/lib/vines/stream/client/tls.rb
@@ -0,0 +1,38 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Client
+      class TLS < State
+        NS = NAMESPACES[:tls]
+        PROCEED  = %Q{<proceed xmlns="#{NS}"/>}.freeze
+        FAILURE  = %Q{<failure xmlns="#{NS}"/>}.freeze
+        STARTTLS = 'starttls'.freeze
+
+        def initialize(stream, success=AuthRestart)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless starttls?(node)
+          if stream.encrypt?
+            stream.write(PROCEED)
+            stream.encrypt
+            stream.reset
+            advance
+          else
+            stream.write(FAILURE)
+            stream.write('</stream:stream>')
+            stream.close_connection_after_writing
+          end
+        end
+
+        private
+
+        def starttls?(node)
+          node.name == STARTTLS && namespace(node) == NS
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/component.rb b/lib/vines/stream/component.rb
new file mode 100644
index 0000000..9978483
--- /dev/null
+++ b/lib/vines/stream/component.rb
@@ -0,0 +1,58 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+
+    # Implements the XMPP protocol for trusted, external component (XEP-0114)
+    # streams. This serves connected streams using the jabber:component:accept
+    # namespace.
+    class Component < Stream
+      attr_reader :remote_domain
+
+      def initialize(config)
+        super
+        @remote_domain = nil
+        @stream_id = Kit.uuid
+        advance(Start.new(self))
+      end
+
+      def max_stanza_size
+        config[:component].max_stanza_size
+      end
+
+      def ready?
+        state.class == Component::Ready
+      end
+
+      def stream_type
+        :component
+      end
+
+      def start(node)
+        @remote_domain = node['to']
+        send_stream_header
+        raise StreamErrors::ImproperAddressing unless valid_address?(@remote_domain)
+        raise StreamErrors::HostUnknown unless config.component?(@remote_domain)
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:component]
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
+      end
+
+      def secret
+        password = config.component_password(@remote_domain)
+        Digest::SHA1.hexdigest(@stream_id + password)
+      end
+
+      private
+
+      def send_stream_header
+        attrs = {
+          'xmlns'        => NAMESPACES[:component],
+          'xmlns:stream' => NAMESPACES[:stream],
+          'id'           => @stream_id,
+          'from'         => @remote_domain
+        }
+        write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/component/handshake.rb b/lib/vines/stream/component/handshake.rb
new file mode 100644
index 0000000..93948cc
--- /dev/null
+++ b/lib/vines/stream/component/handshake.rb
@@ -0,0 +1,26 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Component
+      class Handshake < State
+        def initialize(stream, success=Ready)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless handshake?(node)
+          stream.write('<handshake/>')
+          stream.router << stream
+          advance
+        end
+
+        private
+
+        def handshake?(node)
+          node.name == 'handshake' && node.text == stream.secret
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/component/ready.rb b/lib/vines/stream/component/ready.rb
new file mode 100644
index 0000000..fe2eac9
--- /dev/null
+++ b/lib/vines/stream/component/ready.rb
@@ -0,0 +1,23 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Component
+      class Ready < State
+        def node(node)
+          stanza = to_stanza(node)
+          raise StreamErrors::UnsupportedStanzaType unless stanza
+          to, from = stanza.validate_to, stanza.validate_from
+          raise StreamErrors::ImproperAddressing unless to && from
+          raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
+          stream.user = User.new(jid: from)
+          if stanza.local? || stanza.to_pubsub_domain?
+            stanza.process
+          else
+            stanza.route
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/component/start.rb b/lib/vines/stream/component/start.rb
new file mode 100644
index 0000000..a948623
--- /dev/null
+++ b/lib/vines/stream/component/start.rb
@@ -0,0 +1,19 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Component
+      class Start < State
+        def initialize(stream, success=Handshake)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http.rb b/lib/vines/stream/http.rb
new file mode 100644
index 0000000..f3b4bb9
--- /dev/null
+++ b/lib/vines/stream/http.rb
@@ -0,0 +1,185 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http < Client
+      attr_accessor :session
+
+      def initialize(config)
+        super
+        @session = Http::Session.new(self)
+      end
+
+      # Override Stream#create_parser to provide an HTTP parser rather than
+      # a Nokogiri XML parser.
+      #
+      # Returns nothing.
+      def create_parser
+        @parser = ::Http::Parser.new.tap do |parser|
+          body = ''
+          parser.on_body = proc {|data| body << data }
+          parser.on_message_complete = proc do
+            process_request(Request.new(self, @parser, body))
+            body = ''
+          end
+        end
+      end
+
+      # If the session ID is valid, switch this stream's session to the new
+      # ID and return true. Some clients, like Google Chrome, reuse one stream
+      # for multiple sessions.
+      #
+      # sid - The String session ID.
+      #
+      # Returns true if the server previously distributed this SID to a client.
+      def valid_session?(sid)
+        if session = Sessions[sid]
+          @session = session
+        end
+        !!session
+      end
+
+      %w[max_stanza_size max_resources_per_account bind root].each do |name|
+        define_method name do |*args|
+          config[:http].send(name, *args)
+        end
+      end
+
+      def process_request(request)
+        if request.method == 'POST'
+          if request.path == self.bind && request.options?
+            request.reply_to_options
+          elsif request.path == self.bind
+            body = Nokogiri::XML(request.body).root
+            if session = Sessions[body['sid']]
+              @session = session
+            else
+              @session = Http::Session.new(self)
+            end
+            @session.request(request)
+            @nodes.push(body)
+          end
+        else
+          request.reply('It works!', 'text/plain')
+        end
+      end
+
+      # Alias the Stream#write method before overriding it so we can call
+      # it later from a Session instance.
+      alias :stream_write :write
+
+      # Override Stream#write to queue stanzas rather than immediately writing
+      # to the stream. Stanza responses must be paired with a queued request.
+      #
+      # If a request is not waiting, the written stanzas will buffer until they
+      # can be sent in the next response.
+      #
+      # data - The XML String or XML::Node to write to the HTTP socket.
+      #
+      # Returns nothing.
+      def write(data)
+        @session.write(data)
+      end
+
+      # Parse the one or more stanzas from a single body element. BOSH clients
+      # buffer stanzas sent in quick succession, and send them as a bundle, to
+      # save on the request/response cycle.
+      #
+      # TODO This parses the XML again just to strip namespaces. Figure out
+      # Nokogiri namespace handling instead.
+      #
+      # body - The XML::Node containing the BOSH `body` element.
+      #
+      # Returns an Array of XML::Node stanzas.
+      def parse_body(body)
+        body.namespace = nil
+        body.elements.map do |node|
+          Nokogiri::XML(node.to_s.sub(' xmlns="jabber:client"', '')).root
+        end
+      end
+
+      def start(node)
+        domain, type, hold, wait, rid = %w[to content hold wait rid].map {|a| (node[a] || '').strip }
+        version = node.attribute_with_ns('version', NAMESPACES[:bosh]).value rescue nil
+
+        @session.inactivity = 20
+        @session.domain = domain
+        @session.content_type = type unless type.empty?
+        @session.hold = hold.to_i unless hold.empty?
+        @session.wait = wait.to_i unless wait.empty?
+
+        raise StreamErrors::UndefinedCondition.new('rid required') if rid.empty?
+        raise StreamErrors::UnsupportedVersion unless version == '1.0'
+        raise StreamErrors::ImproperAddressing unless valid_address?(domain)
+        raise StreamErrors::HostUnknown unless config.vhost?(domain)
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:http_bind]
+
+        Sessions[@session.id] = @session
+        send_stream_header
+      end
+
+      def terminate
+        doc = Nokogiri::XML::Document.new
+        node = doc.create_element('body',
+          'type'  => 'terminate',
+          'xmlns' => NAMESPACES[:http_bind])
+        @session.reply(node)
+        close_stream
+      end
+
+      private
+
+      def send_stream_header
+        doc = Nokogiri::XML::Document.new
+        node = doc.create_element('body',
+          'charsets'     => 'UTF-8',
+          'from'         => @session.domain,
+          'hold'         => @session.hold,
+          'inactivity'   => @session.inactivity,
+          'polling'      => '5',
+          'requests'     => '2',
+          'sid'          => @session.id,
+          'ver'          => '1.6',
+          'wait'         => @session.wait,
+          'xmpp:version' => '1.0',
+          'xmlns'        => NAMESPACES[:http_bind],
+          'xmlns:xmpp'   => NAMESPACES[:bosh],
+          'xmlns:stream' => NAMESPACES[:stream])
+
+        node << doc.create_element('stream:features') do |el|
+          el << doc.create_element('mechanisms') do |mechanisms|
+            mechanisms.default_namespace = NAMESPACES[:sasl]
+            mechanisms << doc.create_element('mechanism', 'PLAIN')
+          end
+        end
+        @session.reply(node)
+      end
+
+      # Override Stream#send_stream_error to wrap the error XML in a BOSH
+      # terminate body tag.
+      #
+      # e - The StreamError that caused the connection to close.
+      #
+      # Returns nothing.
+      def send_stream_error(e)
+        doc = Nokogiri::XML::Document.new
+        node = doc.create_element('body',
+          'condition'    => 'remote-stream-error',
+          'type'         => 'terminate',
+          'xmlns'        => NAMESPACES[:http_bind],
+          'xmlns:stream' => NAMESPACES[:stream])
+        node.inner_html = e.to_xml
+        @session.reply(node)
+      end
+
+      # Override Stream#close_stream to simply close the connection without
+      # writing a closing stream tag.
+      #
+      # Returns nothing.
+      def close_stream
+        close_connection_after_writing
+        @session.close
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/auth.rb b/lib/vines/stream/http/auth.rb
new file mode 100644
index 0000000..cbf1aa7
--- /dev/null
+++ b/lib/vines/stream/http/auth.rb
@@ -0,0 +1,22 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Auth < Client::Auth
+        def initialize(stream, success=BindRestart)
+          super
+        end
+
+        def node(node)
+          unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
+            raise StreamErrors::NotAuthorized
+          end
+          nodes = stream.parse_body(node)
+          raise StreamErrors::NotAuthorized unless nodes.size == 1
+          super(nodes.first)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/bind.rb b/lib/vines/stream/http/bind.rb
new file mode 100644
index 0000000..8c6a4a7
--- /dev/null
+++ b/lib/vines/stream/http/bind.rb
@@ -0,0 +1,32 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Bind < Client::Bind
+        FEATURES = %Q{<stream:features xmlns:stream="#{NAMESPACES[:stream]}"/>}.freeze
+
+        def initialize(stream, success=Ready)
+          super
+        end
+
+        def node(node)
+          unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
+            raise StreamErrors::NotAuthorized
+          end
+          nodes = stream.parse_body(node)
+          raise StreamErrors::NotAuthorized unless nodes.size == 1
+          super(nodes.first)
+        end
+
+        private
+
+        # Override Client::Bind#send_empty_features to properly namespace the
+        # empty features element.
+        def send_empty_features
+          stream.write(FEATURES)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/bind_restart.rb b/lib/vines/stream/http/bind_restart.rb
new file mode 100644
index 0000000..7e73073
--- /dev/null
+++ b/lib/vines/stream/http/bind_restart.rb
@@ -0,0 +1,37 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class BindRestart < State
+        def initialize(stream, success=Bind)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless restart?(node)
+
+          doc = Document.new
+          body = doc.create_element('body') do |el|
+            el.add_namespace(nil, NAMESPACES[:http_bind])
+            el.add_namespace('stream', NAMESPACES[:stream])
+            el << doc.create_element('stream:features') do |features|
+              features << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
+            end
+          end
+          stream.reply(body)
+          advance
+        end
+
+        private
+
+        def restart?(node)
+          session = stream.valid_session?(node['sid'])
+          restart = node.attribute_with_ns('restart', NAMESPACES[:bosh]).value rescue nil
+          domain  = node['to'] == stream.domain
+          session && body?(node) && domain && restart == 'true' && node['rid']
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/ready.rb b/lib/vines/stream/http/ready.rb
new file mode 100644
index 0000000..c379319
--- /dev/null
+++ b/lib/vines/stream/http/ready.rb
@@ -0,0 +1,29 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Ready < Client::Ready
+        RID, SID, TYPE, TERMINATE = %w[rid sid type terminate].map {|s| s.freeze }
+
+        def node(node)
+          unless stream.valid_session?(node[SID]) && body?(node) && node[RID]
+            raise StreamErrors::NotAuthorized
+          end
+          stream.parse_body(node).each do |child|
+            begin
+              super(child)
+            rescue StanzaError => e
+              stream.error(e)
+            end
+          end
+          stream.terminate if terminate?(node)
+        end
+
+        def terminate?(node)
+          node[TYPE] == TERMINATE
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/request.rb b/lib/vines/stream/http/request.rb
new file mode 100644
index 0000000..70f02f3
--- /dev/null
+++ b/lib/vines/stream/http/request.rb
@@ -0,0 +1,193 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Request
+        BUF_SIZE      = 1024
+        MODIFIED      = '%a, %d %b %Y %H:%M:%S GMT'.freeze
+        MOVED         = 'Moved Permanently'.freeze
+        NOT_FOUND     = 'Not Found'.freeze
+        NOT_MODIFIED  = 'Not Modified'.freeze
+        IF_MODIFIED   = 'If-Modified-Since'.freeze
+        TEXT_PLAIN    = 'text/plain'.freeze
+        OPTIONS       = 'OPTIONS'.freeze
+        CONTENT_TYPES = {
+          'html'     => 'text/html; charset="utf-8"',
+          'js'       => 'application/javascript; charset="utf-8"',
+          'css'      => 'text/css',
+          'png'      => 'image/png',
+          'jpg'      => 'image/jpeg',
+          'jpeg'     => 'image/jpeg',
+          'gif'      => 'image/gif',
+          'manifest' => 'text/cache-manifest'
+        }.freeze
+
+        attr_reader :stream, :body, :headers, :method, :path, :url, :query
+
+        # Create a new request parsed from an HTTP client connection. We'll try
+        # to keep this request open until there are stanzas available to send
+        # as a response.
+        #
+        # stream - The Stream::Http client connection that received the request.
+        # parser - The Http::Parser that parsed the HTTP request.
+        # body   - The String request body.
+        def initialize(stream, parser, body)
+          uri       = URI(parser.request_url)
+          @stream   = stream
+          @body     = body
+          @headers  = parser.headers
+          @method   = parser.http_method
+          @url      = parser.request_url
+          @path     = uri.path
+          @query    = uri.query
+          @received = Time.now
+        end
+
+        # Return the number of seconds since this request was received.
+        def age
+          Time.now - @received
+        end
+
+        # Write the requested file to the client out of the given document root
+        # directory. Take care to prevent directory traversal attacks with paths
+        # like ../../../etc/passwd. Use the If-Modified-Since request header
+        # to implement caching.
+        #
+        # Returns nothing.
+        def reply_with_file(dir)
+          path = File.expand_path(File.join(dir, @path))
+
+          # Redirect requests missing a slash so relative links work.
+          if File.directory?(path) && !@path.end_with?('/')
+            send_status(301, MOVED, "Location: #{redirect_uri}")
+            return
+          end
+
+          path = File.join(path, 'index.html') if File.directory?(path)
+
+          if path.start_with?(dir) && File.exist?(path)
+            modified?(path) ? send_file(path) : send_status(304, NOT_MODIFIED)
+          else
+            missing = File.join(dir, '404.html')
+            if File.exist?(missing)
+              send_file(missing, 404, NOT_FOUND)
+            else
+              send_status(404, NOT_FOUND)
+            end
+          end
+        end
+
+        # Send an HTTP 200 OK response wrapping the XMPP node content back
+        # to the client.
+        #
+        # Returns nothing.
+        def reply(node, content_type)
+          body = node.to_s
+          header = [
+            "HTTP/1.1 200 OK",
+            "Access-Control-Allow-Origin: *",
+            "Content-Type: #{content_type}",
+            "Content-Length: #{body.bytesize}",
+            vroute_cookie
+          ].compact.join("\r\n")
+          @stream.stream_write([header, body].join("\r\n\r\n"))
+        end
+
+        # Return true if the request method is OPTIONS, signaling a
+        # CORS preflight check.
+        def options?
+          @method == OPTIONS
+        end
+
+        # Send a 200 OK response, allowing any origin domain to connect to the
+        # server, in response to CORS preflight OPTIONS requests. This allows
+        # any web application using strophe.js to connect to our BOSH port.
+        #
+        # Returns nothing.
+        def reply_to_options
+          allow = @headers['Access-Control-Request-Headers']
+          headers = [
+            "Access-Control-Allow-Origin: *",
+            "Access-Control-Allow-Methods: POST, GET, OPTIONS",
+            "Access-Control-Allow-Headers: #{allow}",
+            "Access-Control-Max-Age: #{60 * 60 * 24 * 30}"
+          ]
+          send_status(200, 'OK', headers)
+        end
+
+        private
+
+        # Attempt to rebuild the full request URI from the Host header. If it
+        # wasn't sent by the client, just return the relative path that
+        # was requested. The Location response header must contain the fully
+        # qualified URI, but most browsers will accept relative paths as well.
+        #
+        # Returns the String URL.
+        def redirect_uri
+          host = headers['Host']
+          uri = "#{path}/"
+          uri = "#{uri}?#{query}" unless (query || '').empty?
+          uri = "http://#{host}#{uri}" if host
+          uri
+        end
+
+        # Return true if the file has been modified since the client last
+        # requested it with the If-Modified-Since header.
+        def modified?(path)
+          @headers[IF_MODIFIED] != mtime(path)
+        end
+
+        def mtime(path)
+          File.mtime(path).utc.strftime(MODIFIED)
+        end
+
+        def send_status(status, message, *headers)
+          header = [
+            "HTTP/1.1 #{status} #{message}",
+            "Content-Length: 0",
+            *headers
+          ].join("\r\n")
+          @stream.stream_write("#{header}\r\n\r\n")
+        end
+
+        # Stream the contents of the file to the client in a 200 OK response.
+        # Send a Last-Modified response header so clients can send us an
+        # If-Modified-Since request header for caching.
+        #
+        # Returns nothing.
+        def send_file(path, status=200, message='OK')
+          header = [
+            "HTTP/1.1 #{status} #{message}",
+            "Content-Type: #{content_type(path)}",
+            "Content-Length: #{File.size(path)}",
+            "Last-Modified: #{mtime(path)}"
+          ].join("\r\n")
+          @stream.stream_write("#{header}\r\n\r\n")
+
+          File.open(path) do |file|
+            while (buf = file.read(BUF_SIZE)) != nil
+              @stream.stream_write(buf)
+            end
+          end
+        end
+
+        def content_type(path)
+          ext = File.extname(path).sub('.', '')
+          CONTENT_TYPES[ext] || TEXT_PLAIN
+        end
+
+        # Provide a vroute cookie in each response that uniquely identifies this
+        # HTTP server. Reverse proxy servers (nginx/apache) can use this cookie
+        # to implement sticky sessions. Return nil if vroute was not set in
+        # config.rb and no cookie should be sent.
+        #
+        # Returns a String cookie value or nil if disabled.
+        def vroute_cookie
+          route = @stream.config[:http].vroute
+          route ? "Set-Cookie: vroute=#{route}; path=/; HttpOnly" : nil
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/session.rb b/lib/vines/stream/http/session.rb
new file mode 100644
index 0000000..a697900
--- /dev/null
+++ b/lib/vines/stream/http/session.rb
@@ -0,0 +1,128 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Session < Client::Session
+        include Nokogiri::XML
+
+        attr_accessor :content_type, :hold, :inactivity, :wait
+
+        CONTENT_TYPE = 'text/xml; charset=utf-8'.freeze
+
+        def initialize(stream)
+          super
+          @state = Http::Start.new(stream)
+          @inactivity, @wait, @hold = 20, 60, 1
+          @replied = Time.now
+          @requests, @responses = [], []
+          @content_type = CONTENT_TYPE
+        end
+
+        def close
+          Sessions.delete(@id)
+          router.delete(self)
+          delete_from_cluster
+          unsubscribe_pubsub
+          @requests.each {|req| req.stream.close_connection }
+          @requests.clear
+          @responses.clear
+          @state = Client::Closed.new(nil)
+          @unbound = true
+          @available = false
+          broadcast_unavailable
+        end
+
+        def ready?
+          @state.class == Http::Ready
+        end
+
+        def requests
+          @requests.clone
+        end
+
+        def expired?
+          respond_to_expired_requests
+          @requests.empty? && (Time.now - @replied > @inactivity)
+        end
+
+        # Resume this session from its most recent state with a new client
+        # stream and incoming node.
+        def resume(stream, node)
+          stream.session.requests.each do |req|
+            request(req)
+          end
+          stream.session = self
+          @state.stream = stream
+          @state.node(node)
+        end
+
+        def request(request)
+          if @responses.any?
+            request.reply(wrap_body(@responses.join), @content_type)
+            @replied = Time.now
+            @responses.clear
+          else
+            while @requests.size >= @hold
+              @requests.shift.reply(wrap_body(''), @content_type)
+              @replied = Time.now
+            end
+            @requests << request
+          end
+        end
+
+        # Send an HTTP 200 OK response wrapping the XMPP node content back
+        # to the client.
+        #
+        # node - The XML::Node to send to the client.
+        #
+        # Returns nothing.
+        def reply(node)
+          if request = @requests.shift
+            request.reply(node, @content_type)
+            @replied = Time.now
+          end
+        end
+
+        # Write the XMPP node to the client stream after wrapping it in a BOSH
+        # body tag. If there's a waiting request, the node is written
+        # immediately. If not, it's queued until the next request arrives.
+        #
+        # data - The XML String or XML::Node to send in the next HTTP response.
+        #
+        # Returns nothing.
+        def write(node)
+          if request = @requests.shift
+            request.reply(wrap_body(node), @content_type)
+            @replied = Time.now
+          else
+            @responses << node.to_s
+          end
+        end
+
+        def unbind!(stream)
+          @requests.reject! {|req| req.stream == stream }
+        end
+
+        private
+
+        def respond_to_expired_requests
+          expired = @requests.select {|req| req.age > @wait }
+          expired.each do |request|
+            request.reply(wrap_body(''), @content_type)
+            @requests.delete(request)
+            @replied = Time.now
+          end
+        end
+
+        def wrap_body(data)
+          doc = Document.new
+          doc.create_element('body') do |node|
+            node.add_namespace(nil, NAMESPACES[:http_bind])
+            node.inner_html = data.to_s
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/http/sessions.rb b/lib/vines/stream/http/sessions.rb
new file mode 100644
index 0000000..d31f8fe
--- /dev/null
+++ b/lib/vines/stream/http/sessions.rb
@@ -0,0 +1,65 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      # Sessions is a cache of Http::Session objects for transient HTTP
+      # connections. The cache is monitored for expired client connections.
+      class Sessions
+        include Vines::Log
+
+        @@instance = nil
+        def self.instance
+          @@instance ||= self.new
+        end
+
+        def self.[](sid)
+          instance[sid]
+        end
+
+        def self.[]=(sid, session)
+          instance[sid] = session
+        end
+
+        def self.delete(sid)
+          instance.delete(sid)
+        end
+
+        def initialize
+          @sessions = {}
+          start_timer
+        end
+
+        def []=(sid, session)
+          @sessions[sid] = session
+        end
+
+        def [](sid)
+          @sessions[sid]
+        end
+
+        def delete(sid)
+          @sessions.delete(sid)
+        end
+
+        private
+
+        # Check for expired clients to cleanup every second.
+        def start_timer
+          @timer ||= EventMachine::PeriodicTimer.new(1) { cleanup }
+        end
+
+        # Remove cached information for all expired connections. An expired
+        # HTTP client is one that has no queued requests and has had no activity
+        # for over 20 seconds.
+        def cleanup
+          @sessions.each_value do |session|
+            session.close if session.expired?
+          end
+        rescue => e
+          log.error("Expired session cleanup failed: #{e}")
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/vines/stream/http/start.rb b/lib/vines/stream/http/start.rb
new file mode 100644
index 0000000..8db2622
--- /dev/null
+++ b/lib/vines/stream/http/start.rb
@@ -0,0 +1,23 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Http
+      class Start < State
+        def initialize(stream, success=Auth)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless body?(node)
+          if session = Sessions[node['sid']]
+            session.resume(stream, node)
+          else
+            stream.start(node)
+            advance
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/parser.rb b/lib/vines/stream/parser.rb
new file mode 100644
index 0000000..a59aded
--- /dev/null
+++ b/lib/vines/stream/parser.rb
@@ -0,0 +1,79 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Parser < Nokogiri::XML::SAX::Document
+      include Nokogiri::XML
+      STREAM_NAME = 'stream'.freeze
+      STREAM_URI  = 'http://etherx.jabber.org/streams'.freeze
+      IGNORE = NAMESPACES.values_at(:client, :component, :server)
+
+      def initialize(&block)
+        @listeners, @node = Hash.new {|h, k| h[k] = []}, nil
+        @parser = Nokogiri::XML::SAX::PushParser.new(self)
+        instance_eval(&block) if block
+      end
+
+      [:stream_open, :stream_close, :stanza].each do |name|
+        define_method(name) do |&block|
+          @listeners[name] << block
+        end
+      end
+
+      def <<(data)
+        @parser << data
+        self
+      end
+
+      def start_element_namespace(name, attrs=[], prefix=nil, uri=nil, ns=[])
+        el = node(name, attrs, prefix, uri, ns)
+        if stream?(name, uri)
+          notify(:stream_open, el)
+        else
+          @node << el if @node
+          @node = el
+        end
+      end
+
+      def end_element_namespace(name, prefix=nil, uri=nil)
+        if stream?(name, uri)
+          notify(:stream_close)
+        elsif @node.parent != @node.document
+          @node = @node.parent
+        else
+          notify(:stanza, @node)
+          @node = nil
+        end
+      end
+
+      def characters(chars)
+        @node << Text.new(chars, @node.document) if @node
+      end
+      alias :cdata_block :characters
+
+      private
+
+      def notify(msg, node=nil)
+        @listeners[msg].each do |b|
+          (node ? b.call(node) : b.call) rescue nil
+        end
+      end
+
+      def stream?(name, uri)
+        name == STREAM_NAME && uri == STREAM_URI
+      end
+
+      def node(name, attrs=[], prefix=nil, uri=nil, ns=[])
+        ignore = stream?(name, uri) ? [] : IGNORE
+        doc = @node ? @node.document : Document.new
+        node = doc.create_element(name) do |node|
+          attrs.each {|attr| node[attr.localname] = attr.value }
+          ns.each {|prefix, uri| node.add_namespace(prefix, uri) unless ignore.include?(uri) }
+          doc << node unless @node
+        end
+        node.namespace = node.add_namespace(prefix, uri) unless ignore.include?(uri)
+        node
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/sasl.rb b/lib/vines/stream/sasl.rb
new file mode 100644
index 0000000..2f46d02
--- /dev/null
+++ b/lib/vines/stream/sasl.rb
@@ -0,0 +1,128 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    # Provides plain (username/password) and external (TLS certificate) SASL
+    # authentication to client and server streams.
+    class SASL
+      include Vines::Log
+      EMPTY = '='.freeze
+
+      def initialize(stream)
+        @stream = stream
+      end
+
+      # Authenticate server-to-server streams, comparing their domain to their
+      # SSL certificate.
+      #
+      #   http://xmpp.org/extensions/xep-0178.html#s2s
+      #
+      # encoded - The Base64 encoded remote domain name String sent by the
+      #           server stream.
+      #
+      # Returns true if the Base64 encoded domain matches the TLS certificate
+      #   presented earlier in stream negotiation.
+      #
+      # Raises a SaslError if authentication failed.
+      def external_auth(encoded)
+        unless encoded == EMPTY
+          authzid = decode64(encoded)
+          matches_from = (authzid == @stream.remote_domain)
+          raise SaslErrors::InvalidAuthzid unless matches_from
+        end
+        matches_from = @stream.cert_domain_matches?(@stream.remote_domain)
+        matches_from or raise SaslErrors::NotAuthorized
+      end
+
+      # Authenticate client-to-server streams using a username and password.
+      #
+      # encoded - The Base64 encoded jid and password String sent by the
+      #           client stream.
+      #
+      # Returns the authenticated User or raises SaslError if authentication failed.
+      def plain_auth(encoded)
+        jid, password = decode_credentials(encoded)
+        user = authenticate(jid, password)
+        user or raise SaslErrors::NotAuthorized
+      end
+
+      private
+
+      # Storage backends should not raise errors, but if an unexpected error
+      # occurs during authentication, convert it to a temporary-auth-failure.
+      #
+      # jid      - The user's jid String.
+      # password - The String password.
+      #
+      # Returns the authenticated User or nil if authentication failed.
+      #
+      # Raises TemoraryAuthFailure if the storage system failed.
+      def authenticate(jid, password)
+        log.info("Authenticating user: %s" % jid)
+        @stream.storage.authenticate(jid, password).tap do |user|
+          log.info("Authentication succeeded: %s" % user.jid) if user
+        end
+      rescue => e
+        log.error("Failed to authenticate: #{e.to_s}")
+        raise SaslErrors::TemporaryAuthFailure
+      end
+
+      # Return the JID and password decoded from the Base64 encoded SASL PLAIN
+      # credentials formatted as authzid\0authcid\0password.
+      #
+      #   http://tools.ietf.org/html/rfc6120#section-6.3.8
+      #   http://tools.ietf.org/html/rfc4616
+      #
+      # encoded - The Base64 encoded String from which to extract jid and password.
+      #
+      # Returns an Array of jid String and password String.
+      def decode_credentials(encoded)
+        authzid, node, password = decode64(encoded).split("\x00")
+        raise SaslErrors::NotAuthorized if node.nil? || node.empty? || password.nil? || password.empty?
+        jid = JID.new(node, @stream.domain) rescue (raise SaslErrors::NotAuthorized)
+        validate_authzid!(authzid, jid)
+        [jid, password]
+      end
+
+      # An optional SASL authzid allows a user to authenticate with one
+      # user name and password and then have their connection authorized as a
+      # different ID (the authzid). We don't support that, so raise an error if
+      # the authzid is provided and different than the authcid.
+      #
+      # Most clients don't send an authzid at all because it's optional and not
+      # widely supported. However, Strophe and Blather send a bare JID, in
+      # compliance with RFC 6120, but Smack sends just the user name as the
+      # authzid. So, take care to handle non-compliant clients here.
+      #
+      #   http://tools.ietf.org/html/rfc6120#section-6.3.8
+      #
+      # authzid - The authzid String (may be nil).
+      # jid     - The username String.
+      #
+      # Returns nothing.
+      def validate_authzid!(authzid, jid)
+        return if authzid.nil? || authzid.empty?
+        authzid.downcase!
+        smack = authzid == jid.node
+        compliant = authzid == jid.to_s
+        raise SaslErrors::InvalidAuthzid unless compliant || smack
+      end
+
+      # Decode the Base64 encoded string, raising an error for invalid data.
+      #
+      #   http://tools.ietf.org/html/rfc6120#section-13.9.1
+      #
+      # encoded - The Base64 encoded String.
+      #
+      # Returns a UTF-8 String.
+      def decode64(encoded)
+        Base64.strict_decode64(encoded).tap do |decoded|
+          decoded.force_encoding(Encoding::UTF_8)
+          raise SaslErrors::IncorrectEncoding unless decoded.valid_encoding?
+        end
+      rescue
+        raise SaslErrors::IncorrectEncoding
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server.rb b/lib/vines/stream/server.rb
new file mode 100644
index 0000000..5ae4efe
--- /dev/null
+++ b/lib/vines/stream/server.rb
@@ -0,0 +1,207 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    # Implements the XMPP protocol for server-to-server (s2s) streams. This
+    # serves connected streams using the jabber:server namespace. This handles
+    # both accepting incoming s2s streams and initiating outbound s2s streams
+    # to other servers.
+    class Server < Stream
+      MECHANISMS, FROM, TO = %w(EXTERNAL from to).map(&:freeze)
+
+      # Starts the connection to the remote server. When the stream is
+      # connected and ready to send stanzas it will yield to the callback
+      # block. The callback is run on the EventMachine reactor thread. The
+      # yielded stream will be nil if the remote connection failed. We need to
+      # use a background thread to avoid blocking the server on DNS SRV
+      # lookups.
+      def self.start(config, to, from, dialback_verify_key = false, &callback)
+        op = proc do
+          Resolv::DNS.open do |dns|
+            dns.getresources("_xmpp-server._tcp.#{to}", Resolv::DNS::Resource::IN::SRV)
+          end.sort! {|a,b| a.priority == b.priority ? b.weight <=> a.weight : a.priority <=> b.priority }
+        end
+        cb = proc do |srv|
+          if srv.empty?
+            srv << {target: to, port: 5269}
+            class << srv.first
+              def method_missing(name); self[name]; end
+            end
+          end
+          Server.connect(config, to, from, srv, dialback_verify_key, callback)
+        end
+        EM.defer(proc { op.call rescue [] }, cb)
+      end
+
+      def self.connect(config, to, from, srv, dialback_verify_key = false, callback)
+        if srv.empty?
+          # fiber so storage calls work properly
+          Fiber.new { callback.call(nil) }.resume
+        else
+          begin
+            rr = srv.shift
+            opts = {to: to, from: from, srv: srv, dialback_verify_key: dialback_verify_key, callback: callback}
+            EM.connect(rr.target.to_s, rr.port, Server, config, opts)
+          rescue => e
+            connect(config, to, from, srv, dialback_verify_key, callback)
+          end
+        end
+      end
+
+      attr_reader   :domain
+      attr_accessor :remote_domain
+
+      def initialize(config, options={})
+        super(config)
+        @outbound_tls_required = false
+        @peer_trusted = nil
+        @connected = false
+        @remote_domain = options[:to]
+        @domain = options[:from]
+        @srv = options[:srv]
+        @dialback_verify_key = options[:dialback_verify_key]
+        @callback = options[:callback]
+        @outbound = @remote_domain && @domain
+        start = @outbound ? Outbound::Start.new(self) : Start.new(self)
+        advance(start)
+      end
+
+      def post_init
+        super
+        send_stream_header if @outbound
+      end
+
+      def max_stanza_size
+        config[:server].max_stanza_size
+      end
+
+      def ssl_verify_peer(pem)
+        @peer_trusted = @store.trusted?(pem)
+        true
+      end
+
+      def peer_trusted?
+        !@peer_trusted.nil? && @peer_trusted
+      end
+
+      def dialback_retry?
+        return false if @peer_trusted.nil? || @peer_trusted
+        true
+      end
+
+      def ssl_handshake_completed
+        @peer_trusted = cert_domain_matches?(@remote_domain) && peer_trusted?
+      end
+
+      def outbound_tls_required?
+        @outbound_tls_required
+      end
+
+      def outbound_tls_required(required)
+        @outbound_tls_required = required
+      end
+
+      # Return an array of allowed authentication mechanisms advertised as
+      # server stream features.
+      def authentication_mechanisms
+        MECHANISMS
+      end
+
+      def stream_type
+        :server
+      end
+
+      def unbind
+        super
+        if @outbound && !@connected
+          Server.connect(config, @remote_domain, @domain, @srv, @callback)
+        end
+      end
+
+      def vhost?(domain)
+        config.vhost?(domain)
+      end
+
+      def notify_connected
+        @connected = true
+        self.callback!
+        @callback = nil
+      end
+
+      def callback!
+        @callback.call(self) if @callback
+      end
+
+      def dialback_verify_key?
+        @dialback_verify_key
+      end
+
+      def ready?
+        state.class == Server::Ready
+      end
+
+      def authoritative_dialback(node)
+        stream = self
+        Server.start(stream.config, node[FROM], node[TO], true) do |authoritative|
+          if authoritative
+            # will be closed in outbound/authoritative.rb
+            authoritative.write("<db:verify from='#{node[TO]}' id='#{stream.id}' " \
+                                "to='#{node[FROM]}'>#{node.text}</db:verify>")
+          end
+        end
+        # We need to be discoverable for the dialback connection
+        router << stream
+      rescue StanzaErrors::RemoteServerNotFound
+        write("<db:result from='#{node[TO]}' to='#{node[FROM]}' " \
+              "type='error'><error type='cancel'><item-not-found " \
+              "xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></db:result>")
+        close_connection_after_writing
+      end
+
+      def start(node)
+        if @outbound then send_stream_header; return end
+        to, from = %w[to from].map {|a| node[a] }
+        @domain, @remote_domain = to, from unless @domain
+        send_stream_header
+        raise StreamErrors::NotAuthorized if domain_change?(to, from)
+        raise StreamErrors::ImproperAddressing unless valid_address?(@domain) && valid_address?(@remote_domain)
+        raise StreamErrors::HostUnknown unless config.vhost?(@domain) || config.pubsub?(@domain) || config.component?(@domain)
+        raise StreamErrors::NotAuthorized unless config.s2s?(@remote_domain) && config.allowed?(@domain, @remote_domain)
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:server]
+        raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
+      end
+
+      private
+
+      # The `to` and `from` domain addresses set on the initial stream header
+      # must not change during stream restarts. This prevents a server from
+      # authenticating as one domain, then sending stanzas from users in a
+      # different domain.
+      #
+      # to   - The String domain the other server thinks we are.
+      # from - The String domain the other server is asserting as its identity.
+      #
+      # Returns true if the other server is misbehaving and its connection
+      #   should be closed.
+      def domain_change?(to, from)
+        to != @domain || from != @remote_domain
+      end
+
+      def send_stream_header
+        stream_id = Kit.uuid
+        update_stream_id(stream_id)
+        attrs = {
+          'xmlns'        => NAMESPACES[:server],
+          'xmlns:stream' => NAMESPACES[:stream],
+          'xmlns:db'     => NAMESPACES[:legacy_dialback],
+          'xml:lang'     => 'en',
+          'id'           => stream_id,
+          'version'      => '1.0',
+          'from'         => @domain,
+          'to'           => @remote_domain,
+        }
+        write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/auth.rb b/lib/vines/stream/server/auth.rb
new file mode 100644
index 0000000..0198541
--- /dev/null
+++ b/lib/vines/stream/server/auth.rb
@@ -0,0 +1,30 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Auth < Client::Auth
+        RESULT = "result".freeze
+
+        def initialize(stream, success=FinalRestart)
+          super
+        end
+
+        def node(node)
+          if dialback_result?(node)
+            # open a new connection and verify the dialback key
+            stream.authoritative_dialback(node)
+          else
+            super
+          end
+        end
+
+        private
+
+        def dialback_result?(node)
+          node.name == RESULT && namespace(node) == NAMESPACES[:legacy_dialback]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/auth_method.rb b/lib/vines/stream/server/auth_method.rb
new file mode 100644
index 0000000..22331d3
--- /dev/null
+++ b/lib/vines/stream/server/auth_method.rb
@@ -0,0 +1,66 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class AuthMethod < State
+        VERIFY, VALID_TYPE, INVALID_TYPE = %w[verify valid invalid].map {|t| t.freeze }
+        STARTTLS, RESULT, FROM, TO = %w[starttls result from to].map {|s| s.freeze }
+        PROCEED  = %Q{<proceed xmlns="#{NAMESPACES[:tls]}"/>}.freeze
+        FAILURE  = %Q{<failure xmlns="#{NAMESPACES[:tls]}"/>}.freeze
+
+        def initialize(stream, success=AuthRestart)
+          super
+        end
+
+        def node(node)
+          if dialback_verify?(node)
+            id, from, to = %w[id from to].map {|a| node[a] }
+            key = node.text
+            outbound_stream = stream.router.stream_by_id(id)
+
+            unless outbound_stream && outbound_stream.state.is_a?(Stream::Server::Outbound::AuthDialbackResult)
+              stream.write(%Q{<db:verify from="#{to}" to=#{from} id=#{id} type="error"><error type="cancel"><item-not-found xmlns="#{NAMESPACES[:stanzas]}" /></error></db:verify>})
+              return
+            end
+
+            secret = outbound_stream.state.dialback_secret
+            type = Kit.dialback_key(secret, from, to, id) == key ? VALID_TYPE : INVALID_TYPE
+            stream.write(%Q{<db:verify from="#{to}" to="#{from}" id="#{id}" type="#{type}" />})
+            stream.close_connection_after_writing
+          elsif starttls?(node)
+            if stream.encrypt?
+              stream.write(PROCEED)
+              stream.encrypt
+              stream.reset
+              advance
+            else
+              stream.write(FAILURE)
+              stream.write('</stream:stream>')
+              stream.close_connection_after_writing
+            end
+          elsif dialback_result?(node)
+            # open a new connection and verify the dialback key
+            stream.authoritative_dialback(node)
+          else
+            raise StreamErrors::NotAuthorized
+          end
+        end
+
+        private
+
+        def starttls?(node)
+          node.name == STARTTLS && namespace(node) == NAMESPACES[:tls]
+        end
+
+        def dialback_verify?(node)
+          node.name == VERIFY && namespace(node) == NAMESPACES[:legacy_dialback]
+        end
+
+        def dialback_result?(node)
+          node.name == RESULT && namespace(node) == NAMESPACES[:legacy_dialback]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/auth_restart.rb b/lib/vines/stream/server/auth_restart.rb
new file mode 100644
index 0000000..f5703af
--- /dev/null
+++ b/lib/vines/stream/server/auth_restart.rb
@@ -0,0 +1,39 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class AuthRestart < State
+        def initialize(stream, success=Auth)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          doc = Document.new
+          features = doc.create_element('stream:features')
+          if stream.dialback_retry?
+            if stream.vhost.force_s2s_encryption?
+              stream.close_connection
+              return
+            end
+            @success = AuthMethod
+            features << doc.create_element('dialback') do |db|
+              db.default_namespace = NAMESPACES[:dialback]
+            end
+          else
+            features << doc.create_element('mechanisms') do |parent|
+              parent.default_namespace = NAMESPACES[:sasl]
+              stream.authentication_mechanisms.each do |name|
+                parent << doc.create_element('mechanism', name)
+              end
+            end
+          end
+          stream.write(features)
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/final_restart.rb b/lib/vines/stream/server/final_restart.rb
new file mode 100644
index 0000000..1c96f45
--- /dev/null
+++ b/lib/vines/stream/server/final_restart.rb
@@ -0,0 +1,21 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class FinalRestart < State
+        def initialize(stream, success=Ready)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          stream.write('<stream:features/>')
+          stream.router << stream
+          advance
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/auth.rb b/lib/vines/stream/server/outbound/auth.rb
new file mode 100644
index 0000000..b0567db
--- /dev/null
+++ b/lib/vines/stream/server/outbound/auth.rb
@@ -0,0 +1,65 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class Auth < State
+          REQUIRED = 'required'.freeze
+          FEATURES = 'features'.freeze
+
+          def initialize(stream, success=AuthDialbackResult)
+            super
+          end
+
+          def node(node)
+            # We have to remember tls_require for
+            # closing or restarting the stream
+            stream.outbound_tls_required(tls_required?(node))
+
+            if stream.dialback_verify_key?
+              @success = Authoritative
+              stream.callback!
+              advance
+            elsif dialback?(node)
+              secret = Kit.auth_token
+              dialback_key = Kit.dialback_key(secret, stream.remote_domain, stream.domain, stream.id)
+              stream.write("<db:result xmlns:db='#{NAMESPACES[:legacy_dialback]}' " \
+                "from='#{stream.domain}' to='#{stream.remote_domain}'>#{dialback_key}</db:result>")
+              advance
+              stream.router << stream # We need to be discoverable for the dialback connection
+              stream.state.dialback_secret = secret
+            elsif tls?(node)
+              @success = TLSResult
+              stream.write("<starttls xmlns='#{NAMESPACES[:tls]}'/>")
+              advance
+            else
+              raise StreamErrors::NotAuthorized
+            end
+          end
+
+          private
+
+          def tls_required?(node)
+            child = node.xpath('ns:starttls', 'ns' => NAMESPACES[:tls]).children.first
+            child && child.name == REQUIRED
+          end
+
+          def dialback?(node)
+            dialback = node.xpath('ns:dialback', 'ns' => NAMESPACES[:dialback]).any?
+            features?(node) && dialback && !tls_required?(node)
+          end
+
+          def tls?(node)
+            tls = node.xpath('ns:starttls', 'ns' => NAMESPACES[:tls]).any?
+            features?(node) && tls
+          end
+
+          def features?(node)
+            node.name == FEATURES && namespace(node) == NAMESPACES[:stream]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/auth_dialback_result.rb b/lib/vines/stream/server/outbound/auth_dialback_result.rb
new file mode 100644
index 0000000..0e2848b
--- /dev/null
+++ b/lib/vines/stream/server/outbound/auth_dialback_result.rb
@@ -0,0 +1,39 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class AuthDialbackResult < State
+          RESULT, VALID, INVALID, TYPE = %w[result valid invalid type].map {|s| s.freeze }
+
+          attr_accessor :dialback_secret
+
+          def initialize(stream, success=Ready)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless result?(node)
+
+            case node[TYPE]
+            when VALID
+              advance
+              stream.notify_connected
+            when INVALID
+              stream.close_connection
+            else
+              raise StreamErrors::NotAuthorized
+            end
+          end
+
+          private
+
+          def result?(node)
+            node.name == RESULT && namespace(node) == NAMESPACES[:legacy_dialback]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/auth_external.rb b/lib/vines/stream/server/outbound/auth_external.rb
new file mode 100644
index 0000000..1575fdb
--- /dev/null
+++ b/lib/vines/stream/server/outbound/auth_external.rb
@@ -0,0 +1,33 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class AuthExternal < State
+          def initialize(stream, success=AuthExternalResult)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless external?(node)
+            authzid = Base64.strict_encode64(stream.domain)
+            stream.write(%Q{<auth xmlns="#{NAMESPACES[:sasl]}" mechanism="EXTERNAL">#{authzid}</auth>})
+            advance
+          end
+
+          private
+
+          def external?(node)
+            external = node.xpath("ns:mechanisms/ns:mechanism[text()='EXTERNAL']", 'ns' => NAMESPACES[:sasl]).any?
+            features?(node) && external
+          end
+
+          def features?(node)
+            node.name == 'features' && namespace(node) == NAMESPACES[:stream]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/auth_external_result.rb b/lib/vines/stream/server/outbound/auth_external_result.rb
new file mode 100644
index 0000000..795c935
--- /dev/null
+++ b/lib/vines/stream/server/outbound/auth_external_result.rb
@@ -0,0 +1,32 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class AuthExternalResult < State
+          SUCCESS = 'success'.freeze
+          FAILURE = 'failure'.freeze
+
+          def initialize(stream, success=FinalRestart)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless namespace(node) == NAMESPACES[:sasl]
+            case node.name
+            when SUCCESS
+              stream.start(node)
+              stream.reset
+              advance
+            when FAILURE
+              stream.close_connection
+            else
+              raise StreamErrors::NotAuthorized
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/auth_restart.rb b/lib/vines/stream/server/outbound/auth_restart.rb
new file mode 100644
index 0000000..9a04b62
--- /dev/null
+++ b/lib/vines/stream/server/outbound/auth_restart.rb
@@ -0,0 +1,27 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class AuthRestart < State
+          def initialize(stream, success=AuthExternal)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless stream?(node)
+            if stream.dialback_retry?
+              if stream.outbound_tls_required?
+                stream.close_connection
+                return
+              end
+              @success = Auth
+            end
+            advance
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/authoritative.rb b/lib/vines/stream/server/outbound/authoritative.rb
new file mode 100644
index 0000000..05c5422
--- /dev/null
+++ b/lib/vines/stream/server/outbound/authoritative.rb
@@ -0,0 +1,48 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class Authoritative < State
+          VALID, INVALID, ERROR, TYPE = %w[valid invalid error type]
+          VERIFY, ID, FROM, TO = %w[verify id from to].map {|s| s.freeze }
+
+          def initialize(stream, success=nil)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless authoritative?(node)
+
+            case node[TYPE]
+            when VALID
+              @inbound.write("<db:result xmlns:db='#{NAMESPACES[:legacy_dialback]}' " \
+                "from='#{node[TO]}' to='#{node[FROM]}' type='#{node[TYPE]}'/>")
+              @inbound.advance(Server::Ready.new(@inbound))
+              @inbound.notify_connected
+            when INVALID
+              @inbound.write("<db:result xmlns:db='#{NAMESPACES[:legacy_dialback]}' " \
+                "from='#{node[TO]}' to='#{node[FROM]}' type='#{node[TYPE]}'/>")
+              @inbound.close_connection_after_writing
+            else
+              @inbound.write("<db:result xmlns:db='#{NAMESPACES[:legacy_dialback]}' " \
+                "from='#{node[TO]}' to='#{node[FROM]}' type='#{ERROR}'>" \
+                "<error type='cancel'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" \
+                "</error></db:result>")
+              @inbound.close_connection_after_writing
+            end
+            stream.close_connection
+          end
+
+          private
+
+          def authoritative?(node)
+            @inbound = stream.router.stream_by_id(node[ID])
+            node.name == VERIFY && namespace(node) == NAMESPACES[:legacy_dialback] && !@inbound.nil?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/final_features.rb b/lib/vines/stream/server/outbound/final_features.rb
new file mode 100644
index 0000000..0848533
--- /dev/null
+++ b/lib/vines/stream/server/outbound/final_features.rb
@@ -0,0 +1,28 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class FinalFeatures < State
+          def initialize(stream, success=Server::Ready)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless empty_features?(node)
+            stream.router << stream
+            advance
+            stream.notify_connected
+          end
+
+          private
+
+          def empty_features?(node)
+            node.name == 'features' && namespace(node) == NAMESPACES[:stream] && node.elements.empty?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/final_restart.rb b/lib/vines/stream/server/outbound/final_restart.rb
new file mode 100644
index 0000000..46ba30b
--- /dev/null
+++ b/lib/vines/stream/server/outbound/final_restart.rb
@@ -0,0 +1,20 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class FinalRestart < State
+          def initialize(stream, success=FinalFeatures)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless stream?(node)
+            advance
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/start.rb b/lib/vines/stream/server/outbound/start.rb
new file mode 100644
index 0000000..4677278
--- /dev/null
+++ b/lib/vines/stream/server/outbound/start.rb
@@ -0,0 +1,20 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class Start < State
+          def initialize(stream, success=Auth)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless stream?(node)
+            advance
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/outbound/tls_result.rb b/lib/vines/stream/server/outbound/tls_result.rb
new file mode 100644
index 0000000..6467a2d
--- /dev/null
+++ b/lib/vines/stream/server/outbound/tls_result.rb
@@ -0,0 +1,34 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Outbound
+        class TLSResult < State
+          NS      = NAMESPACES[:tls]
+          PROCEED = 'proceed'.freeze
+          FAILURE = 'failure'.freeze
+
+          def initialize(stream, success=AuthRestart)
+            super
+          end
+
+          def node(node)
+            raise StreamErrors::NotAuthorized unless namespace(node) == NS
+            case node.name
+            when PROCEED
+              stream.encrypt
+              stream.start(node)
+              stream.reset
+              advance
+            when FAILURE
+              stream.close_connection
+            else
+              raise StreamErrors::NotAuthorized
+            end
+          end
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/vines/stream/server/ready.rb b/lib/vines/stream/server/ready.rb
new file mode 100644
index 0000000..aa538e5
--- /dev/null
+++ b/lib/vines/stream/server/ready.rb
@@ -0,0 +1,24 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Ready < State
+        def node(node)
+          stanza = to_stanza(node)
+          raise StreamErrors::UnsupportedStanzaType unless stanza
+          to, from = stanza.validate_to, stanza.validate_from
+          raise StreamErrors::ImproperAddressing unless to && from
+          raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
+          raise StreamErrors::HostUnknown unless to.domain == stream.domain
+          stream.user = User.new(jid: from)
+          if stanza.local? || stanza.to_pubsub_domain?
+            stanza.process
+          else
+            stanza.route
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/server/start.rb b/lib/vines/stream/server/start.rb
new file mode 100644
index 0000000..876f5de
--- /dev/null
+++ b/lib/vines/stream/server/start.rb
@@ -0,0 +1,40 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+    class Server
+      class Start < State
+        FROM = "from".freeze
+
+        def initialize(stream, success=AuthMethod)
+          super
+        end
+
+        def node(node)
+          raise StreamErrors::NotAuthorized unless stream?(node)
+          stream.start(node)
+          doc = Document.new
+          features = doc.create_element('stream:features', 'xmlns:stream' => NAMESPACES[:stream]) do |el|
+            unless stream.dialback_retry?
+              el << doc.create_element('starttls') do |tls|
+                tls.default_namespace = NAMESPACES[:tls]
+                tls << doc.create_element('required') if force_s2s_encryption?
+              end
+            end
+            el << doc.create_element('dialback') do |db|
+              db.default_namespace = NAMESPACES[:dialback]
+            end
+          end
+          stream.write(features)
+          advance
+        end
+
+        private
+
+        def force_s2s_encryption?
+          stream.vhost.force_s2s_encryption?
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/stream/state.rb b/lib/vines/stream/state.rb
new file mode 100644
index 0000000..167ff59
--- /dev/null
+++ b/lib/vines/stream/state.rb
@@ -0,0 +1,46 @@
+# encoding: UTF-8
+
+module Vines
+  class Stream
+
+    # The base class of Stream state machines. States know how to process XML
+    # nodes and advance to their next valid state or fail the stream.
+    class State
+      include Nokogiri::XML
+      include Vines::Log
+      include Vines::Node
+
+      attr_accessor :stream
+
+      def initialize(stream, success=nil)
+        @stream, @success = stream, success
+      end
+
+      def node(node)
+        raise 'subclass must implement'
+      end
+
+      def ==(state)
+        self.class == state.class
+      end
+
+      def eql?(state)
+        state.is_a?(State) && self == state
+      end
+
+      def hash
+        self.class.hash
+      end
+
+      private
+
+      def advance
+        stream.advance(@success.new(stream))
+      end
+
+      def to_stanza(node)
+        super(node, stream)
+      end
+    end
+  end
+end
diff --git a/lib/vines/token_bucket.rb b/lib/vines/token_bucket.rb
new file mode 100644
index 0000000..87becf3
--- /dev/null
+++ b/lib/vines/token_bucket.rb
@@ -0,0 +1,55 @@
+# encoding: UTF-8
+
+module Vines
+
+  # The token bucket algorithm is useful for rate limiting.
+  # Before an operation can be completed, a token is taken from
+  # the bucket.  If no tokens are available, the operation fails.
+  # The bucket is refilled with tokens at the maximum allowed rate
+  # of operations.
+  class TokenBucket
+
+    # Create a full bucket with `capacity` number of tokens to be filled
+    # at the given rate of tokens/second.
+    #
+    # capacity - The Fixnum maximum number of tokens the bucket can hold.
+    # rate     - The Fixnum number of tokens per second at which the bucket is
+    #            refilled.
+    def initialize(capacity, rate)
+      raise ArgumentError.new('capacity must be > 0') unless capacity > 0
+      raise ArgumentError.new('rate must be > 0') unless rate > 0
+      @capacity = capacity
+      @tokens = capacity
+      @rate = rate
+      @timestamp = Time.new
+    end
+
+    # Remove tokens from the bucket if it's full enough. There's no way, or
+    # need, to add tokens to the bucket. It refills over time.
+    #
+    # tokens - The Fixnum number of tokens to attempt to take from the bucket.
+    #
+    # Returns true if the bucket contains enough tokens to take, false if the
+    # bucket isn't full enough to satisy the request.
+    def take(tokens)
+      raise ArgumentError.new('tokens must be > 0') unless tokens > 0
+      tokens <= fill ? @tokens -= tokens : false
+    end
+
+    private
+
+    # Add tokens to the bucket at the `rate` provided in the constructor. This
+    # fills the bucket slowly over time.
+    #
+    # Returns the Fixnum number of tokens left in the bucket.
+    def fill
+      if @tokens < @capacity
+        now = Time.new
+        @tokens += (@rate * (now - @timestamp)).round
+        @tokens = @capacity if @tokens > @capacity
+        @timestamp = now
+      end
+      @tokens
+    end
+  end
+end
diff --git a/lib/vines/user.rb b/lib/vines/user.rb
new file mode 100644
index 0000000..2d0253d
--- /dev/null
+++ b/lib/vines/user.rb
@@ -0,0 +1,125 @@
+# encoding: UTF-8
+
+module Vines
+  class User
+    include Comparable
+
+    attr_accessor :name, :token, :password, :roster
+    attr_reader :jid
+
+    def initialize(args={})
+      @jid = JID.new(args[:jid])
+      raise ArgumentError, 'invalid jid' if @jid.empty?
+
+      @name = args[:name]
+      @password = args[:password]
+      @token = args[:token]
+      @roster = args[:roster] || []
+    end
+
+    def <=>(user)
+      user.is_a?(User) ? self.jid.to_s <=> user.jid.to_s : nil
+    end
+
+    alias :eql? :==
+
+    def hash
+      jid.to_s.hash
+    end
+
+    # Update this user's information from the given user object.
+    def update_from(user)
+      @name = user.name
+      @password = user.password
+      @token = user.token
+      @roster = user.roster.map {|c| c.clone }
+    end
+
+    # Return true if the jid is on this user's roster.
+    def contact?(jid)
+      !contact(jid).nil?
+    end
+
+    # Returns the contact with this jid or nil if not found.
+    def contact(jid)
+      bare = JID.new(jid).bare
+      @roster.find {|c| c.jid.bare == bare }
+    end
+
+    # Returns true if the user is subscribed to this contact's
+    # presence updates.
+    def subscribed_to?(jid)
+      contact = contact(jid)
+      contact && contact.subscribed_to?
+    end
+
+    # Returns true if the user has a presence subscription from this contact.
+    # The contact is subscribed to this user's presence.
+    def subscribed_from?(jid)
+      contact = contact(jid)
+      contact && contact.subscribed_from?
+    end
+
+    # Removes the contact with this jid from the user's roster.
+    def remove_contact(jid)
+      bare = JID.new(jid).bare
+      @roster.reject! {|c| c.jid.bare == bare }
+    end
+
+    # Returns a list of the contacts to which this user has
+    # successfully subscribed.
+    def subscribed_to_contacts
+      @roster.select {|c| c.subscribed_to? }
+    end
+
+    # Returns a list of the contacts that are subscribed to this user's
+    # presence updates.
+    def subscribed_from_contacts
+      @roster.select {|c| c.subscribed_from? }
+    end
+
+    # Update the contact's jid on this user's roster to signal that this user
+    # has requested the contact's permission to receive their presence updates.
+    def request_subscription(jid)
+      unless contact = contact(jid)
+        contact = Contact.new(:jid => jid)
+        @roster << contact
+      end
+      contact.ask = 'subscribe' if %w[none from].include?(contact.subscription)
+    end
+
+    # Add the user's jid to this contact's roster with a subscription state of
+    # 'from.' This signals that this contact has approved a user's subscription.
+    def add_subscription_from(jid)
+      unless contact = contact(jid)
+        contact = Contact.new(:jid => jid)
+        @roster << contact
+      end
+      contact.subscribe_from
+    end
+
+    def remove_subscription_to(jid)
+      if contact = contact(jid)
+        contact.unsubscribe_to
+      end
+    end
+
+    def remove_subscription_from(jid)
+      if contact = contact(jid)
+        contact.unsubscribe_from
+      end
+    end
+
+    # Returns this user's roster contacts as an iq query element.
+    def to_roster_xml(id)
+      doc = Nokogiri::XML::Document.new
+      doc.create_element('iq', 'id' => id, 'type' => 'result') do |el|
+        el << doc.create_element('query', 'xmlns' => 'jabber:iq:roster') do |query|
+          @roster.sort!.each do |contact|
+            query << contact.to_roster_xml
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/vines/version.rb b/lib/vines/version.rb
new file mode 100644
index 0000000..422c464
--- /dev/null
+++ b/lib/vines/version.rb
@@ -0,0 +1,6 @@
+# encoding: UTF-8
+
+module Vines
+  # vines forked version 0.4.10
+  VERSION = '0.2.0.develop.4'
+end
diff --git a/lib/vines/xmpp_server.rb b/lib/vines/xmpp_server.rb
new file mode 100644
index 0000000..b59c3a5
--- /dev/null
+++ b/lib/vines/xmpp_server.rb
@@ -0,0 +1,25 @@
+# encoding: UTF-8
+
+module Vines
+
+  # The main starting point for the XMPP server process. Starts the
+  # EventMachine processing loop and registers the XMPP protocol handler
+  # with the ports defined in the server configuration file.
+  class XmppServer
+    include Vines::Log
+
+    def initialize(config)
+      @config = config
+    end
+
+    def start
+      log.info('XMPP server started')
+      at_exit { log.fatal('XMPP server stopped') }
+      EM.epoll
+      EM.kqueue
+      EM.run do
+        @config.ports.each {|port| port.start }
+      end
+    end
+  end
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..3cf544c
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,456 @@
+--- !ruby/object:Gem::Specification
+name: diaspora-vines
+version: !ruby/object:Gem::Version
+  version: 0.2.0.develop.4
+platform: ruby
+authors:
+- David Graham
+- Lukas Matt
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2015-10-10 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: bcrypt
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '3.1'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '3.1'
+- !ruby/object:Gem::Dependency
+  name: em-hiredis
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.3.0
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.3.0
+- !ruby/object:Gem::Dependency
+  name: eventmachine
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 1.0.8
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 1.0.8
+- !ruby/object:Gem::Dependency
+  name: http_parser.rb
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '0.6'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '0.6'
+- !ruby/object:Gem::Dependency
+  name: nokogiri
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.6'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.6'
+- !ruby/object:Gem::Dependency
+  name: activerecord
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '4.1'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '4.1'
+- !ruby/object:Gem::Dependency
+  name: pronto
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.4.2
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.4.2
+- !ruby/object:Gem::Dependency
+  name: pronto-rubocop
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.4.4
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 0.4.4
+- !ruby/object:Gem::Dependency
+  name: rails
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '4.1'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '4.1'
+- !ruby/object:Gem::Dependency
+  name: sqlite3
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 1.3.9
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: 1.3.9
+- !ruby/object:Gem::Dependency
+  name: minitest
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '5.8'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '5.8'
+- !ruby/object:Gem::Dependency
+  name: rake
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '10.3'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '10.3'
+description: Diaspora-vines is a Vines fork build for diaspora integration. DO NOT
+  use it unless you know what you are doing!
+email:
+- david at negativecode.com
+- lukas at zauberstuhl.de
+executables:
+- vines
+extensions: []
+extra_rdoc_files: []
+files:
+- Gemfile
+- LICENSE
+- README.md
+- Rakefile
+- bin/vines
+- conf/certs/README
+- conf/certs/ca-bundle.crt
+- conf/config.rb
+- lib/vines.rb
+- lib/vines/cli.rb
+- lib/vines/cluster.rb
+- lib/vines/cluster/connection.rb
+- lib/vines/cluster/publisher.rb
+- lib/vines/cluster/pubsub.rb
+- lib/vines/cluster/sessions.rb
+- lib/vines/cluster/subscriber.rb
+- lib/vines/command/cert.rb
+- lib/vines/command/restart.rb
+- lib/vines/command/start.rb
+- lib/vines/command/stop.rb
+- lib/vines/config.rb
+- lib/vines/config/diaspora.rb
+- lib/vines/config/host.rb
+- lib/vines/config/port.rb
+- lib/vines/config/pubsub.rb
+- lib/vines/contact.rb
+- lib/vines/daemon.rb
+- lib/vines/error.rb
+- lib/vines/jid.rb
+- lib/vines/kit.rb
+- lib/vines/log.rb
+- lib/vines/node.rb
+- lib/vines/router.rb
+- lib/vines/stanza.rb
+- lib/vines/stanza/dialback.rb
+- lib/vines/stanza/iq.rb
+- lib/vines/stanza/iq/auth.rb
+- lib/vines/stanza/iq/disco_info.rb
+- lib/vines/stanza/iq/disco_items.rb
+- lib/vines/stanza/iq/error.rb
+- lib/vines/stanza/iq/ping.rb
+- lib/vines/stanza/iq/private_storage.rb
+- lib/vines/stanza/iq/query.rb
+- lib/vines/stanza/iq/result.rb
+- lib/vines/stanza/iq/roster.rb
+- lib/vines/stanza/iq/session.rb
+- lib/vines/stanza/iq/vcard.rb
+- lib/vines/stanza/iq/version.rb
+- lib/vines/stanza/message.rb
+- lib/vines/stanza/presence.rb
+- lib/vines/stanza/presence/error.rb
+- lib/vines/stanza/presence/probe.rb
+- lib/vines/stanza/presence/subscribe.rb
+- lib/vines/stanza/presence/subscribed.rb
+- lib/vines/stanza/presence/unavailable.rb
+- lib/vines/stanza/presence/unsubscribe.rb
+- lib/vines/stanza/presence/unsubscribed.rb
+- lib/vines/stanza/pubsub.rb
+- lib/vines/stanza/pubsub/create.rb
+- lib/vines/stanza/pubsub/delete.rb
+- lib/vines/stanza/pubsub/publish.rb
+- lib/vines/stanza/pubsub/subscribe.rb
+- lib/vines/stanza/pubsub/unsubscribe.rb
+- lib/vines/storage.rb
+- lib/vines/storage/local.rb
+- lib/vines/storage/null.rb
+- lib/vines/storage/sql.rb
+- lib/vines/store.rb
+- lib/vines/stream.rb
+- lib/vines/stream/client.rb
+- lib/vines/stream/client/auth.rb
+- lib/vines/stream/client/auth_restart.rb
+- lib/vines/stream/client/bind.rb
+- lib/vines/stream/client/bind_restart.rb
+- lib/vines/stream/client/closed.rb
+- lib/vines/stream/client/ready.rb
+- lib/vines/stream/client/session.rb
+- lib/vines/stream/client/start.rb
+- lib/vines/stream/client/tls.rb
+- lib/vines/stream/component.rb
+- lib/vines/stream/component/handshake.rb
+- lib/vines/stream/component/ready.rb
+- lib/vines/stream/component/start.rb
+- lib/vines/stream/http.rb
+- lib/vines/stream/http/auth.rb
+- lib/vines/stream/http/bind.rb
+- lib/vines/stream/http/bind_restart.rb
+- lib/vines/stream/http/ready.rb
+- lib/vines/stream/http/request.rb
+- lib/vines/stream/http/session.rb
+- lib/vines/stream/http/sessions.rb
+- lib/vines/stream/http/start.rb
+- lib/vines/stream/parser.rb
+- lib/vines/stream/sasl.rb
+- lib/vines/stream/server.rb
+- lib/vines/stream/server/auth.rb
+- lib/vines/stream/server/auth_method.rb
+- lib/vines/stream/server/auth_restart.rb
+- lib/vines/stream/server/final_restart.rb
+- lib/vines/stream/server/outbound/auth.rb
+- lib/vines/stream/server/outbound/auth_dialback_result.rb
+- lib/vines/stream/server/outbound/auth_external.rb
+- lib/vines/stream/server/outbound/auth_external_result.rb
+- lib/vines/stream/server/outbound/auth_restart.rb
+- lib/vines/stream/server/outbound/authoritative.rb
+- lib/vines/stream/server/outbound/final_features.rb
+- lib/vines/stream/server/outbound/final_restart.rb
+- lib/vines/stream/server/outbound/start.rb
+- lib/vines/stream/server/outbound/tls_result.rb
+- lib/vines/stream/server/ready.rb
+- lib/vines/stream/server/start.rb
+- lib/vines/stream/state.rb
+- lib/vines/token_bucket.rb
+- lib/vines/user.rb
+- lib/vines/version.rb
+- lib/vines/xmpp_server.rb
+- test/cluster/publisher_test.rb
+- test/cluster/sessions_test.rb
+- test/cluster/subscriber_test.rb
+- test/config/host_test.rb
+- test/config/pubsub_test.rb
+- test/config_test.rb
+- test/contact_test.rb
+- test/error_test.rb
+- test/ext/nokogiri.rb
+- test/jid_test.rb
+- test/kit_test.rb
+- test/router_test.rb
+- test/stanza/iq/disco_info_test.rb
+- test/stanza/iq/disco_items_test.rb
+- test/stanza/iq/private_storage_test.rb
+- test/stanza/iq/roster_test.rb
+- test/stanza/iq/session_test.rb
+- test/stanza/iq/vcard_test.rb
+- test/stanza/iq/version_test.rb
+- test/stanza/iq_test.rb
+- test/stanza/message_test.rb
+- test/stanza/presence/probe_test.rb
+- test/stanza/presence/subscribe_test.rb
+- test/stanza/pubsub/create_test.rb
+- test/stanza/pubsub/delete_test.rb
+- test/stanza/pubsub/publish_test.rb
+- test/stanza/pubsub/subscribe_test.rb
+- test/stanza/pubsub/unsubscribe_test.rb
+- test/stanza_test.rb
+- test/storage/local_test.rb
+- test/storage/mock_redis.rb
+- test/storage/null_test.rb
+- test/storage/sql_schema.rb
+- test/storage/sql_test.rb
+- test/storage/storage_tests.rb
+- test/store_test.rb
+- test/stream/client/auth_test.rb
+- test/stream/client/ready_test.rb
+- test/stream/client/session_test.rb
+- test/stream/component/handshake_test.rb
+- test/stream/component/ready_test.rb
+- test/stream/component/start_test.rb
+- test/stream/http/auth_test.rb
+- test/stream/http/ready_test.rb
+- test/stream/http/request_test.rb
+- test/stream/http/sessions_test.rb
+- test/stream/http/start_test.rb
+- test/stream/parser_test.rb
+- test/stream/sasl_test.rb
+- test/stream/server/auth_method_test.rb
+- test/stream/server/auth_test.rb
+- test/stream/server/outbound/auth_dialback_result_test.rb
+- test/stream/server/outbound/auth_external_test.rb
+- test/stream/server/outbound/auth_restart_test.rb
+- test/stream/server/outbound/auth_test.rb
+- test/stream/server/outbound/authoritative_test.rb
+- test/stream/server/outbound/start_test.rb
+- test/stream/server/ready_test.rb
+- test/stream/server/start_test.rb
+- test/test_helper.rb
+- test/token_bucket_test.rb
+- test/user_test.rb
+homepage: https://diasporafoundation.org
+licenses:
+- MIT
+metadata: {}
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">="
+    - !ruby/object:Gem::Version
+      version: 1.9.3
+required_rubygems_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">"
+    - !ruby/object:Gem::Version
+      version: 1.3.1
+requirements: []
+rubyforge_project: 
+rubygems_version: 2.4.5.1
+signing_key: 
+specification_version: 4
+summary: Diaspora-vines is a Vines fork build for diaspora integration.
+test_files:
+- test/error_test.rb
+- test/test_helper.rb
+- test/storage/local_test.rb
+- test/storage/mock_redis.rb
+- test/storage/sql_schema.rb
+- test/storage/sql_test.rb
+- test/storage/null_test.rb
+- test/storage/storage_tests.rb
+- test/ext/nokogiri.rb
+- test/contact_test.rb
+- test/store_test.rb
+- test/cluster/sessions_test.rb
+- test/cluster/publisher_test.rb
+- test/cluster/subscriber_test.rb
+- test/config_test.rb
+- test/stream/parser_test.rb
+- test/stream/client/ready_test.rb
+- test/stream/client/auth_test.rb
+- test/stream/client/session_test.rb
+- test/stream/sasl_test.rb
+- test/stream/http/sessions_test.rb
+- test/stream/http/start_test.rb
+- test/stream/http/ready_test.rb
+- test/stream/http/auth_test.rb
+- test/stream/http/request_test.rb
+- test/stream/component/start_test.rb
+- test/stream/component/ready_test.rb
+- test/stream/component/handshake_test.rb
+- test/stream/server/start_test.rb
+- test/stream/server/ready_test.rb
+- test/stream/server/auth_test.rb
+- test/stream/server/auth_method_test.rb
+- test/stream/server/outbound/start_test.rb
+- test/stream/server/outbound/auth_restart_test.rb
+- test/stream/server/outbound/auth_external_test.rb
+- test/stream/server/outbound/auth_test.rb
+- test/stream/server/outbound/auth_dialback_result_test.rb
+- test/stream/server/outbound/authoritative_test.rb
+- test/token_bucket_test.rb
+- test/router_test.rb
+- test/user_test.rb
+- test/stanza/message_test.rb
+- test/stanza/presence/subscribe_test.rb
+- test/stanza/presence/probe_test.rb
+- test/stanza/pubsub/publish_test.rb
+- test/stanza/pubsub/delete_test.rb
+- test/stanza/pubsub/create_test.rb
+- test/stanza/pubsub/unsubscribe_test.rb
+- test/stanza/pubsub/subscribe_test.rb
+- test/stanza/iq_test.rb
+- test/stanza/iq/roster_test.rb
+- test/stanza/iq/vcard_test.rb
+- test/stanza/iq/disco_info_test.rb
+- test/stanza/iq/private_storage_test.rb
+- test/stanza/iq/session_test.rb
+- test/stanza/iq/version_test.rb
+- test/stanza/iq/disco_items_test.rb
+- test/jid_test.rb
+- test/stanza_test.rb
+- test/config/host_test.rb
+- test/config/pubsub_test.rb
+- test/kit_test.rb
diff --git a/test/cluster/publisher_test.rb b/test/cluster/publisher_test.rb
new file mode 100644
index 0000000..8e18a95
--- /dev/null
+++ b/test/cluster/publisher_test.rb
@@ -0,0 +1,57 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Cluster::Publisher do
+  subject          { Vines::Cluster::Publisher.new(cluster) }
+  let(:connection) { MiniTest::Mock.new }
+  let(:cluster)    { MiniTest::Mock.new }
+
+  before do
+    cluster.expect :id, 'abc'
+    cluster.expect :connection, connection
+  end
+
+  describe '#broadcast' do
+    before do
+      msg = {from: 'abc', type: 'online', time: Time.now.to_i}.to_json
+      connection.expect :publish, nil, ["cluster:nodes:all", msg]
+    end
+
+    it 'publishes the message to every cluster node' do
+      subject.broadcast(:online)
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe '#route' do
+    let(:stanza) { "<message>hello</message>" }
+
+    before do
+      msg = {from: 'abc', type: 'stanza', stanza: stanza}.to_json
+      connection.expect :publish, nil, ["cluster:nodes:node-42", msg]
+    end
+
+    it 'publishes the message to just one cluster node' do
+      subject.route(stanza, "node-42")
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe '#update_user' do
+    let(:jid) { Vines::JID.new('alice at wonderland.lit') }
+
+    before do
+      msg = {from: 'abc', type: 'user', jid: jid.to_s}.to_json
+      connection.expect :publish, nil, ["cluster:nodes:node-42", msg]
+    end
+
+    it 'publishes the new user to just one cluster node' do
+      subject.update_user(jid, "node-42")
+      connection.verify
+      cluster.verify
+    end
+  end
+end
diff --git a/test/cluster/sessions_test.rb b/test/cluster/sessions_test.rb
new file mode 100644
index 0000000..f1f5116
--- /dev/null
+++ b/test/cluster/sessions_test.rb
@@ -0,0 +1,47 @@
+# encoding: UTF-8
+
+require 'test_helper'
+require 'storage/storage_tests'
+require 'storage/mock_redis'
+
+describe Vines::Cluster::Sessions do
+  subject          { Vines::Cluster::Sessions.new(cluster) }
+  let(:connection) { MockRedis.new }
+  let(:cluster)    { OpenStruct.new(id: 'abc', connection: connection) }
+  let(:jid1)       { 'alice at wonderland.lit/tea' }
+  let(:jid2)       { 'alice at wonderland.lit/cake' }
+
+  describe 'when saving to the cluster' do
+    it 'writes to a redis hash' do
+      StorageTests::EMLoop.new do
+        subject.save(jid1, {available: true, interested: true})
+        subject.save(jid2, {available: false, interested: false})
+        EM.next_tick do
+          session1 = {node: 'abc', available: true, interested: true}
+          session2 = {node: 'abc', available: false, interested: false}
+          connection.db["sessions:alice at wonderland.lit"].size.must_equal 2
+          connection.db["sessions:alice at wonderland.lit"]['tea'].must_equal session1.to_json
+          connection.db["sessions:alice at wonderland.lit"]['cake'].must_equal session2.to_json
+          connection.db["cluster:nodes:abc"].to_a.must_equal [jid1, jid2]
+        end
+      end
+    end
+  end
+
+  describe 'when deleting from the cluster' do
+    it 'removes from a redis hash' do
+      StorageTests::EMLoop.new do
+        connection.db["sessions:alice at wonderland.lit"] = {}
+        connection.db["sessions:alice at wonderland.lit"]['tea'] = {node: 'abc', available: true}.to_json
+        connection.db["sessions:alice at wonderland.lit"]['cake'] = {node: 'abc', available: true}.to_json
+        connection.db["cluster:nodes:abc"] = Set.new([jid1, jid2])
+
+        subject.delete(jid1)
+        EM.next_tick do
+          connection.db["sessions:alice at wonderland.lit"].size.must_equal 1
+          connection.db["cluster:nodes:abc"].to_a.must_equal [jid2]
+        end
+      end
+    end
+  end
+end
diff --git a/test/cluster/subscriber_test.rb b/test/cluster/subscriber_test.rb
new file mode 100644
index 0000000..3b313bf
--- /dev/null
+++ b/test/cluster/subscriber_test.rb
@@ -0,0 +1,111 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Cluster::Subscriber do
+  subject          { Vines::Cluster::Subscriber.new(cluster) }
+  let(:connection) { MiniTest::Mock.new }
+  let(:cluster)    { MiniTest::Mock.new }
+  let(:now)        { Time.now.to_i }
+
+  before do
+    cluster.expect :id, 'abc'
+  end
+
+  describe '#subscribe' do
+    before do
+      cluster.expect :connect, connection
+      connection.expect :subscribe, nil, ['cluster:nodes:all']
+      connection.expect :subscribe, nil, ['cluster:nodes:abc']
+      connection.expect :on, nil, [:message]
+    end
+
+    it 'subscribes to its own channel and the broadcast channel' do
+      subject.subscribe
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe 'when receiving a heartbeat broadcast message' do
+    before do
+      cluster.expect :poke, nil, ['node-42', now]
+    end
+
+    it 'pokes the session manager for the broadcasting node' do
+      msg = {from: 'node-42', type: 'heartbeat', time: now}.to_json
+      subject.send(:on_message, 'cluster:nodes:all', msg)
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe 'when receiving an initial online broadcast message' do
+    before do
+      cluster.expect :poke, nil, ['node-42', now]
+    end
+
+    it 'pokes the session manager for the broadcasting node' do
+      msg = {from: 'node-42', type: 'online', time: now}.to_json
+      subject.send(:on_message, 'cluster:nodes:all', msg)
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe 'when receiving an offline broadcast message' do
+    before do
+      cluster.expect :delete_sessions, nil, ['node-42']
+    end
+
+    it 'deletes the sessions for the broadcasting node' do
+      msg = {from: 'node-42', type: 'offline', time: now}.to_json
+      subject.send(:on_message, 'cluster:nodes:all', msg)
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe 'when receiving a stanza routed to my node' do
+    let(:stream) { MiniTest::Mock.new }
+    let(:stanza) { "<message to='alice at wonderland.lit/tea'>hello</message>" }
+    let(:xml) { Nokogiri::XML(stanza).root }
+
+    before do
+      stream.expect :write, nil, [xml]
+      cluster.expect :connected_resources, [stream], ['alice at wonderland.lit/tea']
+    end
+
+    it 'writes the stanza to the connected user streams' do
+      # NOTE https://github.com/diaspora/vines/issues/68
+      skip "This fails randomly! Skipping it for later investigations."
+      msg = {from: 'node-42', type: 'stanza', stanza: stanza}.to_json
+      subject.send(:on_message, 'cluster:nodes:abc', msg)
+      stream.verify
+      connection.verify
+      cluster.verify
+    end
+  end
+
+  describe 'when receiving a user update message to my node' do
+    let(:alice) { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+    let(:storage) { MiniTest::Mock.new }
+    let(:stream) { MiniTest::Mock.new }
+
+    before do
+      storage.expect :find_user, alice, [alice.jid.bare]
+      stream.expect :user, alice
+      cluster.expect :storage, storage, ['wonderland.lit']
+      cluster.expect :connected_resources, [stream], [alice.jid.bare]
+    end
+
+    it 'reloads the user from storage and updates their connected streams' do
+      msg = {from: 'node-42', type: 'user', jid: alice.jid.to_s}.to_json
+      subject.send(:on_message, 'cluster:nodes:abc', msg)
+      storage.verify
+      stream.verify
+      connection.verify
+      cluster.verify
+    end
+  end
+end
diff --git a/test/config/host_test.rb b/test/config/host_test.rb
new file mode 100644
index 0000000..a15b136
--- /dev/null
+++ b/test/config/host_test.rb
@@ -0,0 +1,358 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Config::Host do
+  def test_missing_storage
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          # missing storage
+        end
+      end
+    end
+  end
+
+  def test_bad_storage
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage 'bogus' do
+            # no bogus storage implementation
+          end
+        end
+      end
+    end
+  end
+
+  def test_duplicate_storage
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage('fs') { dir Dir.tmpdir }
+          storage('fs') { dir Dir.tmpdir }
+        end
+      end
+    end
+  end
+
+  def test_good_storage_raises_no_errors
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage 'fs' do
+          dir Dir.tmpdir
+        end
+      end
+    end
+    refute_nil config.vhost('wonderland.lit').storage
+  end
+
+  def test_empty_component_name_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components '' => 'secr3t'
+        end
+      end
+    end
+  end
+
+  def test_nil_component_name_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components nil => 'secr3t'
+        end
+      end
+    end
+  end
+
+  def test_empty_component_password_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'tea' => ''
+        end
+      end
+    end
+  end
+
+  def test_nil_component_password_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'tea' => nil
+        end
+      end
+    end
+  end
+
+  def test_duplicate_component_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'tea' => 'one'
+          components 'TEA' => 'two'
+        end
+      end
+    end
+  end
+
+  def test_duplicate_component_in_one_call_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'tea' => 'one', 'TEA' => 'two'
+        end
+      end
+    end
+  end
+
+  def test_duplicate_component_symbol_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'tea' => 'one'
+          components :TEA => 'two'
+        end
+      end
+    end
+  end
+
+  def test_invalid_host_domain_raises
+    assert_raises(ArgumentError) do
+      Vines::Config.new do
+        host 'wonderland.lit ' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+      end
+    end
+  end
+
+  def test_invalid_jid_host_domain_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'alice at wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+      end
+    end
+  end
+
+  def test_invalid_component_domain_raises
+    assert_raises(ArgumentError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'exam ple' => 'one'
+        end
+      end
+    end
+  end
+
+  def test_invalid_jid_component_domain_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'alice at example' => 'one'
+        end
+      end
+    end
+  end
+
+  def test_multi_subdomain_component_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          components 'exam.ple' => 'one'
+        end
+      end
+    end
+  end
+
+  def test_case_insensitive_component_name
+    config = Vines::Config.new do
+      host 'WONDERLAND.LIT' do
+        storage(:fs) { dir Dir.tmpdir }
+        components 'TEA' => 'secr3t', CAKE: 'Passw0rd'
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    assert_equal 2, host.components.size
+    assert_equal host.components['tea.wonderland.lit'], 'secr3t'
+    assert_equal host.components['cake.wonderland.lit'], 'Passw0rd'
+  end
+
+  def test_component?
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    refute host.component?(nil)
+    refute host.component?('tea')
+    refute host.component?(:cake)
+    assert host.component?('tea.wonderland.lit')
+    assert host.component?(Vines::JID.new('tea.wonderland.lit'))
+    assert host.component?('cake.wonderland.lit')
+    assert_nil host.password(nil)
+    assert_nil host.password('bogus')
+    assert_equal 'secr3t', host.password('tea.wonderland.lit')
+    assert_equal 'passw0rd', host.password('cake.wonderland.lit')
+    expected = {'tea.wonderland.lit' => 'secr3t', 'cake.wonderland.lit' => 'passw0rd'}
+    assert_equal expected, host.components
+
+    refute config.component?(nil)
+    refute config.component?('tea')
+    refute config.component?('bogus')
+    assert config.component?('tea.wonderland.lit')
+    assert config.component?(Vines::JID.new('tea.wonderland.lit'))
+    assert config.component?('cake.wonderland.lit')
+    assert config.component?('tea.wonderland.lit', 'cake.wonderland.lit')
+    refute config.component?('tea.wonderland.lit', 'bogus.wonderland.lit')
+
+    assert_nil config.component_password(nil)
+    assert_nil config.component_password('bogus')
+    assert_equal 'secr3t', config.component_password('tea.wonderland.lit')
+    assert_equal 'passw0rd', config.component_password('cake.wonderland.lit')
+  end
+
+  def test_invalid_pubsub_domain_raises
+    assert_raises(ArgumentError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          pubsub 'exam ple'
+        end
+      end
+    end
+  end
+
+  def test_invalid_jid_pubsub_domain_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          pubsub 'alice at example'
+        end
+      end
+    end
+  end
+
+  def test_multi_subdomain_pubsub_raises
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+          pubsub 'exam.ple'
+        end
+      end
+    end
+  end
+
+  def test_case_insensitive_pubsub_name
+    config = Vines::Config.new do
+      host 'WONDERLAND.LIT' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'TEA', :CAKE
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    assert_equal 2, host.pubsubs.size
+    refute_nil host.pubsubs['tea.wonderland.lit']
+    refute_nil host.pubsubs['cake.wonderland.lit']
+  end
+
+  def test_pubsub?
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'tea', :cake
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    refute host.pubsub?(nil)
+    refute host.pubsub?('tea')
+    refute host.pubsub?(:cake)
+    assert host.pubsub?('tea.wonderland.lit')
+    assert host.pubsub?(Vines::JID.new('tea.wonderland.lit'))
+    assert host.pubsub?('cake.wonderland.lit')
+    assert_equal ['tea.wonderland.lit', 'cake.wonderland.lit'], host.pubsubs.keys
+
+    refute config.pubsub?(nil)
+    refute config.pubsub?('tea')
+    refute config.pubsub?('bogus')
+    assert config.pubsub?('tea.wonderland.lit')
+    assert config.pubsub?(Vines::JID.new('tea.wonderland.lit'))
+    assert config.pubsub?('cake.wonderland.lit')
+    refute config.pubsub?('alice at cake.wonderland.lit')
+  end
+
+  def test_default_private_storage_is_off
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    refute host.private_storage?
+  end
+
+  def test_enable_private_storage
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        private_storage true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    host = config.vhost('wonderland.lit')
+    refute_nil host
+    assert host.private_storage?
+    assert config.private_storage?('wonderland.lit')
+    assert config.private_storage?(Vines::JID.new('wonderland.lit'))
+    refute config.private_storage?(Vines::JID.new('alice at wonderland.lit'))
+    refute config.private_storage?(nil)
+  end
+
+  def test_enabled_blacklisting
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      server '0.0.0.0', 5269 do
+        max_stanza_size 131072
+        blacklist ['wonderland.lit']
+      end
+    end
+    refute config.s2s?('wonderland.lit')
+  end
+
+  def test_disabled_blacklisting
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      server '0.0.0.0', 5269 do
+        max_stanza_size 131072
+        blacklist []
+      end
+    end
+    assert config.s2s?('wonderland.lit')
+  end
+end
diff --git a/test/config/pubsub_test.rb b/test/config/pubsub_test.rb
new file mode 100644
index 0000000..88cf857
--- /dev/null
+++ b/test/config/pubsub_test.rb
@@ -0,0 +1,187 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Config::PubSub do
+  subject { config.pubsub('topics.wonderland.lit') }
+  let(:alice) { Vines::JID.new('alice at wonderland.lit') }
+  let(:romeo) { Vines::JID.new('romeo at verona.lit') }
+  let(:config) do
+    @config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'topics'
+      end
+    end
+  end
+
+  it 'adds and deletes a topic node' do
+    topic = 'rhode_island_is_neither_a_road_nor_an_island'
+    refute subject.node?(topic)
+    subject.add_node(topic)
+    assert subject.node?(topic)
+    subject.delete_node(topic)
+    refute subject.node?(topic)
+  end
+
+  it 'ignores deleting a missing topic node' do
+    topic = 'kittens_vs_puppies'
+    refute subject.node?(topic)
+    subject.delete_node(topic)
+    refute subject.node?(topic)
+  end
+
+  it 'subscribes a jid to a node' do
+    topic = 'with_jid'
+    jid = Vines::JID.new('alice at wonderland.lit')
+    subject.add_node(topic)
+    subject.subscribe(topic, jid)
+    assert subject.subscribed?(topic, jid.to_s)
+    assert subject.subscribed?(topic, jid)
+  end
+
+  it 'does not allow remote jids to subscribe to a node by default' do
+    topic = 'remote_jids_failed'
+    jid = 'romeo at verona.lit'
+    subject.add_node(topic)
+    subject.subscribe(topic, jid)
+    refute subject.subscribed?(topic, jid)
+  end
+
+  it 'allows remote jid subscriptions when cross domain messages are enabled' do
+    topic = 'remote_jids_allowed'
+    jid = 'romeo at verona.lit'
+    config.vhost('wonderland.lit').cross_domain_messages true
+    subject.add_node(topic)
+    subject.subscribe(topic, jid)
+    assert subject.subscribed?(topic, jid)
+  end
+
+  it 'ignores subscribing to a missing node' do
+    topic = 'bogus'
+    jid = 'alice at wonderland.lit'
+    refute subject.node?(topic)
+    subject.subscribe(topic, jid)
+    refute subject.node?(topic)
+    refute subject.subscribed?(topic, jid)
+  end
+
+  it 'deletes the node after unsubscribing' do
+    topic = 'delete_me'
+    jid = 'alice at wonderland.lit/tea'
+    subject.add_node(topic)
+    subject.subscribe(topic, jid)
+    assert subject.subscribed?(topic, jid)
+    subject.unsubscribe(topic, jid)
+    refute subject.subscribed?(topic, jid)
+    refute subject.node?(topic)
+  end
+
+  it 'unsubscribes a jid from all topics' do
+    topic = 'pirates_vs_ninjas'
+    topic2 = 'pirates_vs_ninjas_2'
+    jid = 'alice at wonderland.lit'
+    jid2 = 'hatter at wonderland.lit'
+    subject.add_node(topic)
+    subject.add_node(topic2)
+
+    subject.subscribe(topic, jid)
+    subject.subscribe(topic, jid2)
+    subject.subscribe(topic2, jid)
+    assert subject.subscribed?(topic, jid)
+    assert subject.subscribed?(topic, jid2)
+    assert subject.subscribed?(topic2, jid)
+
+    subject.unsubscribe_all(jid)
+    refute subject.node?(topic2)
+    refute subject.subscribed?(topic, jid)
+    refute subject.subscribed?(topic2, jid)
+    assert subject.subscribed?(topic, jid2)
+  end
+
+  describe 'when publishing a message to a topic node' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='topics.wonderland.lit'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <publish node='pirates_vs_ninjas'>
+              <item id='item_42'>
+                <entry xmlns='http://www.w3.org/2005/Atom'>
+                  <title>Test</title>
+                  <summary>This is a summary.</summary>
+                </entry>
+              </item>
+            </publish>
+          </pubsub>
+        </iq>})
+    end
+
+    let(:recipient) do
+      recipient = MiniTest::Mock.new
+      recipient.expect :user, Vines::User.new(jid: alice)
+      class << recipient
+        attr_accessor :nodes
+        def write(node)
+          @nodes ||= []
+          @nodes << node
+        end
+      end
+      recipient
+    end
+
+    before do
+      router = MiniTest::Mock.new
+      router.expect :connected_resources, [recipient], [alice, 'topics.wonderland.lit']
+      class << router
+        attr_accessor :nodes
+        def route(node)
+          @nodes ||= []
+          @nodes << node
+        end
+      end
+
+      class << config
+        attr_accessor :router
+      end
+      config.router = router
+      config.vhost('wonderland.lit').cross_domain_messages true
+
+      subject.add_node(topic)
+      subject.subscribe(topic, alice)
+      subject.subscribe(topic, romeo)
+    end
+
+    let(:topic) { 'pirates_vs_ninjas' }
+
+    it 'writes the message to local connected resource streams' do
+      expected = xml.clone
+      expected['to'] = 'alice at wonderland.lit'
+      expected['from'] = 'topics.wonderland.lit'
+
+      subject.publish(topic, xml)
+      config.router.verify
+      recipient.verify
+
+      # id is random
+      received = recipient.nodes.first
+      received['id'].wont_be_nil
+      received.remove_attribute('id')
+      received.must_equal expected
+    end
+
+    it 'routes the message to remote jids' do
+      expected = xml.clone
+      expected['to'] = 'romeo at verona.lit'
+      expected['from'] = 'topics.wonderland.lit'
+
+      subject.publish(topic, xml)
+      config.router.verify
+
+      # id is random
+      routed = config.router.nodes.first
+      routed['id'].wont_be_nil
+      routed.remove_attribute('id')
+      routed.must_equal expected
+    end
+  end
+end
diff --git a/test/config_test.rb b/test/config_test.rb
new file mode 100644
index 0000000..2eaa1bd
--- /dev/null
+++ b/test/config_test.rb
@@ -0,0 +1,753 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Config do
+  def test_missing_host
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        # missing hosts
+      end
+    end
+  end
+
+  def test_duplicate_host
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage 'fs' do
+            dir Dir.tmpdir
+          end
+        end
+        host 'WONDERLAND.LIT' do
+          storage 'fs' do
+            dir Dir.tmpdir
+          end
+        end
+      end
+    end
+  end
+
+  def test_duplicate_host_in_one_call
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit', 'wonderland.lit' do
+          storage 'fs' do
+            dir Dir.tmpdir
+          end
+        end
+      end
+    end
+  end
+
+  def test_configure
+    config = Vines::Config.configure do
+      host 'wonderland.lit' do
+        storage :fs do
+          dir Dir.tmpdir
+        end
+      end
+    end
+    refute_nil config
+    assert_same config, Vines::Config.instance
+  end
+
+  def test_vhost
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    refute_nil config.vhost('wonderland.lit')
+    refute_nil config.vhost(Vines::JID.new('wonderland.lit'))
+    assert config.vhost?('wonderland.lit')
+    assert config.vhost?(Vines::JID.new('wonderland.lit'))
+    refute config.vhost?('alice at wonderland.lit')
+    refute config.vhost?('tea.wonderland.lit')
+    refute config.vhost?('bogus')
+  end
+
+  def test_port_lookup
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      client
+    end
+    refute_nil config[:client]
+    assert_raises(ArgumentError) { config[:server] }
+    assert_raises(ArgumentError) { config[:bogus] }
+  end
+
+  def test_duplicate_client
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+        client
+        client
+      end
+    end
+  end
+
+  def test_duplicate_server
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+        server
+        server
+      end
+    end
+  end
+
+  def test_duplicate_http
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+        http
+        http
+      end
+    end
+  end
+
+  def test_duplicate_component
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+        component
+        component
+      end
+    end
+  end
+
+  def test_duplicate_cluster
+    assert_raises(RuntimeError) do
+      Vines::Config.new do
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+        cluster {}
+        cluster {}
+      end
+    end
+  end
+
+  def test_missing_cluster
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    assert_nil config.cluster
+    refute config.cluster?
+  end
+
+  def test_cluster
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      cluster do
+        host 'redis.wonderland.lit'
+        port 12345
+        database 8
+        password 'secr3t'
+      end
+    end
+    refute_nil config.cluster
+    assert config.cluster?
+    assert_equal 'redis.wonderland.lit', config.cluster.host
+    assert_equal 12345, config.cluster.port
+    assert_equal 8, config.cluster.database
+    assert_equal 'secr3t', config.cluster.password
+  end
+
+  def test_default_client
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      client
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ClientPort, port.class
+    assert_equal '0.0.0.0', port.host
+    assert_equal 5222, port.port
+    assert_equal 131_072, port.max_stanza_size
+    assert_equal 5, port.max_resources_per_account
+    assert_equal Vines::Stream::Client, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_configured_client
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      client '0.0.0.1', 42 do
+        max_stanza_size 60_000
+        max_resources_per_account 1
+      end
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ClientPort, port.class
+    assert_equal '0.0.0.1', port.host
+    assert_equal 42, port.port
+    assert_equal 60_000, port.max_stanza_size
+    assert_equal 1, port.max_resources_per_account
+    assert_equal Vines::Stream::Client, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_max_stanza_size
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      client do
+        max_stanza_size 0
+      end
+    end
+    assert_equal 10_000, config.ports.first.max_stanza_size
+  end
+
+  def test_default_server
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      server
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ServerPort, port.class
+    assert_equal '0.0.0.0', port.host
+    assert_equal 5269, port.port
+    assert_equal 131_072, port.max_stanza_size
+    assert_equal Vines::Stream::Server, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_configured_server
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      server '0.0.0.1', 42 do
+        max_stanza_size 60_000
+      end
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ServerPort, port.class
+    assert_equal '0.0.0.1', port.host
+    assert_equal 42, port.port
+    assert_equal 60_000, port.max_stanza_size
+    assert_equal Vines::Stream::Server, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_default_http
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      http
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::HttpPort, port.class
+    assert_equal '0.0.0.0', port.host
+    assert_equal 5280, port.port
+    assert_equal 131_072, port.max_stanza_size
+    assert_equal 5, port.max_resources_per_account
+    assert_equal File.join(Dir.pwd, 'web'), port.root
+    assert_equal '/xmpp', port.bind
+    assert_equal Vines::Stream::Http, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_configured_http
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      http '0.0.0.1', 42 do
+        bind '/custom'
+        max_stanza_size 60_000
+        max_resources_per_account 1
+        root '/var/www/html'
+      end
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::HttpPort, port.class
+    assert_equal '0.0.0.1', port.host
+    assert_equal 42, port.port
+    assert_equal 60_000, port.max_stanza_size
+    assert_equal 1, port.max_resources_per_account
+    assert_equal '/var/www/html', port.root
+    assert_equal '/custom', port.bind
+    assert_equal Vines::Stream::Http, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_default_component
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      component
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ComponentPort, port.class
+    assert_equal '0.0.0.0', port.host
+    assert_equal 5347, port.port
+    assert_equal 131_072, port.max_stanza_size
+    assert_equal Vines::Stream::Component, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_configured_component
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      component '0.0.0.1', 42 do
+        max_stanza_size 60_000
+      end
+    end
+    port = config.ports.first
+    refute_nil port
+    assert_equal Vines::Config::ComponentPort, port.class
+    assert_equal '0.0.0.1', port.host
+    assert_equal 42, port.port
+    assert_equal 60_000, port.max_stanza_size
+    assert_equal Vines::Stream::Component, port.stream
+    assert_same config, port.config
+    assert_equal 1, config.ports.size
+  end
+
+  def test_not_existing_file_path
+    assert_raises(RuntimeError) do
+      config = Vines::Config.new do
+        log 'not/existing/path.log'
+      end
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  def test_invalid_log_level
+    assert_raises(RuntimeError) do
+      config = Vines::Config.new do
+        log 'vines.log' do
+          level 'bogus'
+        end
+        host 'wonderland.lit' do
+          storage(:fs) { dir Dir.tmpdir }
+        end
+      end
+    end
+  end
+
+  def test_valid_log_level
+    config = Vines::Config.new do
+      log 'vines.log' do
+        level :error
+      end
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    assert_equal Logger::ERROR, Class.new.extend(Vines::Log).log.level
+  end
+
+  def test_null_storage
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    assert_equal Vines::Storage::Local, config.storage('wonderland.lit').class
+    assert_equal Vines::Storage::Null, config.storage('bogus').class
+  end
+
+  def test_cross_domain_messages
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      host 'verona.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    refute config.vhost('wonderland.lit').cross_domain_messages?
+    assert config.vhost('verona.lit').cross_domain_messages?
+  end
+
+  def test_accept_self_signed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      host 'verona.lit' do
+        accept_self_signed true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    refute config.vhost('wonderland.lit').accept_self_signed?
+    assert config.vhost('verona.lit').accept_self_signed?
+  end
+
+  def test_local_jid?
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    refute config.local_jid?(nil)
+    refute config.local_jid?('alice at wonderland.lit', nil)
+    assert config.local_jid?('alice at wonderland.lit')
+    assert config.local_jid?(Vines::JID.new('alice at wonderland.lit'))
+    assert config.local_jid?(Vines::JID.new('wonderland.lit'))
+    assert config.local_jid?('alice at wonderland.lit', 'romeo at verona.lit')
+    refute config.local_jid?('alice at wonderland.lit', 'romeo at bogus.lit')
+    refute config.local_jid?('alice at tea.wonderland.lit')
+    refute config.local_jid?('alice at bogus.lit')
+  end
+
+  def test_missing_addresses_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    refute config.allowed?(nil, nil)
+    refute config.allowed?('', '')
+  end
+
+  def test_same_domain_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    hatter = Vines::JID.new('hatter at wonderland.lit')
+    assert config.allowed?(alice, hatter)
+    assert config.allowed?(hatter, alice)
+    assert config.allowed?('wonderland.lit', alice)
+  end
+
+  def test_both_vhosts_with_cross_domain_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    assert config.allowed?(alice, romeo)
+    assert config.allowed?(romeo, alice)
+  end
+
+  def test_one_vhost_with_cross_domain_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    refute config.allowed?(alice, romeo)
+    refute config.allowed?(romeo, alice)
+  end
+
+  def test_same_domain_component_to_pubsub_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+        pubsub 'games'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    tea = Vines::JID.new('tea.wonderland.lit')
+    pubsub = Vines::JID.new('games.wonderland.lit')
+    assert config.allowed?(alice, pubsub)
+    assert config.allowed?(pubsub, alice)
+    assert config.allowed?(tea, pubsub)
+    assert config.allowed?(pubsub, tea)
+  end
+
+  def test_cross_domain_component_to_pubsub_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+        pubsub 'games'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    tea = Vines::JID.new('tea.wonderland.lit')
+    pubsub = Vines::JID.new('games.verona.lit')
+    assert config.allowed?(alice, pubsub)
+    assert config.allowed?(pubsub, alice)
+    assert config.allowed?(tea, pubsub)
+    assert config.allowed?(pubsub, tea)
+  end
+
+  def test_cross_domain_component_to_pubsub_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+        pubsub 'games'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'party' => 'secr3t'
+      end
+    end
+    romeo = Vines::JID.new('romeo at party.verona.lit')
+    party = Vines::JID.new('party.verona.lit')
+    pubsub = Vines::JID.new('games.wonderland.lit')
+    refute config.allowed?(romeo, pubsub)
+    refute config.allowed?(pubsub, romeo)
+    refute config.allowed?(party, pubsub)
+    refute config.allowed?(pubsub, party)
+  end
+
+  def test_same_domain_component_to_component_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    hatter = Vines::JID.new('hatter at cake.wonderland.lit')
+    assert config.allowed?(alice, alice)
+    assert config.allowed?(alice, hatter)
+    assert config.allowed?(hatter, alice)
+  end
+
+  def test_cross_domain_component_to_component_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    romeo = Vines::JID.new('romeo at cake.verona.lit')
+    assert config.allowed?(alice, romeo)
+    assert config.allowed?(romeo, alice)
+  end
+
+  def test_cross_domain_component_to_component_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'party' => 'secr3t'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    romeo = Vines::JID.new('romeo at party.verona.lit')
+    refute config.allowed?(alice, romeo)
+    refute config.allowed?(romeo, alice)
+  end
+
+  def test_same_domain_user_to_component_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    comp = Vines::JID.new('hatter at cake.wonderland.lit')
+    assert config.allowed?(alice, comp)
+    assert config.allowed?(comp, alice)
+  end
+
+  def test_cross_domain_user_to_component_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit', 'verona.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    assert config.allowed?(alice, romeo)
+    assert config.allowed?(romeo, alice)
+  end
+
+  def test_cross_domain_user_to_component_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    refute config.allowed?(alice, romeo)
+    refute config.allowed?(romeo, alice)
+  end
+
+  def test_remote_user_to_component_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', cake: 'passw0rd'
+      end
+    end
+    alice = Vines::JID.new('alice at tea.wonderland.lit')
+    romeo = Vines::JID.new('romeo at tea.verona.lit')
+    hamlet = Vines::JID.new('hamlet at denmark.lit')
+    assert config.allowed?(alice, hamlet)
+    assert config.allowed?(hamlet, alice)
+    refute config.allowed?(romeo, hamlet)
+    refute config.allowed?(hamlet, romeo)
+  end
+
+  def test_same_domain_user_to_pubsub_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    pubsub = Vines::JID.new('games.wonderland.lit')
+    assert config.allowed?(alice, pubsub)
+    assert config.allowed?(pubsub, alice)
+  end
+
+  def test_cross_domain_user_to_pubsub_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    pubsub = Vines::JID.new('games.wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    refute config.allowed?(pubsub, romeo)
+    refute config.allowed?(romeo, pubsub)
+  end
+
+  def test_remote_user_to_pubsub_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+    wonderland = Vines::JID.new('games.wonderland.lit')
+    verona = Vines::JID.new('games.verona.lit')
+    hamlet = Vines::JID.new('hamlet at denmark.lit')
+    assert config.allowed?(wonderland, hamlet)
+    assert config.allowed?(hamlet, wonderland)
+    refute config.allowed?(verona, hamlet)
+    refute config.allowed?(hamlet, verona)
+  end
+
+  def test_remote_user_to_local_user_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      host 'verona.lit' do
+        cross_domain_messages false
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    alice = Vines::JID.new('alice at wonderland.lit')
+    romeo = Vines::JID.new('romeo at verona.lit')
+    hamlet = Vines::JID.new('hamlet at denmark.lit')
+    assert config.allowed?(alice, hamlet)
+    assert config.allowed?(hamlet, alice)
+    refute config.allowed?(romeo, hamlet)
+    refute config.allowed?(hamlet, romeo)
+  end
+
+  def test_remote_user_to_remote_user_not_allowed
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+    romeo = Vines::JID.new('romeo at verona.lit')
+    hamlet = Vines::JID.new('hamlet at denmark.lit')
+    refute config.allowed?(romeo, hamlet)
+    refute config.allowed?(hamlet, romeo)
+  end
+end
diff --git a/test/contact_test.rb b/test/contact_test.rb
new file mode 100644
index 0000000..cbda9da
--- /dev/null
+++ b/test/contact_test.rb
@@ -0,0 +1,102 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Contact do
+  subject do
+    Vines::Contact.new(
+      jid: 'alice at wonderland.lit',
+      name: "Alice",
+      groups: %w[Friends Buddies],
+      subscription: 'from')
+  end
+
+  describe 'contact equality checks' do
+    let(:alice)  { Vines::Contact.new(jid: 'alice at wonderland.lit') }
+    let(:hatter) { Vines::Contact.new(jid: 'hatter at wonderland.lit') }
+
+    it 'uses class in equality check' do
+      (subject <=> 42).must_be_nil
+    end
+
+    it 'is equal to itself' do
+      assert subject == subject
+      assert subject.eql?(subject)
+      assert subject.hash == subject.hash
+    end
+
+    it 'is equal to another contact with the same jid' do
+      assert subject == alice
+      assert subject.eql?(alice)
+      assert subject.hash == alice.hash
+    end
+
+    it 'is not equal to a different jid' do
+      refute subject == hatter
+      refute subject.eql?(hatter)
+      refute subject.hash == hatter.hash
+    end
+  end
+
+  describe 'initialize' do
+    it 'raises when not given a jid' do
+      -> { Vines::Contact.new }.must_raise ArgumentError
+      -> { Vines::Contact.new(jid: '') }.must_raise ArgumentError
+    end
+
+    it 'accepts a domain-only jid' do
+      contact = Vines::Contact.new(jid: 'tea.wonderland.lit')
+      contact.jid.to_s.must_equal 'tea.wonderland.lit'
+    end
+  end
+
+  describe '#to_roster_xml' do
+    let(:expected) do
+      node(%q{
+        <item jid="alice at wonderland.lit" name="Alice" subscription="from" from_diaspora="false">
+          <group>Buddies</group>
+          <group>Friends</group>
+        </item>
+      })
+    end
+
+    it 'sorts group names' do
+      subject.to_roster_xml.must_equal expected
+    end
+  end
+
+  describe '#send_roster_push' do
+    let(:recipient) { MiniTest::Mock.new }
+    let(:expected) do
+      node(%q{
+        <iq to="hatter at wonderland.lit" type="set">
+          <query xmlns="jabber:iq:roster">
+          <item jid="alice at wonderland.lit" name="Alice" subscription="from" from_diaspora="false">
+            <group>Buddies</group>
+            <group>Friends</group>
+          </item>
+          </query>
+        </iq>
+      })
+    end
+
+    before do
+      recipient.expect :user, Vines::User.new(jid: 'hatter at wonderland.lit')
+      class << recipient
+        attr_accessor :nodes
+        def write(node)
+          @nodes ||= []
+          @nodes << node
+        end
+      end
+    end
+
+    it '' do
+      subject.send_roster_push(recipient)
+      recipient.verify
+      recipient.nodes.size.must_equal 1
+      recipient.nodes.first.remove_attribute('id') # id is random
+      recipient.nodes.first.must_equal expected
+    end
+  end
+end
diff --git a/test/error_test.rb b/test/error_test.rb
new file mode 100644
index 0000000..0238e4d
--- /dev/null
+++ b/test/error_test.rb
@@ -0,0 +1,58 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::XmppError do
+  describe Vines::SaslErrors do
+    it 'does not require a text element' do
+      expected = %q{<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><temporary-auth-failure/></failure>}
+      Vines::SaslErrors::TemporaryAuthFailure.new.to_xml.must_equal expected
+    end
+
+    it 'includes a text element when message is given' do
+      text = %q{<text xml:lang="en">busted</text>}
+      expected = %q{<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><temporary-auth-failure/>%s</failure>} % text
+      Vines::SaslErrors::TemporaryAuthFailure.new('busted').to_xml.must_equal expected
+    end
+  end
+
+  describe Vines::StreamErrors do
+    it 'does not require a text element' do
+      expected = %q{<stream:error><internal-server-error xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>}
+      Vines::StreamErrors::InternalServerError.new.to_xml.must_equal expected
+    end
+
+    it 'includes a text element when message is given' do
+      text = %q{<text xmlns="urn:ietf:params:xml:ns:xmpp-streams" xml:lang="en">busted</text>}
+      expected = %q{<stream:error><internal-server-error xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>%s</stream:error>} % text
+      Vines::StreamErrors::InternalServerError.new('busted').to_xml.must_equal expected
+    end
+  end
+
+  describe Vines::StanzaErrors do
+    it 'raises when given a bad type' do
+      node = node('<message/>')
+      -> { Vines::StanzaErrors::BadRequest.new(node, 'bogus') }.must_raise RuntimeError
+    end
+
+    it 'raises when given a bad stanza' do
+      node = node('<bogus/>')
+      -> { Vines::StanzaErrors::BadRequest.new(node, 'modify') }.must_raise RuntimeError
+    end
+
+    it 'does not require a text element' do
+      error = %q{<error type="modify"><bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error>}
+      expected = %q{<message from="hatter at wonderland.lit" to="alice at wonderland.lit" type="error">%s</message>} % error
+      node = node(%Q{<message from="alice at wonderland.lit" to="hatter at wonderland.lit"/>})
+      Vines::StanzaErrors::BadRequest.new(node, 'modify').to_xml.must_equal expected
+    end
+
+    it 'includes a text element when message is given' do
+      text = %q{<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">busted</text>}
+      error = %q{<error type="modify"><bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>%s</error>} % text
+      expected = %q{<message id="42" type="error">%s</message>} % error
+      node = node(%Q{<message id="42"/>})
+      Vines::StanzaErrors::BadRequest.new(node, 'modify', 'busted').to_xml.must_equal expected
+    end
+  end
+end
diff --git a/test/ext/nokogiri.rb b/test/ext/nokogiri.rb
new file mode 100644
index 0000000..80641f8
--- /dev/null
+++ b/test/ext/nokogiri.rb
@@ -0,0 +1,14 @@
+# encoding: UTF-8
+
+module Nokogiri
+  module XML
+    class Node
+      # Override equality testing so we can use MiniTest::Mock#expect with
+      # Nokogiri::XML::Node arguments. Node's default behavior considers
+      # all nodes unequal.
+      def ==(node)
+        self.to_s == node.to_s
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/test/jid_test.rb b/test/jid_test.rb
new file mode 100644
index 0000000..479af44
--- /dev/null
+++ b/test/jid_test.rb
@@ -0,0 +1,147 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::JID do
+  it 'handles empty input' do
+    [nil, ''].each do |text|
+      Vines::JID.new(text) # shouldn't raise an error
+      jid = Vines::JID.new(text)
+      assert_nil jid.node
+      assert_nil jid.resource
+      assert_equal '', jid.domain
+      assert_equal '', jid.to_s
+      assert_equal '', jid.bare.to_s
+      assert jid.empty?
+      refute jid.domain?
+    end
+  end
+
+  it 'raises when a jid part is too long' do
+    Vines::JID.new('n' * 1023) # shouldn't raise an error
+    assert_raises(ArgumentError) { Vines::JID.new('n' * 1024) }
+    assert_raises(ArgumentError) { Vines::JID.new('n', 'd' * 1024) }
+    assert_raises(ArgumentError) { Vines::JID.new('n', 'd', 'r' * 1024) }
+    Vines::JID.new('n' * 1023, 'd' * 1023, 'r' * 1023) # shouldn't raise an error
+  end
+
+  it 'correctly handles domain only jids' do
+    jid = Vines::JID.new('wonderland.lit')
+    assert_equal 'wonderland.lit', jid.to_s
+    assert_equal 'wonderland.lit', jid.domain
+    assert_nil jid.node
+    assert_nil jid.resource
+    assert_equal jid, jid.bare
+    assert jid.domain?
+    refute jid.empty?
+  end
+
+  it 'correctly handles bare jid components' do
+    jid = Vines::JID.new('alice', 'wonderland.lit')
+    assert_equal 'alice at wonderland.lit', jid.to_s
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'alice', jid.node
+    assert_nil jid.resource
+    assert_equal jid, jid.bare
+    refute jid.domain?
+    refute jid.empty?
+  end
+
+  it 'correctly parses bare jids' do
+    jid = Vines::JID.new('alice at wonderland.lit')
+    assert_equal 'alice at wonderland.lit', jid.to_s
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'alice', jid.node
+    assert_nil jid.resource
+    assert_equal jid, jid.bare
+    refute jid.domain?
+    refute jid.empty?
+  end
+
+  it 'correctly handles full jid components' do
+    jid = Vines::JID.new('alice', 'wonderland.lit', 'tea')
+    assert_equal 'alice at wonderland.lit/tea', jid.to_s
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'alice', jid.node
+    assert_equal 'tea', jid.resource
+    refute_equal jid, jid.bare
+    refute jid.domain?
+    refute jid.empty?
+  end
+
+  it 'correctly parses full jids' do
+    jid = Vines::JID.new('alice at wonderland.lit/tea')
+    assert_equal 'alice at wonderland.lit/tea', jid.to_s
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'alice', jid.node
+    assert_equal 'tea', jid.resource
+    refute_equal jid, jid.bare
+    refute jid.domain?
+    refute jid.empty?
+  end
+
+  it 'accepts separator characters in resource part' do
+    jid = Vines::JID.new('alice at wonderland.lit/foo/bar at blarg test')
+    assert_equal 'alice', jid.node
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'foo/bar at blarg test', jid.resource
+  end
+
+  it 'accepts separator characters in resource part with missing node part' do
+    jid = Vines::JID.new('wonderland.lit/foo/bar at blarg')
+    assert_nil jid.node
+    assert_equal 'wonderland.lit', jid.domain
+    assert_equal 'foo/bar at blarg', jid.resource
+    refute jid.domain?
+  end
+
+  it 'accepts strange characters in node part' do
+    jid = Vines::JID.new(%q{nasty!#$%()*+,-.;=?[\]^_`{|}~node at example.com})
+    jid.node.must_equal %q{nasty!#$%()*+,-.;=?[\]^_`{|}~node}
+    jid.domain.must_equal 'example.com'
+    jid.resource.must_be_nil
+  end
+
+  it 'accepts strange characters in resource part' do
+    jid = Vines::JID.new(%q{node at example.com/repulsive !#"$%&'()*+,-./:;<=>?@[\]^_`{|}~resource})
+    jid.node.must_equal 'node'
+    jid.domain.must_equal 'example.com'
+    jid.resource.must_equal %q{repulsive !#"$%&'()*+,-./:;<=>?@[\]^_`{|}~resource}
+  end
+
+  it 'rejects empty jid parts' do
+    assert_raises(ArgumentError) { Vines::JID.new('@wonderland.lit') }
+    assert_raises(ArgumentError) { Vines::JID.new('wonderland.lit/') }
+    assert_raises(ArgumentError) { Vines::JID.new('@') }
+    assert_raises(ArgumentError) { Vines::JID.new('alice@') }
+    assert_raises(ArgumentError) { Vines::JID.new('/') }
+    assert_raises(ArgumentError) { Vines::JID.new('/res') }
+    assert_raises(ArgumentError) { Vines::JID.new('@/') }
+  end
+
+  it 'rejects invalid characters' do
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice"s at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice&s at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice's at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice:s at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice<s at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new(%q{alice>s at wonderland.lit}) }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\u0000s at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\ts at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\rs at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\ns at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\vs at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice\fs at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new(" alice at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at wonderland.lit ") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice s at wonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w onderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\tonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\ronderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\nonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\vonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\fonderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at w\u0000onderland.lit") }
+    assert_raises(ArgumentError) { Vines::JID.new("alice at wonderland.lit/\u0000res") }
+  end
+end
diff --git a/test/kit_test.rb b/test/kit_test.rb
new file mode 100644
index 0000000..ae72d71
--- /dev/null
+++ b/test/kit_test.rb
@@ -0,0 +1,31 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Kit do
+  describe '#hmac' do
+    it 'generates a SHA-512 HMAC' do
+      Vines::Kit.hmac('secret', 'username').length.must_equal 128
+      assert_equal Vines::Kit.hmac('s1', 'u1'), Vines::Kit.hmac('s1', 'u1')
+      refute_equal Vines::Kit.hmac('s1', 'u1'), Vines::Kit.hmac('s2', 'u1')
+      refute_equal Vines::Kit.hmac('s1', 'u1'), Vines::Kit.hmac('s1', 'u2')
+    end
+  end
+
+  describe '#uuid' do
+    it 'returns a random uuid' do
+      ids = Array.new(1000) { Vines::Kit.uuid }
+      assert ids.all? {|id| !id.nil? }
+      assert ids.all? {|id| id.length == 36 }
+      assert ids.all? {|id| id.match(/\w{8}-\w{4}-[4]\w{3}-[89ab]\w{3}-\w{12}/) }
+      ids.uniq.length.must_equal ids.length
+    end
+  end
+
+  describe '#auth_token' do
+    it 'returns a random 128 character token' do
+      Vines::Kit.auth_token.wont_equal Vines::Kit.auth_token
+      Vines::Kit.auth_token.length.must_equal 128
+    end
+  end
+end
diff --git a/test/router_test.rb b/test/router_test.rb
new file mode 100644
index 0000000..d681256
--- /dev/null
+++ b/test/router_test.rb
@@ -0,0 +1,243 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Router do
+  subject      { Vines::Router.new(config) }
+  let(:alice)  { Vines::JID.new('alice at wonderland.lit/tea') }
+  let(:hatter) { 'hatter at wonderland.lit/cake' }
+  let(:romeo)  { 'romeo at verona.lit/party' }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t'
+      end
+    end
+  end
+
+  describe '#connected_resources' do
+    let(:cake) { 'alice at wonderland.lit/cake' }
+    let(:stream1) { stream(alice) }
+    let(:stream2) { stream(cake) }
+
+    it 'is empty before any streams are connected' do
+      subject.connected_resources(alice, alice).size.must_equal 0
+      subject.connected_resources(cake, alice).size.must_equal 0
+      subject.size.must_equal 0
+    end
+
+    it 'returns only one stream matching full jid' do
+      subject << stream1
+      subject << stream2
+
+      streams = subject.connected_resources(alice, alice)
+      streams.size.must_equal 1
+      streams.first.user.jid.must_equal alice
+
+      streams = subject.connected_resources(cake, alice)
+      streams.size.must_equal 1
+      streams.first.user.jid.to_s.must_equal cake
+    end
+
+    it 'returns all streams matching bare jid' do
+      subject << stream1
+      subject << stream2
+
+      streams = subject.connected_resources(alice.bare, alice)
+      streams.size.must_equal 2
+      subject.size.must_equal 2
+    end
+  end
+
+  describe '#connected_resources with permissions' do
+    let(:stream1) { stream(alice) }
+    let(:stream2) { stream(romeo) }
+
+    before do
+      subject << stream1
+      subject << stream2
+    end
+
+    it 'denies access when cross domain messages is off' do
+      subject.connected_resources(alice, romeo).size.must_equal 0
+    end
+
+    it 'allows access when cross domain messages is on' do
+      config.vhost('wonderland.lit').cross_domain_messages true
+      subject.connected_resources(alice, romeo).size.must_equal 1
+    end
+  end
+
+  describe '#available_resources' do
+    let(:cake) { 'alice at wonderland.lit/cake' }
+    let(:stream1) { stream(alice) }
+    let(:stream2) { stream(cake) }
+
+    before do
+      stream1.send 'available?=', true
+      stream2.send 'available?=', false
+    end
+
+    it 'is empty before any streams are connected' do
+      subject.available_resources(alice, alice).size.must_equal 0
+      subject.available_resources(cake, alice).size.must_equal 0
+      subject.size.must_equal 0
+    end
+
+    it 'returns available streams based on bare jid, not full jid' do
+      subject << stream1
+      subject << stream2
+
+      streams = [alice, cake, alice.bare].map do |jid|
+        subject.available_resources(jid, alice)
+      end.flatten
+
+      # should only have found alice's stream
+      streams.size.must_equal 3
+      streams.uniq.size.must_equal 1
+      streams.first.user.jid.must_equal alice
+
+      subject.size.must_equal 2
+    end
+  end
+
+  describe '#interested_resources with no streams' do
+    it 'is empty before any streams are connected' do
+      subject.interested_resources(alice, alice).size.must_equal 0
+      subject.interested_resources(hatter, alice).size.must_equal 0
+      subject.interested_resources(alice, hatter, alice).size.must_equal 0
+      subject.size.must_equal 0
+    end
+  end
+
+  describe '#interested_resources' do
+    let(:stream1) { stream(alice) }
+    let(:stream2) { stream(hatter) }
+
+    before do
+      stream1.send 'interested?=', true
+      stream2.send 'interested?=', false
+      subject << stream1
+      subject << stream2
+    end
+
+    it 'does not find streams for unauthenticated jids' do
+      subject.interested_resources('bogus at wonderland.lit', alice).size.must_equal 0
+    end
+
+    it 'finds interested streams for full jids' do
+      subject.interested_resources(alice, hatter, alice).size.must_equal 1
+      subject.interested_resources([alice, hatter], alice).size.must_equal 1
+      subject.interested_resources(alice, hatter, alice)[0].user.jid.must_equal alice
+    end
+
+    it 'does not find streams for uninterested jids' do
+      subject.interested_resources(hatter, alice).size.must_equal 0
+      subject.interested_resources([hatter], alice).size.must_equal 0
+    end
+
+    it 'finds interested streams for bare jids' do
+      subject.interested_resources(alice.bare, alice).size.must_equal 1
+      subject.interested_resources(alice.bare, alice)[0].user.jid.must_equal alice
+    end
+  end
+
+  describe '#delete' do
+    let(:stream1) { stream(alice) }
+    let(:stream2) { stream(hatter) }
+
+    it 'correctly adds and removes streams' do
+      subject.size.must_equal 0
+
+      subject << stream1
+      subject << stream2
+      subject.size.must_equal 2
+
+      subject.delete(stream2)
+      subject.size.must_equal 1
+
+      subject.delete(stream2)
+      subject.size.must_equal 1
+
+      subject.delete(stream1)
+      subject.size.must_equal 0
+    end
+  end
+
+  describe 'load balanced component streams' do
+    let(:stream1) { component('tea.wonderland.lit') }
+    let(:stream2) { component('tea.wonderland.lit') }
+    let(:stanza)  { node('<message from="alice at wonderland.lit" to="tea.wonderland.lit">test</message>')}
+
+    before do
+      subject << stream1
+      subject << stream2
+    end
+
+    it 'must evenly distribute routed stanzas to both streams' do
+      100.times { subject.route(stanza) }
+
+      (stream1.count + stream2.count).must_equal 100
+      stream1.count.must_be :>, 33
+      stream2.count.must_be :>, 33
+    end
+  end
+
+  describe 'load balanced s2s streams' do
+    let(:stream1) { s2s('wonderland.lit', 'verona.lit') }
+    let(:stream2) { s2s('wonderland.lit', 'verona.lit') }
+    let(:stanza) { node('<message from="alice at wonderland.lit" to="romeo at verona.lit">test</message>') }
+
+    before do
+      config.vhost('wonderland.lit').cross_domain_messages true
+      subject << stream1
+      subject << stream2
+    end
+
+    it 'must evenly distribute routed stanzas to both streams' do
+      100.times { subject.route(stanza) }
+
+      (stream1.count + stream2.count).must_equal 100
+      stream1.count.must_be :>, 33
+      stream2.count.must_be :>, 33
+    end
+  end
+
+  private
+
+  def stream(jid)
+    OpenStruct.new.tap do |stream|
+      stream.send('connected?=', true)
+      stream.stream_type = :client
+      stream.user = Vines::User.new(jid: jid)
+    end
+  end
+
+  def component(jid)
+    OpenStruct.new.tap do |stream|
+      stream.stream_type = :component
+      stream.remote_domain = jid
+      stream.send('ready?=', true)
+      def stream.count; @count || 0; end
+      def stream.write(stanza)
+        @count ||= 0
+        @count += 1
+      end
+    end
+  end
+
+  def s2s(domain, remote_domain)
+    OpenStruct.new.tap do |stream|
+      stream.stream_type = :server
+      stream.domain = domain
+      stream.remote_domain = remote_domain
+      stream.send('ready?=', true)
+      def stream.count; @count || 0; end
+      def stream.write(stanza)
+        @count ||= 0
+        @count += 1
+      end
+    end
+  end
+end
diff --git a/test/stanza/iq/disco_info_test.rb b/test/stanza/iq/disco_info_test.rb
new file mode 100644
index 0000000..ad7896a
--- /dev/null
+++ b/test/stanza/iq/disco_info_test.rb
@@ -0,0 +1,80 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::DiscoInfo do
+  subject      { Vines::Stanza::Iq::DiscoInfo.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/home') }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  let(:xml) do
+    query = %q{<query xmlns="http://jabber.org/protocol/disco#info"/>}
+    node(%Q{<iq id="42" to="wonderland.lit" type="get">#{query}</iq>})
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :user
+    end
+    stream.config = config
+    stream.user = alice
+  end
+
+  describe 'when private storage is disabled' do
+    let(:expected) do
+      node(%Q{
+        <iq from="wonderland.lit" id="42" to="#{alice.jid}" type="result">
+          <query xmlns="http://jabber.org/protocol/disco#info">
+            <identity category="server" type="im"/>
+            <feature var="http://jabber.org/protocol/disco#info"/>
+            <feature var="http://jabber.org/protocol/disco#items"/>
+            <feature var="msgoffline"/>
+            <feature var="urn:xmpp:ping"/>
+            <feature var="vcard-temp"/>
+            <feature var="jabber:iq:version"/>
+          </query>
+        </iq>
+      })
+    end
+
+    it 'returns info stanza without the private storage feature' do
+      config.vhost('wonderland.lit').private_storage false
+      stream.expect :write, nil, [expected]
+      subject.process
+      stream.verify
+    end
+  end
+
+  describe 'when private storage is enabled' do
+    let(:expected) do
+      node(%Q{
+        <iq from="wonderland.lit" id="42" to="#{alice.jid}" type="result">
+          <query xmlns="http://jabber.org/protocol/disco#info">
+            <identity category="server" type="im"/>
+            <feature var="http://jabber.org/protocol/disco#info"/>
+            <feature var="http://jabber.org/protocol/disco#items"/>
+            <feature var="msgoffline"/>
+            <feature var="urn:xmpp:ping"/>
+            <feature var="vcard-temp"/>
+            <feature var="jabber:iq:version"/>
+            <feature var="jabber:iq:private"/>
+          </query>
+        </iq>
+      })
+    end
+
+    it 'announces private storage feature in info stanza result' do
+      config.vhost('wonderland.lit').private_storage true
+      stream.expect :write, nil, [expected]
+      subject.process
+      stream.verify
+    end
+  end
+end
diff --git a/test/stanza/iq/disco_items_test.rb b/test/stanza/iq/disco_items_test.rb
new file mode 100644
index 0000000..baaa52f
--- /dev/null
+++ b/test/stanza/iq/disco_items_test.rb
@@ -0,0 +1,49 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::DiscoItems do
+  subject      { Vines::Stanza::Iq::DiscoItems.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/home') }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        components 'tea' => 'secr3t', 'cake' => 'passw0rd'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :user
+    end
+    stream.config = config
+    stream.user = alice
+  end
+
+  describe 'when querying server items' do
+    let(:xml) do
+      query = %q{<query xmlns="http://jabber.org/protocol/disco#items"/>}
+      node(%Q{<iq id="42" to="wonderland.lit" type="get">#{query}</iq>})
+    end
+
+    let(:result) do
+      node(%q{
+        <iq from="wonderland.lit" id="42" to="alice at wonderland.lit/home" type="result">
+          <query xmlns="http://jabber.org/protocol/disco#items">
+            <item jid="cake.wonderland.lit"/>
+            <item jid="tea.wonderland.lit"/>
+          </query>
+        </iq>
+      })
+    end
+
+    it 'includes component domains in output' do
+      stream.expect :write, nil, [result]
+      subject.process
+      stream.verify
+    end
+  end
+end
diff --git a/test/stanza/iq/private_storage_test.rb b/test/stanza/iq/private_storage_test.rb
new file mode 100644
index 0000000..7029c88
--- /dev/null
+++ b/test/stanza/iq/private_storage_test.rb
@@ -0,0 +1,184 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::PrivateStorage do
+  subject       { Vines::Stanza::Iq::PrivateStorage.new(xml, stream) }
+  let(:alice)   { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:storage) { MiniTest::Mock.new }
+  let(:stream)  { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        private_storage true
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :domain, :user
+    end
+    stream.config = config
+    stream.user = alice
+    stream.domain = 'wonderland.lit'
+  end
+
+  describe 'when private storage feature is disabled' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
+      node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    before do
+      config.vhost('wonderland.lit').private_storage false
+    end
+
+    it 'raises a service-unavailable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ServiceUnavailable
+      stream.verify
+    end
+  end
+
+  describe 'when retrieving a fragment for another user jid' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
+      node(%Q{<iq id="42" to="hatter at wonderland.lit" type="get">#{query}</iq>})
+    end
+
+    it 'raises a forbidden stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::Forbidden
+      stream.verify
+    end
+  end
+
+  describe 'when get stanza contains zero child elements' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"></query>}
+      node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    it 'raises a not-acceptable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::NotAcceptable
+      stream.verify
+    end
+  end
+
+  describe 'when get stanza contains more than one child element' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/><two xmlns="b"/></query>}
+      node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    it 'raises a not-acceptable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::NotAcceptable
+      stream.verify
+    end
+  end
+
+  describe 'when get stanza is missing a namespace' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one/></query>}
+      node = node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    it 'raises a not-acceptable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::NotAcceptable
+      stream.verify
+    end
+  end
+
+  describe 'when get stanza is missing fragment' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
+      node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    before do
+      storage.expect :find_fragment, nil, [alice.jid, xml.elements[0].elements[0]]
+      stream.expect :storage, storage, ['wonderland.lit']
+    end
+
+    it 'raises an item-not-found stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+      stream.verify
+      storage.verify
+    end
+  end
+
+  describe 'when get finds fragment successfully' do
+    let(:xml) do
+      query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
+      node = node(%Q{<iq id="42" type="get">#{query}</iq>})
+    end
+
+    before do
+      data = %q{<one xmlns="a"><child>data</child></one>}
+      query = %Q{<query xmlns="jabber:iq:private">#{data}</query>}
+      expected = node(%Q{<iq from="#{alice.jid}" id="42" to="#{alice.jid}" type="result">#{query}</iq>})
+
+      storage.expect :find_fragment, node(data), [alice.jid, xml.elements[0].elements[0]]
+      stream.expect :storage, storage, ['wonderland.lit']
+      stream.expect :write, nil, [expected]
+    end
+
+    it 'writes a response to the stream' do
+      subject.process
+      stream.verify
+      storage.verify
+    end
+  end
+
+  describe 'when saving a fragment' do
+    let(:result) { node(%Q{<iq from="#{alice.jid}" id="42" to="#{alice.jid}" type="result"/>}) }
+
+    before do
+      storage.expect :save_fragment, nil, [alice.jid, xml.elements[0].elements[0]]
+      stream.expect :storage, storage, ['wonderland.lit']
+      stream.expect :write, nil, [result]
+    end
+
+    describe 'and stanza contains zero child elements' do
+      let(:xml) do
+        query = %q{<query xmlns="jabber:iq:private"></query>}
+        node(%Q{<iq id="42" type="set">#{query}</iq>})
+      end
+
+      it 'raises a not-acceptable stanza error' do
+        -> { subject.process }.must_raise Vines::StanzaErrors::NotAcceptable
+      end
+    end
+
+    describe 'and a single single fragment saves successfully' do
+      let(:xml) do
+        query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
+        node(%Q{<iq id="42" type="set">#{query}</iq>})
+      end
+
+      it 'writes a result to the stream' do
+        subject.process
+        stream.verify
+        storage.verify
+      end
+    end
+
+    describe 'and two fragments save successfully' do
+      let(:xml) do
+        query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/><two xmlns="a"/></query>}
+        node(%Q{<iq id="42" type="set">#{query}</iq>})
+      end
+
+      before do
+        storage.expect :save_fragment, nil, [alice.jid, xml.elements[0].elements[1]]
+        stream.expect :storage, storage, ['wonderland.lit']
+      end
+
+      it 'writes a result to the stream' do
+        subject.process
+        stream.verify
+        storage.verify
+      end
+    end
+  end
+end
diff --git a/test/stanza/iq/roster_test.rb b/test/stanza/iq/roster_test.rb
new file mode 100644
index 0000000..2fedc37
--- /dev/null
+++ b/test/stanza/iq/roster_test.rb
@@ -0,0 +1,229 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::Roster do
+  subject      { Vines::Stanza::Iq::Roster.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+
+  before do
+    class << stream
+      attr_accessor :domain, :user
+    end
+    stream.user = alice
+    stream.domain = 'wonderland.lit'
+  end
+
+  describe 'when retrieving an empty roster' do
+    let(:xml) { node(%q{<iq id="42" type="get"><query xmlns='jabber:iq:roster'/></iq>}) }
+    let(:expected) { node(%q{<iq id="42" type="result"><query xmlns="jabber:iq:roster"/></iq>}) }
+
+    before do
+      stream.expect :write, nil, [expected]
+      stream.expect :requested_roster!, nil
+    end
+
+    it 'returns an empty stanza' do
+      subject.process
+      stream.verify
+    end
+  end
+
+  describe 'when retrieving a non-empty roster' do
+    let(:xml) { node(%q{<iq id="42" type="get"><query xmlns='jabber:iq:roster'/></iq>}) }
+    let(:expected) do
+      node(%q{
+        <iq id="42" type="result">
+          <query xmlns="jabber:iq:roster">
+            <item jid="cat at wonderland.lit" subscription="none" from_diaspora="false">
+              <group>Cats</group>
+              <group>Friends</group>
+            </item>
+            <item jid="hatter at wonderland.lit" subscription="none" from_diaspora="false"/>
+          </query>
+        </iq>})
+    end
+
+    before do
+      alice.roster << Vines::Contact.new(jid: 'hatter at wonderland.lit')
+      alice.roster << Vines::Contact.new(jid: 'cat at wonderland.lit', :groups => ['Friends', 'Cats'])
+
+      stream.expect :write, nil, [expected]
+      stream.expect :requested_roster!, nil
+    end
+
+    it 'sorts groups alphabetically' do
+      subject.process
+      stream.verify
+    end
+  end
+
+  describe 'when requesting a roster for another user' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="get" to="romeo at verona.lit">
+          <query xmlns="jabber:iq:roster"/>
+        </iq>})
+    end
+
+    it 'raises a forbidden stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::Forbidden
+      stream.verify
+    end
+  end
+
+  describe 'when saving a roster for another user' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set" to="romeo at verona.lit">
+          <query xmlns="jabber:iq:roster"/>
+        </iq>})
+    end
+
+    it 'raises a forbidden stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::Forbidden
+      stream.verify
+    end
+  end
+
+  describe 'when saving a roster with no items' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster"/>
+        </iq>})
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when updating a roster with more than one item' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item jid="hatter at wonderland.lit"/>
+            <item jid="cat at wonderland.lit"/>
+          </query>
+        </iq>})
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when adding a roster item without a jid attribute' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item name="Mad Hatter"/>
+          </query>
+        </iq>})
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when adding a roster item with duplicate groups' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item jid="hatter at wonderland.lit" name="Mad Hatter">
+              <group>Friends</group>
+              <group>Friends</group>
+            </item>
+          </query>
+        </iq>})
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when adding a roster item with an empty group name' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item jid="hatter at wonderland.lit" name="Mad Hatter">
+              <group></group>
+            </item>
+          </query>
+        </iq>})
+    end
+
+    it 'raises a not-acceptable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::NotAcceptable
+      stream.verify
+    end
+  end
+
+  describe 'when saving a roster successfully' do
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item jid="hatter at wonderland.lit" name="Mad Hatter">
+              <group>Friends</group>
+            </item>
+          </query>
+        </iq>})
+    end
+
+    let(:expected) do
+      node(%q{
+        <iq to="alice at wonderland.lit/tea" type="set">
+          <query xmlns="jabber:iq:roster">
+            <item jid="hatter at wonderland.lit" name="Mad Hatter" subscription="none" from_diaspora="false">
+              <group>Friends</group>
+            </item>
+          </query>
+        </iq>})
+    end
+
+    let(:storage) { MiniTest::Mock.new }
+    let(:recipient) { MiniTest::Mock.new }
+    let(:result) { node(%Q{<iq id="42" to="#{alice.jid}" type="result"/>}) }
+
+    before do
+      storage.expect :save_user, nil, [alice]
+
+      recipient.expect :user, alice
+      def recipient.nodes; @nodes; end
+      def recipient.write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+
+      stream.expect :interested_resources, [recipient], [alice.jid]
+      stream.expect :update_user_streams, nil, [alice]
+      stream.expect :storage, storage, ['wonderland.lit']
+      stream.expect :write, nil, [result]
+    end
+
+    it 'sends a result to the sender' do
+      subject.process
+      stream.verify
+      storage.verify
+    end
+
+    it 'sends the new roster item to the interested streams' do
+      subject.process
+      recipient.nodes.first.remove_attribute('id') # id is random
+      recipient.nodes.first.must_equal expected
+    end
+  end
+end
diff --git a/test/stanza/iq/session_test.rb b/test/stanza/iq/session_test.rb
new file mode 100644
index 0000000..785f22a
--- /dev/null
+++ b/test/stanza/iq/session_test.rb
@@ -0,0 +1,25 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::Session do
+  subject      { Vines::Stanza::Iq::Session.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+
+  describe 'when session initiation is requested' do
+    let(:xml) { node(%q{<iq id="42" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>}) }
+    let(:result) { node(%q{<iq from="wonderland.lit" id="42" to="alice at wonderland.lit/tea" type="result"/>}) }
+
+    before do
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :user, alice
+      stream.expect :write, nil, [result]
+    end
+
+    it 'just returns a result to satisy older clients' do
+      subject.process
+      stream.verify
+    end
+  end
+end
diff --git a/test/stanza/iq/vcard_test.rb b/test/stanza/iq/vcard_test.rb
new file mode 100644
index 0000000..5681521
--- /dev/null
+++ b/test/stanza/iq/vcard_test.rb
@@ -0,0 +1,146 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::Vcard do
+  subject       { Vines::Stanza::Iq::Vcard.new(xml, stream) }
+  let(:alice)   { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream)  { MiniTest::Mock.new }
+  let(:storage) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        cross_domain_messages true
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :domain, :user
+    end
+    stream.config = config
+    stream.domain = 'wonderland.lit'
+    stream.user = alice
+  end
+
+  describe 'when getting vcard' do
+    describe 'and addressed to a remote jid' do
+      let(:xml) { get('romeo at verona.lit') }
+      let(:router) { MiniTest::Mock.new }
+
+      before do
+        router.expect :route, nil, [xml]
+        stream.expect :router, router
+      end
+
+      it 'routes rather than handle locally' do
+        subject.process
+        stream.verify
+        router.verify
+      end
+    end
+
+    describe 'and missing to address' do
+      let(:xml) { get('') }
+      let(:card) { vcard('Alice') }
+      let(:expected) { result(alice.jid, '', card) }
+
+      before do
+        storage.expect :find_vcard, card, [alice.jid.bare]
+        stream.expect :storage, storage, ['wonderland.lit']
+        stream.expect :write, nil, [expected]
+      end
+
+      it 'sends vcard for authenticated jid' do
+        subject.process
+        stream.verify
+        storage.verify
+      end
+    end
+
+    describe 'for another user' do
+      let(:xml) { get(hatter) }
+      let(:card) { vcard('Hatter') }
+      let(:hatter) { Vines::JID.new('hatter at wonderland.lit') }
+      let(:expected) { result(alice.jid, hatter, card) }
+
+      before do
+        storage.expect :find_vcard, card, [hatter]
+        stream.expect :storage, storage, ['wonderland.lit']
+        stream.expect :write, nil, [expected]
+      end
+
+      it 'succeeds and returns vcard with from address' do
+        subject.process
+        stream.verify
+        storage.verify
+      end
+    end
+
+    describe 'for missing vcard' do
+      let(:xml) { get('') }
+
+      before do
+        storage.expect :find_vcard, nil, [alice.jid.bare]
+        stream.expect :storage, storage, ['wonderland.lit']
+      end
+
+      it 'returns an item-not-found stanza error' do
+        -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+        stream.verify
+        storage.verify
+      end
+    end
+  end
+
+  describe 'when setting vcard' do
+    describe 'and addressed to another user' do
+      let(:xml) { set('hatter at wonderland.lit') }
+
+      it 'raises a forbidden stanza error' do
+        -> { subject.process }.must_raise Vines::StanzaErrors::Forbidden
+        stream.verify
+      end
+    end
+
+    describe 'and missing to address' do
+      let(:xml) { set('') }
+      let(:card) { vcard('Alice') }
+      let(:expected) { result(alice.jid) }
+
+      before do
+        storage.expect :save_vcard, nil, [alice.jid, card]
+        stream.expect :storage, storage, ['wonderland.lit']
+        stream.expect :write, nil, [expected]
+      end
+
+      it 'succeeds and returns an iq result' do
+        subject.process
+        stream.verify
+        storage.verify
+      end
+    end
+  end
+
+  private
+
+  def vcard(name)
+    node(%Q{<vCard xmlns="vcard-temp"><FN>#{name}</FN></vCard>})
+  end
+
+  def get(to)
+    card = '<vCard xmlns="vcard-temp"/>'
+    iq(id: 42, to: to, type: 'get', body: card)
+  end
+
+  def set(to)
+    card = '<vCard xmlns="vcard-temp"><FN>Alice</FN></vCard>'
+    iq(id: 42, to: to, type: 'set', body: card)
+  end
+
+  def result(to, from=nil, card=nil)
+    iq(from: from, id: 42, to: to, type: 'result', body: card)
+  end
+end
diff --git a/test/stanza/iq/version_test.rb b/test/stanza/iq/version_test.rb
new file mode 100644
index 0000000..1016e65
--- /dev/null
+++ b/test/stanza/iq/version_test.rb
@@ -0,0 +1,64 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq::Version do
+  subject      { Vines::Stanza::Iq::Version.new(xml, stream) }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :user
+    end
+    stream.config = config
+    stream.user = alice
+  end
+
+  describe 'when not addressed to the server' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) { node(%q{<iq id="42" to="romeo at verona.lit" type="get"><query xmlns="jabber:iq:version"/></iq>}) }
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+    end
+
+    it 'routes the stanza to the recipient jid' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) { node(%q{<iq id="42" type="get"><query xmlns="jabber:iq:version"/></iq>}) }
+    let(:expected) do
+      node(%Q{
+        <iq from="wonderland.lit" id="42" to="alice at wonderland.lit/tea" type="result">
+          <query xmlns="jabber:iq:version">
+            <name>Vines</name>
+            <version>#{Vines::VERSION}</version>
+          </query>
+        </iq>})
+    end
+
+    before do
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :write, nil, [expected]
+    end
+
+    it 'returns a version result when missing a to jid' do
+      subject.process
+      stream.verify
+    end
+  end
+end
diff --git a/test/stanza/iq_test.rb b/test/stanza/iq_test.rb
new file mode 100644
index 0000000..df43920
--- /dev/null
+++ b/test/stanza/iq_test.rb
@@ -0,0 +1,70 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Iq do
+  subject      { Vines::Stanza::Iq.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:hatter) { Vines::User.new(jid: 'hatter at wonderland.lit/crumpets') }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :user
+    end
+    stream.user = hatter
+    stream.config = config
+  end
+
+  describe 'when addressed to a user rather than the server itself' do
+    let(:recipient) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq id="42" type="set" to="alice at wonderland.lit/tea" from="hatter at wonderland.lit/crumpets">
+            <si xmlns="http://jabber.org/protocol/si" id="42_si" profile="http://jabber.org/protocol/si/profile/file-transfer">
+              <file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="file" size="1"/>
+              <feature xmlns="http://jabber.org/protocol/feature-neg">
+                <x xmlns="jabber:x:data" type="form">
+                  <field var="stream-method" type="list-single">
+                    <option>
+                      <value>http://jabber.org/protocol/bytestreams</value>
+                    </option>
+                    <option>
+                      <value>http://jabber.org/protocol/ibb</value>
+                    </option>
+                  </field>
+                </x>
+              </feature>
+            </si>
+          </iq>
+      })
+    end
+
+    before do
+      recipient.expect :user, alice, []
+      recipient.expect :write, nil, [xml]
+      stream.expect :connected_resources, [recipient], [alice.jid]
+    end
+
+    it 'routes the stanza to the users connected resources' do
+      subject.process
+      stream.verify
+      recipient.verify
+    end
+  end
+
+  describe 'when given no type or body elements' do
+    let(:xml) { node('<iq type="set" id="42"/>') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+    end
+  end
+end
diff --git a/test/stanza/message_test.rb b/test/stanza/message_test.rb
new file mode 100644
index 0000000..c44a410
--- /dev/null
+++ b/test/stanza/message_test.rb
@@ -0,0 +1,127 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Message do
+  subject      { Vines::Stanza::Message.new(xml, stream) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:romeo)  { Vines::User.new(jid: 'romeo at verona.lit/balcony') }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :user
+    end
+    stream.user = alice
+    stream.config = config
+  end
+
+  describe 'when message type attribute is invalid' do
+    let(:xml) { node('<message type="bogus">hello!</message>') }
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+    end
+  end
+
+  describe 'when the to address is missing' do
+    let(:xml) { node('<message>hello!</message>') }
+    let(:recipient) { MiniTest::Mock.new }
+
+    before do
+      recipient.expect :user, alice
+      recipient.expect :write, nil, [xml]
+      stream.expect :connected_resources, [recipient], [alice.jid.bare]
+    end
+
+    it 'sends the message to the senders connected streams' do
+      subject.process
+      stream.verify
+      recipient.verify
+    end
+  end
+
+  describe 'when addressed to a non-user' do
+    let(:bogus) { Vines::JID.new('bogus at wonderland.lit/cake') }
+    let(:xml) { node(%Q{<message to="#{bogus}">hello!</message>}) }
+    let(:storage) { MiniTest::Mock.new }
+
+    before do
+      storage.expect :find_user, nil, [bogus]
+      stream.expect :storage, storage, [bogus.domain]
+      stream.expect :connected_resources, [], [bogus]
+    end
+
+    it 'ignores the stanza' do
+      subject.process
+      stream.verify
+      storage.verify
+    end
+  end
+
+  describe 'when addressed to an offline user' do
+    let(:hatter) { Vines::User.new(jid: 'hatter at wonderland.lit/cake') }
+    let(:xml) { node(%Q{<message to="#{hatter.jid}">hello!</message>}) }
+    let(:storage) { MiniTest::Mock.new }
+
+    before do
+      skip # due offline message implementation
+      storage.expect :find_user, hatter, [hatter.jid]
+      stream.expect :storage, storage, [hatter.jid.domain]
+      stream.expect :connected_resources, [], [hatter.jid]
+    end
+
+    it 'raises a service-unavailable stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ServiceUnavailable
+      stream.verify
+      storage.verify
+    end
+  end
+
+  describe 'when address to a local user in a different domain' do
+    let(:xml) { node(%Q{<message to="#{romeo.jid}">hello!</message>}) }
+    let(:expected) { node(%Q{<message to="#{romeo.jid}" from="#{alice.jid}">hello!</message>}) }
+    let(:recipient) { MiniTest::Mock.new }
+
+    before do
+      recipient.expect :user, romeo
+      recipient.expect :write, nil, [expected]
+
+      config.host 'verona.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+
+      stream.expect :connected_resources, [recipient], [romeo.jid]
+    end
+
+    it 'delivers the stanza to the user' do
+      subject.process
+      stream.verify
+      recipient.verify
+    end
+  end
+
+  describe 'when addressed to a remote user' do
+    let(:xml) { node(%Q{<message to="#{romeo.jid}">hello!</message>}) }
+    let(:expected) { node(%Q{<message to="#{romeo.jid}" from="#{alice.jid}">hello!</message>}) }
+    let(:router) { MiniTest::Mock.new }
+
+    before do
+      router.expect :route, nil, [expected]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+end
diff --git a/test/stanza/presence/probe_test.rb b/test/stanza/presence/probe_test.rb
new file mode 100644
index 0000000..fcaff07
--- /dev/null
+++ b/test/stanza/presence/probe_test.rb
@@ -0,0 +1,50 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Presence::Probe do
+  def setup
+    @alice = Vines::JID.new('alice at wonderland.lit/tea')
+    @stream = MiniTest::Mock.new
+    @config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  def test_missing_to_address_raises
+    node = node(%q{<presence id="42" type="probe"/>})
+    stanza = Vines::Stanza::Presence::Probe.new(node, @stream)
+    def stanza.inbound?; false; end
+
+    @stream.expect(:user, Vines::User.new(jid: @alice))
+
+    assert_raises(Vines::StanzaErrors::BadRequest) { stanza.process }
+    assert @stream.verify
+  end
+
+  def test_to_remote_address_routes
+    node = node(%q{<presence id="42" to="romeo at verona.lit" type="probe"/>})
+    stanza = Vines::Stanza::Presence::Probe.new(node, @stream)
+    def stanza.inbound?; false; end
+
+    expected = node(%Q{<presence id="42" to="romeo at verona.lit" type="probe" from="#{@alice}"/>})
+    router = MiniTest::Mock.new
+    router.expect(:route, nil, [expected])
+
+    @stream.expect(:router, router)
+    @stream.expect(:user, Vines::User.new(jid: @alice))
+    @stream.expect(:config, @config)
+
+    stanza.process
+    assert @stream.verify
+    assert router.verify
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stanza/presence/subscribe_test.rb b/test/stanza/presence/subscribe_test.rb
new file mode 100644
index 0000000..000e3da
--- /dev/null
+++ b/test/stanza/presence/subscribe_test.rb
@@ -0,0 +1,83 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::Presence::Subscribe do
+  subject       { Vines::Stanza::Presence::Subscribe.new(xml, stream) }
+  let(:stream)  { MiniTest::Mock.new }
+  let(:alice)   { Vines::JID.new('alice at wonderland.lit/tea') }
+  let(:hatter)  { Vines::JID.new('hatter at wonderland.lit') }
+  let(:contact) { Vines::Contact.new(jid: hatter) }
+
+  before do
+    class << stream
+      attr_accessor :user, :nodes
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+  end
+
+  describe 'outbound subscription to a local jid, but missing contact' do
+    let(:xml) { node(%q{<presence id="42" to="hatter at wonderland.lit" type="subscribe"/>}) }
+    let(:user) { MiniTest::Mock.new }
+    let(:storage) { MiniTest::Mock.new }
+    let(:recipient) { MiniTest::Mock.new }
+
+    before do
+      class << user
+        attr_accessor :jid
+      end
+      user.jid = alice
+      user.expect :request_subscription, nil, [hatter]
+      user.expect :contact, contact, [hatter]
+
+      storage.expect :save_user, nil, [user]
+      storage.expect :find_user, nil, [hatter]
+
+      recipient.expect :user, user
+      class << recipient
+        attr_accessor :nodes
+        def write(node)
+          @nodes ||= []
+          @nodes << node
+        end
+      end
+
+      stream.user = user
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :storage, storage, ['wonderland.lit']
+      stream.expect :storage, storage, ['wonderland.lit']
+      stream.expect :interested_resources, [recipient], [alice]
+      stream.expect :update_user_streams, nil, [user]
+
+      class << subject
+        def route_iq; false; end
+        def inbound?; false; end
+        def local?;   true;  end
+      end
+    end
+
+    it 'rejects the subscription with an unsubscribed response' do
+      subject.process
+      stream.verify
+      user.verify
+      storage.verify
+      stream.nodes.size.must_equal 1
+
+      expected = node(%q{<presence from="hatter at wonderland.lit" id="42" to="alice at wonderland.lit" type="unsubscribed"/>})
+      stream.nodes.first.must_equal expected
+    end
+
+    it 'sends a roster set to the interested resources with subscription none' do
+      subject.process
+      recipient.nodes.size.must_equal 1
+
+      query = %q{<query xmlns="jabber:iq:roster"><item jid="hatter at wonderland.lit" subscription="none" from_diaspora="false"/></query>}
+      expected = node(%Q{<iq to="alice at wonderland.lit/tea" type="set">#{query}</iq>})
+      recipient.nodes.first.remove_attribute('id') # id is random
+      recipient.nodes.first.must_equal expected
+    end
+  end
+end
diff --git a/test/stanza/pubsub/create_test.rb b/test/stanza/pubsub/create_test.rb
new file mode 100644
index 0000000..02010c6
--- /dev/null
+++ b/test/stanza/pubsub/create_test.rb
@@ -0,0 +1,116 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::PubSub::Create do
+  subject      { Vines::Stanza::PubSub::Create.new(xml, stream) }
+  let(:user)   { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :nodes, :user
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+    stream.config = config
+    stream.user = user
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) { create('') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      stream.expect :domain, 'wonderland.lit'
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to bare server domain' do
+    let(:xml) { create('wonderland.lit') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a non-pubsub component' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) { create('bogus.wonderland.lit') }
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when attempting to create multiple nodes' do
+    let(:xml) { create('games.wonderland.lit', true) }
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when attempting to create duplicate nodes' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) { create('games.wonderland.lit') }
+
+    it 'raises a conflict stanza error' do
+      pubsub.expect :node?, true, ['game_13']
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::Conflict
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when given a valid stanza' do
+    let(:xml) { create('games.wonderland.lit') }
+    let(:expected) { result(user.jid, 'games.wonderland.lit') }
+
+    it 'sends an iq result stanza to sender' do
+      subject.process
+      stream.nodes.size.must_equal 1
+      stream.nodes.first.must_equal expected
+      stream.verify
+    end
+  end
+
+  private
+
+  def create(to, multiple=false)
+    extra_create = "<create node='game_14'/>" if multiple
+    body = %Q{
+      <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+        <create node='game_13'/>
+        #{extra_create}
+      </pubsub>}
+    iq(type: 'set', to: to, id: 42, body: body)
+  end
+
+  def result(to, from)
+    body = '<pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="game_13"/></pubsub>'
+    iq(from: from, id: 42, to: to, type: 'result', body: body)
+  end
+end
diff --git a/test/stanza/pubsub/delete_test.rb b/test/stanza/pubsub/delete_test.rb
new file mode 100644
index 0000000..2ec2894
--- /dev/null
+++ b/test/stanza/pubsub/delete_test.rb
@@ -0,0 +1,169 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::PubSub::Delete do
+  subject      { Vines::Stanza::PubSub::Delete.new(xml, stream) }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :domain, :nodes, :user
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+    stream.config = config
+    stream.domain = 'wonderland.lit'
+    stream.user = alice
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a bare server domain jid' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a non-pubsub address' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='bogus.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when stanza contains multiple delete elements' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+            <delete node='game_14'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when deleting a missing node' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises an item-not-found stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+      stream.verify
+    end
+  end
+
+  describe 'when valid stanza is received' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <delete node='game_13'/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    let(:result) { node(%Q{<iq from="games.wonderland.lit" id="42" to="#{alice.jid}" type="result"/>}) }
+
+    let(:broadcast) do
+      node(%q{
+        <message>
+          <event xmlns="http://jabber.org/protocol/pubsub#event">
+            <delete node="game_13"/>
+          </event>
+        </message>})
+    end
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :publish, nil, ['game_13', broadcast]
+      pubsub.expect :delete_node, nil, ['game_13']
+    end
+
+    it 'broadcasts the delete to subscribers' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+      stream.verify
+      pubsub.verify
+    end
+
+    it 'sends a result stanza to sender' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+      stream.nodes.size.must_equal 1
+      stream.nodes.first.must_equal result
+    end
+  end
+end
diff --git a/test/stanza/pubsub/publish_test.rb b/test/stanza/pubsub/publish_test.rb
new file mode 100644
index 0000000..08b190c
--- /dev/null
+++ b/test/stanza/pubsub/publish_test.rb
@@ -0,0 +1,309 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::PubSub::Publish do
+  subject      { Vines::Stanza::PubSub::Publish.new(xml, stream) }
+  let(:user)   { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :nodes, :user
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+    stream.config = config
+    stream.user = user
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) { publish('') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      stream.expect(:domain, 'wonderland.lit')
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to bare server domain' do
+    let(:xml) { publish('wonderland.lit') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a non-pubsub component' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) { publish('bogus.wonderland.lit') }
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when publishing to multiple nodes' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <publish node='game_13'>
+              <item id='item_42'>
+                <entry xmlns='http://www.w3.org/2005/Atom'>
+                  <title>Test</title>
+                  <summary>This is a summary.</summary>
+                </entry>
+              </item>
+            </publish>
+            <publish node='game_13'>
+              <item id='item_42'>
+                <entry xmlns='http://www.w3.org/2005/Atom'>
+                  <title>Test</title>
+                  <summary>This is a summary.</summary>
+                </entry>
+              </item>
+            </publish>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when publishing multiple items' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <publish node='game_13'>
+              <item id='item_42'>
+                <entry xmlns='http://www.w3.org/2005/Atom'>
+                  <title>Test</title>
+                  <summary>This is a summary.</summary>
+                </entry>
+              </item>
+              <item id="item_43">bad</item>
+            </publish>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a bad-request stanza error' do
+      pubsub.expect :node?, true, ['game_13']
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when publishing one item with multiple payloads' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <publish node='game_13'>
+              <item id='item_42'>
+                <entry xmlns='http://www.w3.org/2005/Atom'>
+                  <title>Test</title>
+                  <summary>This is a summary.</summary>
+                </entry>
+                <entry>bad</entry>
+              </item>
+            </publish>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a bad-request stanza error' do
+      pubsub.expect :node?, true, ['game_13']
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when publishing with no payload' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+        <iq type='set' to='games.wonderland.lit' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <publish node='game_13'>
+              <item id='item_42'>
+              </item>
+            </publish>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a bad-request stanza error' do
+      pubsub.expect :node?, true, ['game_13']
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when publishing to a missing node' do
+    let(:xml) { publish('games.wonderland.lit') }
+
+    it 'raises an item-not-found stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+      stream.verify
+    end
+  end
+
+  describe 'when publishing an item without an id' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) { publish('games.wonderland.lit', '') }
+    let(:broadcast) { message_broadcast('') }
+    let(:response) do
+      node(%q{
+        <iq from="games.wonderland.lit" id="42" to="alice at wonderland.lit/tea" type="result">
+          <pubsub xmlns="http://jabber.org/protocol/pubsub">
+            <publish node="game_13">
+              <item/>
+            </publish>
+          </pubsub>
+        </iq>})
+    end
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      def pubsub.published; @published; end
+      def pubsub.publish(node, message)
+        @published ||= []
+        @published << [node, message]
+      end
+    end
+
+    it 'generates an item id in the response' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+      stream.verify
+      pubsub.verify
+      stream.nodes.size.must_equal 1
+
+      # id is random
+      item = stream.nodes.first.xpath('ns:pubsub/ns:publish/ns:item',
+        'ns' => 'http://jabber.org/protocol/pubsub').first
+      item['id'].wont_be_nil
+      item.remove_attribute('id')
+      stream.nodes.first.must_equal response
+    end
+
+    it 'broadcasts the message with the generated item id' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+      stream.verify
+      pubsub.verify
+      stream.nodes.size.must_equal 1
+
+      published_node, published_message = *pubsub.published[0]
+      published_node.must_equal 'game_13'
+      # id is random
+      item = published_message.xpath('ns:event/ns:items/ns:item',
+        'ns' => 'http://jabber.org/protocol/pubsub#event').first
+      item['id'].wont_be_nil
+      item.remove_attribute('id')
+      published_message.must_equal broadcast
+    end
+  end
+
+  describe 'when publishing a valid stanza' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) { publish('games.wonderland.lit') }
+    let(:response) { result(user.jid, 'games.wonderland.lit') }
+    let(:broadcast) { message_broadcast('item_42') }
+
+    it 'broadcasts and returns result to sender' do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :publish, nil, ['game_13', broadcast]
+
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+
+      stream.nodes.size.must_equal 1
+      stream.nodes.first.must_equal response
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  private
+
+  def message_broadcast(item_id)
+    item_id = (item_id.nil? || item_id.empty?) ?  ' ' : " id='#{item_id}' "
+    node(%Q{
+      <message>
+        <event xmlns="http://jabber.org/protocol/pubsub#event">
+          <items node="game_13">
+            <item#{item_id}publisher="alice at wonderland.lit/tea">
+              <entry xmlns="http://www.w3.org/2005/Atom">
+                <title>Test</title>
+                <summary>This is a summary.</summary>
+              </entry>
+            </item>
+          </items>
+        </event>
+      </message>})
+  end
+
+  def publish(to, item_id='item_42')
+    item_id = "id='#{item_id}'" unless item_id.nil? || item_id.empty?
+    body = %Q{
+      <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+        <publish node='game_13'>
+          <item #{item_id}>
+            <entry xmlns='http://www.w3.org/2005/Atom'>
+              <title>Test</title>
+              <summary>This is a summary.</summary>
+            </entry>
+          </item>
+        </publish>
+      </pubsub>}
+    iq(type: 'set', to: to, id: 42, body: body)
+  end
+
+  def result(to, from)
+    iq(from: from, id: 42, to: to, type: 'result')
+  end
+end
diff --git a/test/stanza/pubsub/subscribe_test.rb b/test/stanza/pubsub/subscribe_test.rb
new file mode 100644
index 0000000..2a97238
--- /dev/null
+++ b/test/stanza/pubsub/subscribe_test.rb
@@ -0,0 +1,205 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::PubSub::Subscribe do
+  subject      { Vines::Stanza::PubSub::Subscribe.new(xml, stream) }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :domain, :nodes, :user
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+    stream.config = config
+    stream.user = alice
+    stream.domain = 'wonderland.lit'
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) do
+      node(%q{
+        <iq type='set' id='42'>
+          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+            <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+          </pubsub>
+        </iq>
+      })
+    end
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a bare server domain' do
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    it 'raises a feature-not-implemented stanza error' do
+        -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a non-pubsub address' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='bogus.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    it 'routes rather than handle locally' do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when stanza contains multiple subscribe elements' do
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='games.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+              <subscribe node='game_14' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when stanza is missing a subscribe element' do
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='games.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    it 'raises an item-not-found stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+      stream.verify
+    end
+  end
+
+  describe 'when attempting to subscribe to a node twice' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='games.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :subscribed?, true, ['game_13', alice.jid]
+    end
+
+    it 'raises a policy-violation stanza error' do
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::PolicyViolation
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when subscribing with an illegal jid' do
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='games.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="not_alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when subscribing with a valid stanza' do
+    let(:xml) do
+      node(%q{
+          <iq type='set' to='games.wonderland.lit' id='42'>
+            <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+              <subscribe node='game_13' jid="alice at wonderland.lit/tea"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    let(:expected) do
+      node(%q{
+        <iq from="games.wonderland.lit" id="42" to="alice at wonderland.lit/tea" type="result">
+            <pubsub xmlns="http://jabber.org/protocol/pubsub">
+              <subscription node="game_13" jid="alice at wonderland.lit/tea" subscription="subscribed"/>
+            </pubsub>
+          </iq>
+        })
+    end
+
+    let(:pubsub) { MiniTest::Mock.new }
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :subscribed?, false, ['game_13', alice.jid]
+      pubsub.expect :subscribe, nil, ['game_13', alice.jid]
+    end
+
+    it 'writes a result stanza to the stream' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+
+      stream.verify
+      pubsub.verify
+      stream.nodes.size.must_equal 1
+      stream.nodes.first.must_equal expected
+    end
+  end
+end
diff --git a/test/stanza/pubsub/unsubscribe_test.rb b/test/stanza/pubsub/unsubscribe_test.rb
new file mode 100644
index 0000000..35ecd4d
--- /dev/null
+++ b/test/stanza/pubsub/unsubscribe_test.rb
@@ -0,0 +1,148 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza::PubSub::Unsubscribe do
+  subject      { Vines::Stanza::PubSub::Unsubscribe.new(xml, stream) }
+  let(:user)   { Vines::User.new(jid: 'alice at wonderland.lit/tea') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+        pubsub 'games'
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config, :nodes, :user
+      def write(node)
+        @nodes ||= []
+        @nodes << node
+      end
+    end
+    stream.config = config
+    stream.user = user
+  end
+
+  describe 'when missing a to address' do
+    let(:xml) { unsubscribe('') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      stream.expect :domain, 'wonderland.lit'
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to bare server domain' do
+    let(:xml) { unsubscribe('wonderland.lit') }
+
+    it 'raises a feature-not-implemented stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::FeatureNotImplemented
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a non-pubsub component' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) { unsubscribe('bogus.wonderland.lit') }
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.process
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when attempting to unsubscribe from multiple nodes' do
+    let(:xml) { unsubscribe('games.wonderland.lit', true) }
+
+    it 'raises a bad-request stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::BadRequest
+      stream.verify
+    end
+  end
+
+  describe 'when unsubscribing from a missing node' do
+    let(:xml) { unsubscribe('games.wonderland.lit') }
+
+    it 'raises an item-not-found stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::ItemNotFound
+      stream.verify
+    end
+  end
+
+  describe 'when unsubscribing without a subscription' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) { unsubscribe('games.wonderland.lit') }
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :subscribed?, false, ['game_13', user.jid]
+    end
+
+    it 'raises an unexpected-request stanza error' do
+      subject.stub :pubsub, pubsub do
+        -> { subject.process }.must_raise Vines::StanzaErrors::UnexpectedRequest
+      end
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  describe 'when unsubscribing an illegal jid' do
+    let(:xml) { unsubscribe('games.wonderland.lit', false, 'not_alice at wonderland.lit/tea') }
+
+    it 'raises a forbidden stanza error' do
+      -> { subject.process }.must_raise Vines::StanzaErrors::Forbidden
+      stream.verify
+    end
+  end
+
+  describe 'when given a valid stanza' do
+    let(:pubsub) { MiniTest::Mock.new }
+    let(:xml) { unsubscribe('games.wonderland.lit') }
+    let(:expected) { result(user.jid, 'games.wonderland.lit') }
+
+    before do
+      pubsub.expect :node?, true, ['game_13']
+      pubsub.expect :subscribed?, true, ['game_13', user.jid]
+      pubsub.expect :unsubscribe, nil, ['game_13', user.jid]
+    end
+
+    it 'sends an iq result stanza to sender' do
+      subject.stub :pubsub, pubsub do
+        subject.process
+      end
+
+      stream.nodes.size.must_equal 1
+      stream.nodes.first.must_equal expected
+      stream.verify
+      pubsub.verify
+    end
+  end
+
+  private
+
+  def unsubscribe(to, multiple=false, jid=user.jid)
+    extra = "<unsubscribe node='game_14' jid='#{jid}'/>" if multiple
+    body = %Q{
+      <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+        <unsubscribe node='game_13' jid="#{jid}"/>
+        #{extra}
+      </pubsub>}
+    iq(type: 'set', to: to, id: 42, body: body)
+  end
+
+  def result(to, from)
+    iq(from: from, id: 42, to: to, type: 'result')
+  end
+end
diff --git a/test/stanza_test.rb b/test/stanza_test.rb
new file mode 100644
index 0000000..b004735
--- /dev/null
+++ b/test/stanza_test.rb
@@ -0,0 +1,85 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stanza do
+  subject      { Vines::Stanza::Message.new(xml, stream) }
+  let(:alice)  { Vines::JID.new('alice at wonderland.lit/tea') }
+  let(:romeo)  { Vines::JID.new('romeo at verona.lit/balcony') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  describe 'when stanza contains no addresses' do
+    let(:xml) { node(%Q{<message>hello!</message>}) }
+
+    it 'validates them as nil' do
+      subject.validate_to.must_be_nil
+      subject.validate_from.must_be_nil
+      stream.verify
+    end
+  end
+
+  describe 'when stanza contains valid addresses' do
+    let(:xml) { node(%Q{<message from="#{alice}" to="#{romeo}">hello!</message>}) }
+
+    it 'validates and returns JID objects' do
+      subject.validate_to.must_equal romeo
+      subject.validate_from.must_equal alice
+      stream.verify
+    end
+  end
+
+  describe 'when stanza contains invalid addresses' do
+    let(:xml) { node(%Q{<message from="a lice at wonderland.lit" to="romeo at v erona.lit">hello!</message>}) }
+
+    it 'raises a jid-malformed stanza error' do
+      -> { subject.validate_to }.must_raise Vines::StanzaErrors::JidMalformed
+      -> { subject.validate_from }.must_raise Vines::StanzaErrors::JidMalformed
+      stream.verify
+    end
+  end
+
+  describe 'when receiving a non-routable stanza type' do
+    let(:xml) { node('<auth/>') }
+
+    it 'handles locally rather than routing' do
+      subject.local?.must_equal true
+      stream.verify
+    end
+  end
+
+  describe 'when stanza is missing a to address' do
+    let(:xml) { node(%Q{<message>hello!</message>}) }
+
+    it 'handles locally rather than routing' do
+      subject.local?.must_equal true
+      stream.verify
+    end
+  end
+
+  describe 'when stanza is addressed to a local jid' do
+    let(:xml) { node(%Q{<message to="#{alice}">hello!</message>}) }
+
+    it 'handles locally rather than routing' do
+      stream.expect :config, config
+      subject.local?.must_equal true
+      stream.verify
+    end
+  end
+
+  describe 'when stanza is addressed to a remote jid' do
+    let(:xml) { node(%Q{<message to="#{romeo}">hello!</message>}) }
+
+    it 'is not considered a local stanza' do
+      stream.expect :config, config
+      subject.local?.must_equal false
+      stream.verify
+    end
+  end
+end
diff --git a/test/storage/local_test.rb b/test/storage/local_test.rb
new file mode 100644
index 0000000..b848e92
--- /dev/null
+++ b/test/storage/local_test.rb
@@ -0,0 +1,59 @@
+# encoding: UTF-8
+
+require 'storage_tests'
+require 'test_helper'
+
+describe Vines::Storage::Local do
+  include StorageTests
+
+  DIR = Dir.mktmpdir
+
+  def setup
+    Dir.mkdir(DIR) unless File.exists?(DIR)
+    %w[user vcard fragment].each do |d|
+      Dir.mkdir(File.join(DIR, d))
+    end
+
+    files = {
+      :empty      => "#{DIR}/user/empty at wonderland.lit",
+      :no_pass    => "#{DIR}/user/no_password at wonderland.lit",
+      :clear_pass => "#{DIR}/user/clear_password at wonderland.lit",
+      :bcrypt     => "#{DIR}/user/bcrypt_password at wonderland.lit",
+      :full       => "#{DIR}/user/full at wonderland.lit",
+      :vcard      => "#{DIR}/vcard/full at wonderland.lit",
+      :fragment   => "#{DIR}/fragment/full at wonderland.lit-#{StorageTests::FRAGMENT_ID}"
+    }
+    File.open(files[:empty], 'w') {|f| f.write('') }
+    File.open(files[:no_pass], 'w') {|f| f.write('foo: bar') }
+    File.open(files[:clear_pass], 'w') {|f| f.write('password: secret') }
+    File.open(files[:bcrypt], 'w') {|f| f.write("password: #{BCrypt::Password.create('secret')}") }
+    File.open(files[:full], 'w') do |f|
+      f.puts("password: #{BCrypt::Password.create('secret')}")
+      f.puts("name: Tester")
+      f.puts("roster:")
+      f.puts("  contact1 at wonderland.lit:")
+      f.puts("    name: Contact1")
+      f.puts("    groups: [Group1, Group2]")
+      f.puts("  contact2 at wonderland.lit:")
+      f.puts("    name: Contact2")
+      f.puts("    groups: [Group3, Group4]")
+    end
+    File.open(files[:vcard], 'w') {|f| f.write(StorageTests::VCARD.to_xml) }
+    File.open(files[:fragment], 'w') {|f| f.write(StorageTests::FRAGMENT.to_xml) }
+  end
+
+  def teardown
+    FileUtils.remove_entry_secure(DIR)
+  end
+
+  def storage
+    Vines::Storage::Local.new { dir DIR }
+  end
+
+  def test_init
+    assert_raises(RuntimeError) { Vines::Storage::Local.new {} }
+    assert_raises(RuntimeError) { Vines::Storage::Local.new { dir 'bogus' } }
+    assert_raises(RuntimeError) { Vines::Storage::Local.new { dir '/sbin' } }
+    Vines::Storage::Local.new { dir DIR } # shouldn't raise an error
+  end
+end
diff --git a/test/storage/mock_redis.rb b/test/storage/mock_redis.rb
new file mode 100644
index 0000000..6b4f4aa
--- /dev/null
+++ b/test/storage/mock_redis.rb
@@ -0,0 +1,97 @@
+# encoding: UTF-8
+
+# A mock redis storage implementation that saves data to an in-memory Hash.
+class MockRedis
+  attr_reader :db
+
+  # Mimic em-hiredis behavior.
+  def self.defer(method)
+    old = instance_method(method)
+    define_method method do |*args, &block|
+      result = old.bind(self).call(*args)
+      deferred = EM::DefaultDeferrable.new
+      deferred.callback(&block) if block
+      EM.next_tick { deferred.succeed(result) }
+      deferred
+    end
+  end
+
+  def initialize
+    @db = {}
+  end
+
+  def del(key)
+    @db.delete(key)
+  end
+  defer :del
+
+  def get(key)
+    @db[key]
+  end
+  defer :get
+
+  def set(key, value)
+    @db[key] = value
+  end
+  defer :set
+
+  def hget(key, field)
+    @db[key][field] rescue nil
+  end
+  defer :hget
+
+  def hdel(key, field)
+    @db[key].delete(field) rescue nil
+  end
+  defer :hdel
+
+  def hgetall(key)
+    (@db[key] || {}).map do |k, v|
+      [k, v]
+    end.flatten
+  end
+  defer :hgetall
+
+  def hset(key, field, value)
+    @db[key] ||= {}
+    @db[key][field] = value
+  end
+  defer :hset
+
+  def hmset(key, *args)
+    @db[key] = Hash[*args]
+  end
+  defer :hmset
+
+  def sadd(key, obj)
+    @db[key] ||= Set.new
+    @db[key] << obj
+  end
+  defer :sadd
+
+  def srem(key, obj)
+    @db[key].delete(obj) rescue nil
+  end
+  defer :srem
+
+  def smembers
+    @db[key].to_a rescue []
+  end
+  defer :smembers
+
+  def flushdb
+    @db.clear
+  end
+  defer :flushdb
+
+  def multi
+    @transaction = true
+  end
+  defer :multi
+
+  def exec
+    raise 'transaction must start with multi' unless @transaction
+    @transaction = false
+  end
+  defer :exec
+end
\ No newline at end of file
diff --git a/test/storage/null_test.rb b/test/storage/null_test.rb
new file mode 100644
index 0000000..a0cf662
--- /dev/null
+++ b/test/storage/null_test.rb
@@ -0,0 +1,29 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Storage::Null do
+  before do
+    @storage = Vines::Storage::Null.new
+    @user = Vines::User.new(jid: 'alice at wonderland.lit')
+  end
+
+  def test_find_user_returns_nil
+    assert_nil @storage.find_user(@user.jid)
+    @storage.save_user(@user)
+    assert_nil @storage.find_user(@user.jid)
+  end
+
+  def test_find_vcard_returns_nil
+    assert_nil @storage.find_vcard(@user.jid)
+    @storage.save_vcard(@user.jid, 'card')
+    assert_nil @storage.find_vcard(@user.jid)
+  end
+
+  def test_find_fragment_returns_nil
+    assert_nil @storage.find_fragment(@user.jid, 'node')
+    @storage.save_fragment(@user.jid, 'node')
+    assert_nil @storage.find_fragment(@user.jid, 'node')
+    nil
+  end
+end
diff --git a/test/storage/sql_schema.rb b/test/storage/sql_schema.rb
new file mode 100644
index 0000000..1279c7b
--- /dev/null
+++ b/test/storage/sql_schema.rb
@@ -0,0 +1,186 @@
+module SqlSchema
+  def fibered
+    EM.run do
+      Fiber.new do
+        yield
+        EM.stop
+      end.resume
+    end
+  end
+
+  def fragment_id
+    Digest::SHA1.hexdigest("characters:urn:wonderland")
+  end
+
+  def fragment
+    Nokogiri::XML(%q{
+      <characters xmlns="urn:wonderland">
+        <character>Alice</character>
+      </characters>
+    }.strip).root
+  end
+
+  def db_file
+    Rails.application.config.database_configuration["development"]["database"]
+  end
+
+  def storage
+    Vines::Storage::Sql.new
+  end
+
+  def create_schema(args={})
+    args[:force] ||= false
+
+    # disable stdout logging
+    ActiveRecord::Migration.verbose = false
+    ActiveRecord::Schema.define do
+      create_table "people", :force => true do |t|
+        t.string   "guid",                                     :null => false
+        t.text     "url",                                      :null => false
+        t.string   "diaspora_handle",                          :null => false
+        t.text     "serialized_public_key",                    :null => false
+        t.integer  "owner_id"
+        t.datetime "created_at",                               :null => false
+        t.datetime "updated_at",                               :null => false
+        t.boolean  "closed_account",        :default => false
+        t.integer  "fetch_status",          :default => 0
+      end
+
+      add_index "people", ["diaspora_handle"], :name => "index_people_on_diaspora_handle", :unique => true
+      add_index "people", ["guid"], :name => "index_people_on_guid", :unique => true
+      add_index "people", ["owner_id"], :name => "index_people_on_owner_id", :unique => true
+
+      create_table "profiles", force: true do |t|
+        t.string   "diaspora_handle"
+        t.string   "first_name",       limit: 127
+        t.string   "last_name",        limit: 127
+        t.string   "image_url"
+        t.string   "image_url_small"
+        t.string   "image_url_medium"
+        t.date     "birthday"
+        t.string   "gender"
+        t.text     "bio"
+        t.boolean  "searchable",                   default: true,  null: false
+        t.integer  "person_id",                                    null: false
+        t.datetime "created_at",                                   null: false
+        t.datetime "updated_at",                                   null: false
+        t.string   "location"
+        t.string   "full_name",        limit: 70
+        t.boolean  "nsfw",                         default: false
+      end
+
+      add_index "profiles", ["full_name", "searchable"], name: "index_profiles_on_full_name_and_searchable", using: :btree
+      add_index "profiles", ["full_name"], name: "index_profiles_on_full_name", using: :btree
+      add_index "profiles", ["person_id"], name: "index_profiles_on_person_id", using: :btree
+
+      create_table "aspects", :force => true do |t|
+        t.string   "name",                               :null => false
+        t.integer  "user_id",                            :null => false
+        t.datetime "created_at",                         :null => false
+        t.datetime "updated_at",                         :null => false
+        t.boolean  "contacts_visible", :default => true, :null => false
+        t.integer  "order_id"
+        t.boolean  "chat_enabled",     default: false
+      end
+      
+      add_index "aspects", ["user_id", "contacts_visible"], :name => "index_aspects_on_user_id_and_contacts_visible"
+      add_index "aspects", ["user_id"], :name => "index_aspects_on_user_id"
+
+      create_table "aspect_memberships", :force => true do |t|
+        t.integer  "aspect_id",  :null => false
+        t.integer  "contact_id", :null => false
+        t.datetime "created_at", :null => false
+        t.datetime "updated_at", :null => false
+      end
+      
+      add_index "aspect_memberships", ["aspect_id", "contact_id"], :name => "index_aspect_memberships_on_aspect_id_and_contact_id", :unique => true
+      add_index "aspect_memberships", ["aspect_id"], :name => "index_aspect_memberships_on_aspect_id"
+      add_index "aspect_memberships", ["contact_id"], :name => "index_aspect_memberships_on_contact_id"
+
+      create_table "contacts", :force => true do |t|
+        t.integer  "user_id",                       :null => false
+        t.integer  "person_id",                     :null => false
+        t.datetime "created_at",                    :null => false
+        t.datetime "updated_at",                    :null => false
+        t.boolean  "sharing",    :default => false, :null => false
+        t.boolean  "receiving",  :default => false, :null => false
+      end
+      
+      add_index "contacts", ["person_id"], :name => "index_contacts_on_person_id"
+      add_index "contacts", ["user_id", "person_id"], :name => "index_contacts_on_user_id_and_person_id", :unique => true
+
+      create_table "chat_contacts", :force => true do |t|
+        t.integer "user_id", :null => false
+        t.string "jid", :null => false
+        t.string "name"
+        t.string "ask", :limit => 128
+        t.string "subscription", :limit => 128, :null => false
+        t.text "groups"
+      end
+      
+      add_index "chat_contacts", ["user_id", "jid"], :name => "index_chat_contacts_on_user_id_and_jid", :unique => true
+      
+      create_table "chat_fragments", :force => true do |t|
+        t.integer "user_id", :null => false
+        t.string "root", :limit => 256, :null => false
+        t.string "namespace", :limit => 256, :null => false
+        t.text "xml", :null => false
+      end
+      
+      add_index "chat_fragments", ["user_id"], :name => "index_chat_fragments_on_user_id", :unique => true
+
+      create_table "chat_offline_messages", force: true do |t|
+        t.string "from", null: false
+        t.string "to", null: false
+        t.text "message", null: false
+        t.datetime "created_at", null: false
+      end
+
+      create_table "users", :force => true do |t|
+        t.string   "username"
+        t.text     "serialized_private_key"
+        t.boolean  "getting_started",                                   :default => true,  :null => false
+        t.boolean  "disable_mail",                                      :default => false, :null => false
+        t.string   "language"
+        t.string   "email",                                             :default => "",    :null => false
+        t.string   "encrypted_password",                                :default => "",    :null => false
+        t.string   "invitation_token",                   :limit => 60
+        t.datetime "invitation_sent_at"
+        t.string   "reset_password_token"
+        t.datetime "remember_created_at"
+        t.integer  "sign_in_count",                                     :default => 0
+        t.datetime "current_sign_in_at"
+        t.datetime "last_sign_in_at"
+        t.string   "current_sign_in_ip"
+        t.string   "last_sign_in_ip"
+        t.datetime "created_at",                                                           :null => false
+        t.datetime "updated_at",                                                           :null => false
+        t.string   "invitation_service",                 :limit => 127
+        t.string   "invitation_identifier",              :limit => 127
+        t.integer  "invitation_limit"
+        t.integer  "invited_by_id"
+        t.string   "invited_by_type"
+        t.string   "authentication_token",               :limit => 30
+        t.string   "unconfirmed_email"
+        t.string   "confirm_email_token",                :limit => 30
+        t.datetime "locked_at"
+        t.boolean  "show_community_spotlight_in_stream",                :default => true,  :null => false
+        t.boolean  "auto_follow_back",                                  :default => false
+        t.integer  "auto_follow_back_aspect_id"
+        t.text     "hidden_shareables"
+        t.datetime "reset_password_sent_at"
+        t.datetime "last_seen"
+      end
+      
+      add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
+      add_index "users", ["email"], :name => "index_users_on_email"
+      add_index "users", ["invitation_service", "invitation_identifier"], :name => "index_users_on_invitation_service_and_invitation_identifier", :unique => true
+      add_index "users", ["invitation_token"], :name => "index_users_on_invitation_token"
+      add_index "users", ["username"], :name => "index_users_on_username", :unique => true
+      
+      #add_foreign_key "aspect_memberships", "aspects", name: "aspect_memberships_aspect_id_fk", dependent: :delete
+      #add_foreign_key "aspect_memberships", "contacts", name: "aspect_memberships_contact_id_fk", dependent: :delete
+      #add_foreign_key "contacts", "people", name: "contacts_person_id_fk", dependent: :delete
+    end
+  end
+end
diff --git a/test/storage/sql_test.rb b/test/storage/sql_test.rb
new file mode 100644
index 0000000..c2c969d
--- /dev/null
+++ b/test/storage/sql_test.rb
@@ -0,0 +1,290 @@
+# encoding: UTF-8
+
+require "test_helper"
+require "storage/sql_schema"
+
+module Diaspora
+  class Application < Rails::Application
+    def config.database_configuration
+      {
+        "development" => {
+          "adapter" => "sqlite3",
+          "database" => "test.db"
+        }
+      }
+    end
+  end
+end
+
+describe Vines::Storage::Sql do
+  include SqlSchema
+
+  def setup
+    _config = Vines::Config.configure do
+      max_offline_msgs 1
+
+      host "wonderland.lit" do
+        storage :fs do
+          dir Dir.tmpdir
+        end
+      end
+    end
+
+    @test_user = {
+      name: "test", url: "http://remote.host/",
+      image_url: "http://path.to/image.png", jid: "test at local.host", email: "test at test.de",
+      password: "$2a$10$c2G6rHjGeamQIOFI0c1/b.4mvFBw4AfOtgVrAkO1QPMuAyporj5e6", # pppppp
+      token: "1234"
+    }
+
+    return if File.exist?(db_file)
+    # create sql schema
+    storage && create_schema(force: true)
+
+    Vines::Storage::Sql::User.new(
+      username: @test_user[:name], email: @test_user[:email],
+      encrypted_password: @test_user[:password],
+      authentication_token: @test_user[:token]
+    ).save
+    Vines::Storage::Sql::Person.new(
+      owner_id: 1,
+      guid: "1697a4b0198901321e9b10e6ba921ce9",
+      url: @test_user[:url],
+      serialized_public_key: "some pub key",
+      diaspora_handle: @test_user[:jid]
+    ).save
+    Vines::Storage::Sql::Profile.new(
+      person_id: 1,
+      last_name: "Hirsch",
+      first_name: "Harry",
+      diaspora_handle: @test_user[:jid],
+      image_url: @test_user[:image_url]
+    ).save
+    Vines::Storage::Sql::Contact.new(
+      user_id: 1, person_id: 1,
+      sharing: true, receiving: true
+    ).save
+    Vines::Storage::Sql::Aspect.new(
+      user_id: 1, name: "without_chat",
+      contacts_visible: true, order_id: nil
+    ).save
+    Vines::Storage::Sql::AspectMembership.new(
+      # without_chat
+      aspect_id: 1, contact_id: 1
+    ).save
+  end
+
+  after do
+    # since we create the database once we
+    # have to reset it after every test run
+    Vines::Storage::Sql::ChatOfflineMessage.all.each(&:destroy)
+  end
+
+  def test_save_message
+    fibered do
+      db = storage
+
+      assert_nil db.save_message("", "", "")
+      assert_nil db.save_message("dude at valid@jid", "dude2 at valid.jid", "")
+      assert_nil db.save_message("dude at valid@jid", "", "test")
+      assert_nil db.save_message("", "dude2 at valid.jid", "test")
+
+      db.save_message(@test_user[:jid], "someone at inthe.void", "test")
+
+      msgs = Vines::Storage::Sql::ChatOfflineMessage.where(:to => "someone at inthe.void")
+      assert_equal 1, msgs.count
+      assert_equal "someone at inthe.void", msgs.first.to
+      assert_equal @test_user[:jid], msgs.first.from
+      assert_equal "test", msgs.first.message
+
+      db.save_message("someone at else.void", "someone at inthe.void", "test2")
+
+      msgs = Vines::Storage::Sql::ChatOfflineMessage.where(:to => "someone at inthe.void")
+      assert_equal 1, msgs.count # due max limit equals one (see max_offline_msgs)
+      assert_equal "someone at inthe.void", msgs.first.to
+      assert_equal "someone at else.void", msgs.first.from
+      assert_equal "test2", msgs.first.message # should be latest message
+    end
+  end
+
+  def test_find_avatar_by_jid
+    fibered do
+      db = storage
+
+      assert_nil db.find_avatar_by_jid("")
+      assert_nil db.find_avatar_by_jid("someone at inthe.void")
+
+      image_path = db.find_avatar_by_jid(@test_user[:jid])
+      assert_equal @test_user[:image_url], image_path
+    end
+  end
+
+  def test_find_messages
+    fibered do
+      db = storage
+
+      assert_nil db.find_messages("")
+      assert_equal 0, db.find_messages("someone at inthe.void").keys.count
+
+      Vines::Storage::Sql::ChatOfflineMessage.new(
+        from: @test_user[:jid], to: "someone at inthe.void", message: "test"
+      ).save
+
+      msgs = db.find_messages("someone at inthe.void")
+      assert_equal 1, msgs.keys.count
+      msgs.each {|_, msg|
+        assert_equal "someone at inthe.void", msg[:to]
+        assert_equal @test_user[:jid], msg[:from]
+        assert_equal "test", msg[:message]
+      }
+    end
+  end
+
+  def test_destroy_message
+    fibered do
+      db = storage
+      Vines::Storage::Sql::ChatOfflineMessage.new(
+        from: @test_user[:jid], to: "someone at inthe.void", message: "test"
+      ).save
+      Vines::Storage::Sql::ChatOfflineMessage.all.each do |com|
+        db.destroy_message(com.id)
+      end
+      count = Vines::Storage::Sql::ChatOfflineMessage.count(id: 1)
+      assert_equal 0, count
+    end
+  end
+
+  def test_aspect_chat_enabled
+    fibered do
+      db = storage
+      user = db.find_user(@test_user[:jid])
+      assert_equal 0, user.roster.length
+
+      aspect = Vines::Storage::Sql::Aspect.where(:id => 1)
+      aspect.update_all(name: "with_chat", chat_enabled: true)
+      user = db.find_user(@test_user[:jid])
+      assert_equal 1, user.roster.length
+    end
+  end
+
+  def test_save_user
+    fibered do
+      db = storage
+      user = Vines::User.new(jid: "test2 at test.de",
+        name: "test2 at test.de", password: "secret")
+      db.save_user(user)
+      assert_nil db.find_user("test2 at test.de")
+    end
+  end
+
+  def test_find_user
+    fibered do
+      db = storage
+      user = db.find_user(nil)
+      assert_nil user
+
+      user = db.find_user(@test_user[:jid])
+      assert (user != nil), "no user found"
+      assert_equal @test_user[:name], user.name
+
+      user.roster do |contact|
+        assert_equal "Harry Hirsch", contact.name
+      end
+
+      user = db.find_user(Vines::JID.new(@test_user[:jid]))
+      assert (user != nil), "no user found"
+      assert_equal @test_user[:name], user.name
+
+      user = db.find_user(Vines::JID.new("#{@test_user[:jid]}/resource"))
+      assert (user != nil), "no user found"
+      assert_equal @test_user[:name], user.name
+    end
+  end
+
+  def test_authenticate
+    fibered do
+      db = storage
+
+      assert_nil db.authenticate(nil, nil)
+      assert_nil db.authenticate(nil, "secret")
+      assert_nil db.authenticate("bogus", nil)
+
+      # user credential auth
+      pepper = "065eb8798b181ff0ea2c5c16aee0ff8b70e04e2ee6bd6e08b49da46924223e39127d5335e466207d42bf2a045c12be5f90e92012a4f05f7fc6d9f3c875f4c95b"
+      user = db.authenticate(@test_user[:jid], "pppppp#{pepper}")
+      assert (user != nil), "no user found"
+      assert_equal @test_user[:name], user.name
+
+      # user token auth
+      user = db.authenticate(@test_user[:jid], @test_user[:token])
+      assert (user != nil), "no user found"
+      assert_equal @test_user[:name], user.name
+    end
+  end
+
+  def test_find_vcard
+    fibered do
+      db = storage
+      xml = db.find_vcard(@test_user[:jid])
+      assert (xml != nil), "no vcard found"
+
+      doc = node(xml)
+      assert_equal "Harry Hirsch", doc.search("FN").text
+      assert_equal "Harry", doc.search("GIVEN").text
+      assert_equal "Hirsch", doc.search("FAMILY").text
+      assert_equal @test_user[:url], doc.search("URL").text
+      assert_equal @test_user[:image_url], doc.search("EXTVAL").text
+    end
+  end
+
+  def test_save_vcard
+    fibered do
+      assert_nil storage.save_vcard(@test_user[:jid], "<vCard></vCard>")
+    end
+  end
+
+  def test_find_fragment
+    skip("not working probably")
+
+    fibered do
+      db = storage
+      root = Nokogiri::XML(%(<characters xmlns="urn:wonderland"/>)).root
+      bad_name = Nokogiri::XML(%(<not_characters xmlns="urn:wonderland"/>)).root
+      bad_ns = Nokogiri::XML(%(<characters xmlns="not:wonderland"/>)).root
+
+      node = db.find_fragment(nil, nil)
+      assert_nil node
+
+      node = db.find_fragment("full at wonderland.lit", bad_name)
+      assert_nil node
+
+      node = db.find_fragment("full at wonderland.lit", bad_ns)
+      assert_nil node
+
+      node = db.find_fragment("full at wonderland.lit", root)
+      assert (node != nil), "node should include fragment"
+      assert_equal fragment.to_s, node.to_s
+
+      node = db.find_fragment(Vines::JID.new("full at wonderland.lit"), root)
+      assert (node != nil), "node should include fragment"
+      assert_equal fragment.to_s, node.to_s
+
+      node = db.find_fragment(Vines::JID.new("full at wonderland.lit/resource"), root)
+      assert (node != nil), "node should include fragment"
+      assert_equal fragment.to_s, node.to_s
+    end
+  end
+
+  def test_save_fragment
+    skip("not working probably")
+
+    fibered do
+      db = storage
+      root = Nokogiri::XML(%(<characters xmlns="urn:wonderland"/>)).root
+      db.save_fragment("test at test.de/resource1", fragment)
+      node = db.find_fragment("test at test.de", root)
+      assert (node != nil), "node should include fragment"
+      assert_equal fragment.to_s, node.to_s
+    end
+  end
+end
diff --git a/test/storage/storage_tests.rb b/test/storage/storage_tests.rb
new file mode 100644
index 0000000..7403a7b
--- /dev/null
+++ b/test/storage/storage_tests.rb
@@ -0,0 +1,182 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+# Mixin methods for storage implementation test classes. The behavioral
+# tests are the same regardless of implementation so share those methods
+# here.
+module StorageTests
+  FRAGMENT_ID = Digest::SHA1.hexdigest("characters:urn:wonderland")
+
+  FRAGMENT = Nokogiri::XML(%q{
+    <characters xmlns="urn:wonderland">
+      <character>Alice</character>
+    </characters>
+  }.strip).root
+
+  VCARD = Nokogiri::XML(%q{
+    <vCard xmlns="vcard-temp">
+      <FN>Alice in Wonderland</FN>
+    </vCard>
+  }.strip).root
+
+  class EMLoop
+    def initialize
+      EM.run do
+        Fiber.new do
+          yield
+          EM.stop
+        end.resume
+      end
+    end
+  end
+
+  def test_authenticate
+    EMLoop.new do
+      db = storage
+      assert_nil db.authenticate(nil, nil)
+      assert_nil db.authenticate(nil, 'secret')
+      assert_nil db.authenticate('bogus', nil)
+      assert_nil db.authenticate('bogus', 'secret')
+      assert_nil db.authenticate('empty at wonderland.lit', 'secret')
+      assert_nil db.authenticate('no_password at wonderland.lit', 'secret')
+      assert_nil db.authenticate('clear_password at wonderland.lit', 'secret')
+
+      user = db.authenticate('bcrypt_password at wonderland.lit', 'secret')
+      refute_nil user
+      assert_equal('bcrypt_password at wonderland.lit', user.jid.to_s)
+
+      user = db.authenticate('full at wonderland.lit', 'secret')
+      refute_nil user
+      assert_equal 'Tester', user.name
+      assert_equal 'full at wonderland.lit', user.jid.to_s
+
+      assert_equal 2, user.roster.length
+      assert_equal 'contact1 at wonderland.lit', user.roster[0].jid.to_s
+      assert_equal 'Contact1', user.roster[0].name
+      assert_equal 2, user.roster[0].groups.length
+      assert_equal 'Group1', user.roster[0].groups[0]
+      assert_equal 'Group2', user.roster[0].groups[1]
+
+      assert_equal 'contact2 at wonderland.lit', user.roster[1].jid.to_s
+      assert_equal 'Contact2', user.roster[1].name
+      assert_equal 2, user.roster[1].groups.length
+      assert_equal 'Group3', user.roster[1].groups[0]
+      assert_equal 'Group4', user.roster[1].groups[1]
+    end
+  end
+
+  def test_find_user
+    EMLoop.new do
+      db = storage
+      user = db.find_user(nil)
+      assert_nil user
+
+      user = db.find_user('full at wonderland.lit')
+      refute_nil user
+      assert_equal 'full at wonderland.lit', user.jid.to_s
+
+      user = db.find_user(Vines::JID.new('full at wonderland.lit'))
+      refute_nil user
+      assert_equal 'full at wonderland.lit', user.jid.to_s
+
+      user = db.find_user(Vines::JID.new('full at wonderland.lit/resource'))
+      refute_nil user
+      assert_equal 'full at wonderland.lit', user.jid.to_s
+    end
+  end
+
+  def test_save_user
+    EMLoop.new do
+      db = storage
+      user = Vines::User.new(
+        :jid => 'save_user at domain.tld/resource1',
+        :name => 'Save User',
+        :password => 'secret')
+      user.roster << Vines::Contact.new(
+        :jid => 'contact1 at domain.tld/resource2',
+        :name => 'Contact 1')
+      db.save_user(user)
+      user = db.find_user('save_user at domain.tld')
+      refute_nil user
+      assert_equal 'save_user at domain.tld', user.jid.to_s
+      assert_equal 'Save User', user.name
+      assert_equal 1, user.roster.length
+      assert_equal 'contact1 at domain.tld', user.roster[0].jid.to_s
+      assert_equal 'Contact 1', user.roster[0].name
+    end
+  end
+
+  def test_find_vcard
+    EMLoop.new do
+      db = storage
+      card = db.find_vcard(nil)
+      assert_nil card
+
+      card = db.find_vcard('full at wonderland.lit')
+      refute_nil card
+      assert_equal VCARD, card
+
+      card = db.find_vcard(Vines::JID.new('full at wonderland.lit'))
+      refute_nil card
+      assert_equal VCARD, card
+
+      card = db.find_vcard(Vines::JID.new('full at wonderland.lit/resource'))
+      refute_nil card
+      assert_equal VCARD, card
+    end
+  end
+
+  def test_save_vcard
+    EMLoop.new do
+      db = storage
+      db.save_user(Vines::User.new(:jid => 'save_user at domain.tld'))
+      db.save_vcard('save_user at domain.tld/resource1', VCARD)
+      card = db.find_vcard('save_user at domain.tld')
+      refute_nil card
+      assert_equal VCARD, card
+    end
+  end
+
+  def test_find_fragment
+    EMLoop.new do
+      db = storage
+      root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
+      bad_name = Nokogiri::XML(%q{<not_characters xmlns="urn:wonderland"/>}).root
+      bad_ns = Nokogiri::XML(%q{<characters xmlns="not:wonderland"/>}).root
+
+      node = db.find_fragment(nil, nil)
+      assert_nil node
+
+      node = db.find_fragment('full at wonderland.lit', bad_name)
+      assert_nil node
+
+      node = db.find_fragment('full at wonderland.lit', bad_ns)
+      assert_nil node
+
+      node = db.find_fragment('full at wonderland.lit', root)
+      refute_nil node
+      assert_equal FRAGMENT, node
+
+      node = db.find_fragment(Vines::JID.new('full at wonderland.lit'), root)
+      refute_nil node
+      assert_equal FRAGMENT, node
+
+      node = db.find_fragment(Vines::JID.new('full at wonderland.lit/resource'), root)
+      refute_nil node
+      assert_equal FRAGMENT, node
+    end
+  end
+
+  def test_save_fragment
+    EMLoop.new do
+      db = storage
+      root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
+      db.save_user(Vines::User.new(:jid => 'save_user at domain.tld'))
+      db.save_fragment('save_user at domain.tld/resource1', FRAGMENT)
+      node = db.find_fragment('save_user at domain.tld', root)
+      refute_nil node
+      assert_equal FRAGMENT, node
+    end
+  end
+end
diff --git a/test/store_test.rb b/test/store_test.rb
new file mode 100644
index 0000000..4be98e8
--- /dev/null
+++ b/test/store_test.rb
@@ -0,0 +1,164 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Store do
+  let(:dir) { 'conf/certs' }
+  let(:domain_pair) { certificate('wonderland.lit') }
+  let(:wildcard_pair) { certificate('*.wonderland.lit') }
+  subject { Vines::Store.new(dir) }
+
+  before do
+    @files =
+      save('wonderland.lit', domain_pair) +
+      save('wildcard.lit', wildcard_pair) +
+      save('duplicate.lit', domain_pair)
+  end
+
+  after do
+    @files.each do |name|
+      File.delete(name) if File.exists?(name)
+    end
+  end
+
+  describe 'creating a store' do
+    it 'parses certificate files' do
+      refute subject.certs.empty?
+      assert_equal OpenSSL::X509::Certificate, subject.certs.first.class
+    end
+
+    it 'ignores expired certificates' do
+      assert subject.certs.all? {|c| c.not_after > Time.new }
+    end
+
+    it 'does not raise an error for duplicate certificates' do
+      assert Vines::Store.new(dir)
+    end
+  end
+
+  describe 'files_for_domain' do
+    it 'handles invalid input' do
+      assert_nil subject.files_for_domain(nil)
+      assert_nil subject.files_for_domain('')
+    end
+
+    it 'finds files by name' do
+      refute_nil subject.files_for_domain('wonderland.lit')
+      cert, key = subject.files_for_domain('wonderland.lit')
+      assert_certificate_matches_key cert, key
+      assert_equal 'wonderland.lit.crt', File.basename(cert)
+      assert_equal 'wonderland.lit.key', File.basename(key)
+    end
+
+    it 'finds files for wildcard' do
+      refute_nil subject.files_for_domain('foo.wonderland.lit')
+      cert, key = subject.files_for_domain('foo.wonderland.lit')
+      assert_certificate_matches_key cert, key
+      assert_equal 'wildcard.lit.crt', File.basename(cert)
+      assert_equal 'wildcard.lit.key', File.basename(key)
+    end
+  end
+
+  describe 'trusted?' do
+    it 'does not trust malformed certificates' do
+      refute subject.trusted?('bogus')
+    end
+
+    it 'does not trust unsigned certificates' do
+      pair = certificate('something.lit')
+      refute subject.trusted?(pair.cert)
+    end
+  end
+
+  describe 'domain?' do
+    it 'handles invalid input' do
+      pair = certificate('wonderland.lit')
+      refute subject.domain?(nil, nil)
+      refute subject.domain?(pair.cert, nil)
+      refute subject.domain?(pair.cert, '')
+      refute subject.domain?(nil, '')
+      assert subject.domain?(pair.cert, 'wonderland.lit')
+    end
+
+    it 'verifies certificate subject domains' do
+      pair = certificate('wonderland.lit')
+      refute subject.domain?(pair.cert, 'bogus')
+      refute subject.domain?(pair.cert, 'www.wonderland.lit')
+      assert subject.domain?(pair.cert, 'wonderland.lit')
+    end
+
+    it 'verifies certificate subject alt domains' do
+      pair = certificate('wonderland.lit', 'www.wonderland.lit')
+      refute subject.domain?(pair.cert, 'bogus')
+      refute subject.domain?(pair.cert, 'tea.wonderland.lit')
+      assert subject.domain?(pair.cert, 'www.wonderland.lit')
+      assert subject.domain?(pair.cert, 'wonderland.lit')
+    end
+
+    it 'verifies certificate wildcard domains' do
+      pair = certificate('wonderland.lit', '*.wonderland.lit')
+      refute subject.domain?(pair.cert, 'bogus')
+      refute subject.domain?(pair.cert, 'one.two.wonderland.lit')
+      assert subject.domain?(pair.cert, 'tea.wonderland.lit')
+      assert subject.domain?(pair.cert, 'www.wonderland.lit')
+      assert subject.domain?(pair.cert, 'wonderland.lit')
+    end
+  end
+
+  private
+
+  # A public certificate + private key pair.
+  Pair = Struct.new(:cert, :key)
+
+  def assert_certificate_matches_key(cert, key)
+    refute_nil cert
+    refute_nil key
+    cert = OpenSSL::X509::Certificate.new(File.read(cert))
+    key = OpenSSL::PKey::RSA.new(File.read(key))
+    assert_equal cert.public_key.to_s, key.public_key.to_s
+  end
+
+  def certificate(domain, altname=nil)
+    # Use small key so tests are fast.
+    key = OpenSSL::PKey::RSA.generate(512)
+
+    name = OpenSSL::X509::Name.parse("/C=US/ST=Colorado/L=Denver/O=Test/CN=#{domain}")
+    cert = OpenSSL::X509::Certificate.new
+    cert.version = 2
+    cert.subject = name
+    cert.issuer = name
+    cert.serial = Time.now.to_i
+    cert.public_key = key.public_key
+    cert.not_before = Time.now
+    cert.not_after = Time.now + 3600
+
+    if altname
+      factory = OpenSSL::X509::ExtensionFactory.new
+      factory.subject_certificate = cert
+      factory.issuer_certificate = cert
+      cert.extensions = [
+        %w[subjectKeyIdentifier hash],
+        %w[subjectAltName] << [domain, altname].map {|n| "DNS:#{n}" }.join(',')
+      ].map {|k, v| factory.create_ext(k, v) }
+    end
+
+    cert.sign key, OpenSSL::Digest::SHA1.new
+
+    Pair.new(cert.to_pem, key.to_pem)
+  end
+
+  # Write the domain's certificate and private key files to the filesystem for
+  # the store to use.
+  #
+  # domain - The domain name String to use in the file name (e.g. wonderland.lit).
+  # pair   - The Pair containing the public certificate and private key data.
+  #
+  # Returns a String Array of file names that were written.
+  def save(domain, pair)
+    crt = File.expand_path("#{domain}.crt", dir)
+    key = File.expand_path("#{domain}.key", dir)
+    File.open(crt, 'w') {|f| f.write(pair.cert) }
+    File.open(key, 'w') {|f| f.write(pair.key) }
+    [crt, key]
+  end
+end
diff --git a/test/stream/client/auth_test.rb b/test/stream/client/auth_test.rb
new file mode 100644
index 0000000..1cb0f41
--- /dev/null
+++ b/test/stream/client/auth_test.rb
@@ -0,0 +1,137 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Client::Auth do
+  # disable logging for tests
+  Class.new.extend(Vines::Log).log.level = Logger::FATAL
+
+  class MockStorage < Vines::Storage
+    def initialize(raise_error=false)
+      @raise_error = raise_error
+    end
+
+    def authenticate(username, password)
+      username = username.to_s
+      raise 'temp auth fail' if @raise_error
+      user = Vines::User.new(jid: 'alice at wonderland.lit')
+      users = {'alice at wonderland.lit' => 'secr3t'}
+      (users.key?(username) && (users[username] == password)) ? user : nil
+    end
+
+    def find_user(jid)
+    end
+
+    def save_user(user)
+    end
+  end
+
+  subject      { Vines::Stream::Client::Auth.new(stream) }
+  let(:stream) { MiniTest::Mock.new }
+
+  describe 'error handling' do
+    it 'rejects invalid element' do
+      node = node('<bogus/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    end
+
+    it 'rejects invalid element in sasl namespace' do
+      node = node(%Q{<bogus xmlns="#{Vines::NAMESPACES[:sasl]}"/>})
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    end
+
+    it 'rejects auth elements missing sasl namespace' do
+      node = node('<auth/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    end
+
+    it 'rejects auth element with invalid namespace' do
+      node = node('<auth xmlns="bogus"/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    end
+
+    it 'rejects valid auth element missing mechanism' do
+      stream.expect :error, nil, [Vines::SaslErrors::InvalidMechanism]
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}">tokens</auth>})
+      subject.node(node)
+      stream.verify
+    end
+
+    it 'rejects valid auth element with invalid mechanism' do
+      stream.expect :error, nil, [Vines::SaslErrors::InvalidMechanism]
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="bogus">tokens</auth>})
+      subject.node(node)
+      stream.verify
+    end
+  end
+
+  describe 'plain auth' do
+    it 'rejects valid mechanism missing base64 text' do
+      stream.expect :error, nil, [Vines::SaslErrors::MalformedRequest]
+      node = plain('')
+      subject.node(node)
+      stream.verify
+    end
+
+    it 'rejects invalid base64 text' do
+      stream.expect :error, nil, [Vines::SaslErrors::IncorrectEncoding]
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      node = plain('tokens')
+      subject.node(node)
+      stream.verify
+    end
+
+    it 'rejects invalid password' do
+      stream.expect :storage, MockStorage.new
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      node = plain(Base64.strict_encode64("\x00alice\x00bogus"))
+      subject.node(node)
+      stream.verify
+    end
+
+    it 'passes with valid password' do
+      user = Vines::User.new(jid: 'alice at wonderland.lit')
+      stream.expect :reset, nil
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :storage, MockStorage.new
+      stream.expect :user=, nil, [user]
+      stream.expect :write, nil, [%Q{<success xmlns="#{Vines::NAMESPACES[:sasl]}"/>}]
+      stream.expect :advance, nil, [Vines::Stream::Client::BindRestart]
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      node = plain(Base64.strict_encode64("\x00alice\x00secr3t"))
+      subject.node(node)
+      stream.verify
+    end
+
+    it 'raises policy-violation after max auth attempts is reached' do
+      stream.expect :domain, 'wonderland.lit'
+      stream.expect :storage, MockStorage.new
+      node = -> { plain(Base64.strict_encode64("\x00alice\x00bogus")) }
+
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
+      subject.node(node.call)
+      stream.verify
+
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
+      subject.node(node.call)
+      stream.verify
+
+      stream.expect :authentication_mechanisms, ['PLAIN']
+      stream.expect :error, nil, [Vines::StreamErrors::PolicyViolation]
+      subject.node(node.call)
+      stream.verify
+    end
+  end
+
+  private
+
+  def plain(authzid)
+    node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">#{authzid}</auth>})
+  end
+end
diff --git a/test/stream/client/ready_test.rb b/test/stream/client/ready_test.rb
new file mode 100644
index 0000000..39f84eb
--- /dev/null
+++ b/test/stream/client/ready_test.rb
@@ -0,0 +1,47 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Client::Ready do
+  STANZAS = []
+
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Client::Ready.new(@stream, nil)
+    def @state.to_stanza(node)
+      if node.name == 'bogus'
+        nil
+      else
+        stanza = MiniTest::Mock.new
+        stanza.expect(:process, nil)
+        stanza.expect(:validate_to, nil)
+        stanza.expect(:validate_from, nil)
+        STANZAS << stanza
+        stanza
+      end
+    end
+  end
+
+  after do
+    STANZAS.clear
+  end
+
+  it 'processes a valid node' do
+    node = node('<message/>')
+    @state.node(node)
+    assert_equal 1, STANZAS.size
+    assert STANZAS.map {|s| s.verify }.all?
+  end
+
+  it 'raises an unsupported-stanza-type stream error for invalid node' do
+    node = node('<bogus/>')
+    assert_raises(Vines::StreamErrors::UnsupportedStanzaType) { @state.node(node) }
+    assert STANZAS.empty?
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/client/session_test.rb b/test/stream/client/session_test.rb
new file mode 100644
index 0000000..04e8bef
--- /dev/null
+++ b/test/stream/client/session_test.rb
@@ -0,0 +1,27 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Client::Session do
+  subject       { Vines::Stream::Client::Session.new(stream) }
+  let(:another) { Vines::Stream::Client::Session.new(stream) }
+  let(:stream)  { OpenStruct.new(config: nil) }
+
+  describe 'session equality checks' do
+    it 'uses class in equality check' do
+      (subject <=> 42).must_be_nil
+    end
+
+    it 'is equal to itself' do
+      assert subject == subject
+      assert subject.eql?(subject)
+      assert subject.hash == subject.hash
+    end
+
+    it 'is not equal to another session' do
+      refute subject == another
+      refute subject.eql?(another)
+      refute subject.hash == another.hash
+    end
+  end
+end
diff --git a/test/stream/component/handshake_test.rb b/test/stream/component/handshake_test.rb
new file mode 100644
index 0000000..91dea81
--- /dev/null
+++ b/test/stream/component/handshake_test.rb
@@ -0,0 +1,52 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Component::Handshake do
+  subject      { Vines::Stream::Component::Handshake.new(stream) }
+  let(:stream) { MiniTest::Mock.new }
+
+  describe 'when invalid element is received' do
+    it 'raises a not-authorized stream error' do
+      node = node('<message/>')
+       -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    end
+  end
+
+  describe 'when handshake with no text is received' do
+    it 'raises a not-authorized stream error' do
+      stream.expect :secret, 'secr3t'
+      node = node('<handshake/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+      stream.verify
+    end
+  end
+
+  describe 'when handshake with invalid secret is received' do
+    it 'raises a not-authorized stream error' do
+      stream.expect :secret, 'secr3t'
+      node = node('<handshake>bogus</handshake>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+      stream.verify
+    end
+  end
+
+  describe 'when good handshake is received' do
+    let(:router) { MiniTest::Mock.new }
+
+    before do
+      router.expect :<<, nil, [stream]
+      stream.expect :router, router
+      stream.expect :secret, 'secr3t'
+      stream.expect :write, nil, ['<handshake/>']
+      stream.expect :advance, nil, [Vines::Stream::Component::Ready.new(stream)]
+    end
+
+    it 'completes the handshake and advances the stream into the ready state' do
+      node = node('<handshake>secr3t</handshake>')
+      subject.node(node)
+      stream.verify
+      router.verify
+    end
+  end
+end
diff --git a/test/stream/component/ready_test.rb b/test/stream/component/ready_test.rb
new file mode 100644
index 0000000..59d0ddb
--- /dev/null
+++ b/test/stream/component/ready_test.rb
@@ -0,0 +1,103 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Component::Ready do
+  subject      { Vines::Stream::Component::Ready.new(stream, nil) }
+  let(:alice)  { Vines::User.new(jid: 'alice at tea.wonderland.lit') }
+  let(:hatter) { Vines::User.new(jid: 'hatter at wonderland.lit') }
+  let(:stream) { MiniTest::Mock.new }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  before do
+    class << stream
+      attr_accessor :config
+    end
+    stream.config = config
+  end
+
+  describe 'when missing to and from addresses' do
+    it 'raises an improper-addressing stream error' do
+      node = node('<message/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
+      stream.verify
+    end
+  end
+
+  describe 'when missing from address' do
+    it 'raises an improper-addressing stream error' do
+      node = node(%q{<message to="hatter at wonderland.lit"/>})
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
+      stream.verify
+    end
+  end
+
+  describe 'when missing to address' do
+    it 'raises an improper-addressing stream error' do
+      node = node(%q{<message from="alice at tea.wonderland.lit"/>})
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
+      stream.verify
+    end
+  end
+
+  describe 'when from address domain does not match component domain' do
+    it 'raises and invalid-from stream error' do
+      stream.expect :remote_domain, 'tea.wonderland.lit'
+      node = node(%q{<message from="alice at bogus.wonderland.lit" to="hatter at wonderland.lit"/>})
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::InvalidFrom
+      stream.verify
+    end
+  end
+
+  describe 'when unrecognized element is received' do
+    it 'raises an unsupported-stanza-type stream error' do
+      node = node('<bogus/>')
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::UnsupportedStanzaType
+      stream.verify
+    end
+  end
+
+  describe 'when addressed to a remote jid' do
+    let(:router) { MiniTest::Mock.new }
+    let(:xml) { node(%q{<message from="alice at tea.wonderland.lit" to="romeo at verona.lit"/>}) }
+
+    before do
+      router.expect :route, nil, [xml]
+      stream.expect :remote_domain, 'tea.wonderland.lit'
+      stream.expect :user=, nil, [alice]
+      stream.expect :router, router
+    end
+
+    it 'routes rather than handle locally' do
+      subject.node(xml)
+      stream.verify
+      router.verify
+    end
+  end
+
+  describe 'when addressed to a local jid' do
+    let(:recipient) { MiniTest::Mock.new }
+    let(:xml) { node(%q{<message from="alice at tea.wonderland.lit" to="hatter at wonderland.lit"/>}) }
+
+    before do
+      recipient.expect :user, hatter
+      recipient.expect :write, nil, [xml]
+      stream.expect :remote_domain, 'tea.wonderland.lit'
+      stream.expect :user=, nil, [alice]
+      stream.expect :user, alice
+      stream.expect :connected_resources, [recipient], [hatter.jid]
+    end
+
+    it 'sends the message to the connected stream' do
+      subject.node(xml)
+      stream.verify
+      recipient.verify
+    end
+  end
+end
diff --git a/test/stream/component/start_test.rb b/test/stream/component/start_test.rb
new file mode 100644
index 0000000..3257fdd
--- /dev/null
+++ b/test/stream/component/start_test.rb
@@ -0,0 +1,39 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Component::Start do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Component::Start.new(@stream)
+  end
+
+  it 'raises not-authorized stream error for invalid element' do
+    node = node('<message/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  it 'raises not-authorized stream error for missing stream namespace' do
+    node = node('<stream:stream/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  it 'raises not-authorized stream error for invalid stream namespace' do
+    node = node('<stream:stream xmlns="bogus"/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  it 'advances the state machine for valid stream header' do
+    node = node(%q{<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:component:accept" to="tea.wonderland.lit"/>})
+    @stream.expect(:start, nil, [node])
+    @stream.expect(:advance, nil, [Vines::Stream::Component::Handshake.new(@stream)])
+    @state.node(node)
+    assert @stream.verify
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/http/auth_test.rb b/test/stream/http/auth_test.rb
new file mode 100644
index 0000000..911a3bd
--- /dev/null
+++ b/test/stream/http/auth_test.rb
@@ -0,0 +1,70 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Http::Auth do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Http::Auth.new(@stream, nil)
+  end
+
+  def test_missing_body_raises_error
+    node = node('<presence type="unavailable"/>')
+    @stream.expect(:valid_session?, true, [nil])
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  def test_body_with_missing_namespace_raises_error
+    node = node('<body rid="42" sid="12"/>')
+    @stream.expect(:valid_session?, true, ['12'])
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  def test_missing_rid_raises_error
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" sid="12"/>')
+    @stream.expect(:valid_session?, true, ['12'])
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  def test_invalid_session_raises_error
+    @stream.expect(:valid_session?, false, ['12'])
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+    assert @stream.verify
+  end
+
+  def test_empty_body_raises_error
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
+    @stream.expect(:valid_session?, true, ['12'])
+    @stream.expect(:parse_body, [], [node])
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+    assert @stream.verify
+  end
+
+  def test_body_with_two_children_raises_error
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"><message/><message/></body>')
+    message = node('<message/>')
+    @stream.expect(:valid_session?, true, ['12'])
+    @stream.expect(:parse_body, [message, message], [node])
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+    assert @stream.verify
+  end
+
+  def test_valid_body_processes
+    auth = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN"/>})
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"></body>')
+    node << auth
+    @stream.expect(:valid_session?, true, ['12'])
+    @stream.expect(:parse_body, [auth], [node])
+    # this error means we correctly called the parent method Client#node
+    @stream.expect(:error, nil, [Vines::SaslErrors::MalformedRequest.new])
+    @state.node(node)
+    assert @stream.verify
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/http/ready_test.rb b/test/stream/http/ready_test.rb
new file mode 100644
index 0000000..50c0614
--- /dev/null
+++ b/test/stream/http/ready_test.rb
@@ -0,0 +1,86 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Http::Ready do
+  subject      { Vines::Stream::Http::Ready.new(stream, nil) }
+  let(:stream) { MiniTest::Mock.new }
+  let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit') }
+  let(:hatter) { Vines::User.new(jid: 'hatter at wonderland.lit') }
+  let(:config) do
+    Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+    end
+  end
+
+  it "raises when body element is missing" do
+    node = node('<presence type="unavailable"/>')
+    stream.expect :valid_session?, true, [nil]
+    -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+  end
+
+  it "raises when namespace is missing" do
+    node = node('<body rid="42" sid="12"/>')
+    stream.expect :valid_session?, true, ['12']
+    -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+  end
+
+  it "raises when rid attribute is missing" do
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" sid="12"/>')
+    stream.expect :valid_session?, true, ['12']
+    -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+  end
+
+  it "raises when session id is invalid" do
+    stream.expect :valid_session?, false, ['12']
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
+    -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
+    stream.verify
+  end
+
+  it "processes when body element is empty" do
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
+    stream.expect :valid_session?, true, ['12']
+    stream.expect :parse_body, [], [node]
+    subject.node(node)
+    stream.verify
+  end
+
+  describe 'when receiving multiple stanzas in one body element' do
+    let(:recipient) { MiniTest::Mock.new }
+    let(:bogus) { node('<message type="bogus">raises stanza error</message>') }
+    let(:ok) { node('<message to="hatter at wonderland.lit">but processes this message</message>') }
+    let(:xml) { node(%Q{<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12">#{bogus}#{ok}</body>}) }
+    let(:raises) { Vines::Stanza.from_node(bogus, stream) }
+    let(:processes) { Vines::Stanza.from_node(ok, stream) }
+
+    before do
+      recipient.expect :user, hatter
+      recipient.expect :write, nil, [Vines::Stanza::Message]
+
+      stream.expect :valid_session?, true, ['12']
+      stream.expect :parse_body, [raises, processes], [xml]
+      stream.expect :error, nil, [Vines::StanzaErrors::BadRequest]
+      stream.expect :config, config
+      stream.expect :user, alice
+      stream.expect :connected_resources, [recipient], [hatter.jid]
+    end
+
+    it 'processes all stanzas' do
+      subject.node(xml)
+      stream.verify
+      recipient.verify
+    end
+  end
+
+  it "terminates the session" do
+    node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12" type="terminate"/>')
+    stream.expect :valid_session?, true, ['12']
+    stream.expect :parse_body, [], [node]
+    stream.expect :terminate, nil
+    subject.node(node)
+    stream.verify
+  end
+end
diff --git a/test/stream/http/request_test.rb b/test/stream/http/request_test.rb
new file mode 100644
index 0000000..830fc0f
--- /dev/null
+++ b/test/stream/http/request_test.rb
@@ -0,0 +1,194 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Http::Request do
+  PASSWORD = File.expand_path('../passwords').freeze
+  INDEX    = File.expand_path('index.html').freeze
+
+  before do
+    File.open(PASSWORD, 'w') {|f| f.puts '/etc/passwd contents' }
+    File.open(INDEX, 'w') {|f| f.puts 'index.html contents' }
+    @stream = MiniTest::Mock.new
+  end
+
+  after do
+    File.delete(PASSWORD)
+    File.delete(INDEX)
+  end
+
+  describe 'initialize' do
+    it 'copies request info from parser' do
+      headers = ['Host: wonderland.lit', 'Content-Type: text/html']
+      parser = parser('GET', '/blogs/12?ok=true', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
+
+      assert_equal request.headers, {'Host' => 'wonderland.lit', 'Content-Type' => 'text/html'}
+      assert_equal request.method, 'GET'
+      assert_equal request.path, '/blogs/12'
+      assert_equal request.url, '/blogs/12?ok=true'
+      assert_equal request.query, 'ok=true'
+      assert_equal request.body, '<html></html>'
+      assert @stream.verify
+    end
+  end
+
+  describe 'reply_with_file' do
+    it 'returns 404 file not found' do
+      headers = ['Host: wonderland.lit', 'Content-Type: text/html']
+      parser = parser('GET', '/blogs/12?ok=true', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
+
+      headers = [
+        "HTTP/1.1 404 Not Found",
+        "Content-Length: 0"
+      ].join("\r\n")
+
+      @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
+
+      request.reply_with_file(Dir.pwd)
+      assert @stream.verify
+    end
+
+    it 'prevents directory traversal with 404 response' do
+      headers = ['Host: wonderland.lit', 'Content-Type: text/html']
+      parser = parser('GET', '/../passwords', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
+
+      headers = [
+        "HTTP/1.1 404 Not Found",
+        "Content-Length: 0"
+      ].join("\r\n")
+
+      @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
+
+      request.reply_with_file(Dir.pwd)
+      assert @stream.verify
+    end
+
+    it 'serves index.html for directory request' do
+      headers = ['Host: wonderland.lit', 'Content-Type: text/html']
+      parser = parser('GET', '/?ok=true', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
+
+      mtime = File.mtime(INDEX).utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
+      headers = [
+        "HTTP/1.1 200 OK",
+        'Content-Type: text/html; charset="utf-8"',
+        "Content-Length: 20",
+        "Last-Modified: #{mtime}"
+      ].join("\r\n")
+
+      @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
+      @stream.expect(:stream_write, nil, ["index.html contents\n"])
+
+      request.reply_with_file(Dir.pwd)
+      assert @stream.verify
+    end
+
+    it 'redirects for missing trailing slash' do
+      headers = ['Host: wonderland.lit', 'Content-Type: text/html']
+      parser = parser('GET', '/http?ok=true', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
+
+      headers = [
+        "HTTP/1.1 301 Moved Permanently",
+        "Content-Length: 0",
+        "Location: http://wonderland.lit/http/?ok=true"
+      ].join("\r\n")
+
+      @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
+      # so the /http url above will work
+      request.reply_with_file(File.expand_path('../../', __FILE__))
+      assert @stream.verify
+    end
+  end
+
+  describe 'reply_to_options' do
+    it 'returns cors headers' do
+      headers = [
+        'Content-Type: text/xml',
+        'Host: wonderland.lit',
+        'Origin: remote.wonderland.lit',
+        'Access-Control-Request-Headers: Content-Type, Origin'
+      ]
+      parser = parser('OPTIONS', '/xmpp', headers)
+      request = Vines::Stream::Http::Request.new(@stream, parser, '')
+
+      headers = [
+        "HTTP/1.1 200 OK",
+        "Content-Length: 0",
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Allow-Methods: POST, GET, OPTIONS",
+        "Access-Control-Allow-Headers: Content-Type, Origin",
+        "Access-Control-Max-Age: 2592000"
+      ].join("\r\n")
+
+      @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
+      request.reply_to_options
+      assert @stream.verify
+    end
+  end
+
+  describe 'reply' do
+    it 'returns set-cookie header when vroute is defined' do
+      reply_with_cookie('v1')
+    end
+
+    it 'does not return set-cookie header when vroute is undefined' do
+      reply_with_cookie('')
+    end
+  end
+
+  private
+
+  # Create a parser that has completed one valid HTTP request.
+  #
+  # method  - The HTTP method String (e.g. 'GET', 'POST').
+  # url     - The request URL String (e.g. '/blogs/12?ok=true').
+  # headers - The optional Array of request headers.
+  #
+  # Returns an Http::Parser.
+  def parser(method, url, headers = [])
+    head = ["#{method} #{url} HTTP/1.1"].concat(headers).join("\r\n")
+    body = '<html></html>'
+    Http::Parser.new.tap do |parser|
+      parser << "%s\r\n\r\n%s" % [head, body]
+    end
+  end
+
+  def reply_with_cookie(cookie)
+    config = Vines::Config.new do
+      host 'wonderland.lit' do
+        storage(:fs) { dir Dir.tmpdir }
+      end
+      http '0.0.0.0', 5280 do
+        vroute cookie
+      end
+    end
+
+    headers = [
+      'Content-Type: text/xml',
+      'Host: wonderland.lit',
+      'Origin: remote.wonderland.lit'
+    ]
+    parser = parser('POST', '/xmpp', headers)
+
+    request = Vines::Stream::Http::Request.new(@stream, parser, '')
+    message = '<message>hello</message>'
+
+    headers = [
+      "HTTP/1.1 200 OK",
+      "Access-Control-Allow-Origin: *",
+      "Content-Type: application/xml",
+      "Content-Length: 24",
+    ]
+    headers << "Set-Cookie: vroute=#{cookie}; path=/; HttpOnly" unless cookie.empty?
+    headers = headers.join("\r\n")
+
+    @stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n#{message}"])
+    @stream.expect(:config, config)
+    request.reply(message, 'application/xml')
+    assert @stream.verify
+  end
+end
diff --git a/test/stream/http/sessions_test.rb b/test/stream/http/sessions_test.rb
new file mode 100644
index 0000000..ce83d98
--- /dev/null
+++ b/test/stream/http/sessions_test.rb
@@ -0,0 +1,49 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Http::Sessions do
+  class MockSessions < Vines::Stream::Http::Sessions
+    def start_timer
+      # do nothing
+    end
+  end
+
+  def setup
+    @sessions = MockSessions.new
+  end
+
+  def test_session_add_and_delete
+    session = "session"
+    assert_nil @sessions['42']
+    @sessions['42'] = session
+    assert_equal session, @sessions['42']
+    @sessions.delete('42')
+    assert_nil @sessions['42']
+  end
+
+  def test_access_singleton_through_class_methods
+    session = "session"
+    assert_nil MockSessions['42']
+    MockSessions['42'] = session
+    assert_equal session, MockSessions['42']
+    MockSessions.delete('42')
+    assert_nil MockSessions['42']
+  end
+
+  def test_cleanup
+    live = MiniTest::Mock.new
+    live.expect(:expired?, false)
+
+    dead = MiniTest::Mock.new
+    dead.expect(:expired?, true)
+    dead.expect(:close, nil)
+
+    @sessions['live'] = live
+    @sessions['dead'] = dead
+
+    @sessions.send(:cleanup)
+    assert live.verify
+    assert dead.verify
+  end
+end
diff --git a/test/stream/http/start_test.rb b/test/stream/http/start_test.rb
new file mode 100644
index 0000000..23ece79
--- /dev/null
+++ b/test/stream/http/start_test.rb
@@ -0,0 +1,50 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Http::Start do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Http::Start.new(@stream)
+  end
+
+  def test_missing_body_raises_error
+    node = node('<presence type="unavailable"/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  def test_body_with_missing_namespace_raises_error
+    node = node('<body rid="42" sid="12"/>')
+    assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+  end
+
+  def test_missing_session_starts_stream
+    EM.run do
+      node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
+      @stream.expect(:start, nil, [node])
+      @stream.expect(:advance, nil, [Vines::Stream::Http::Auth.new(@stream)])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    end
+  end
+
+  def test_valid_session_resumes_stream
+    EM.run do
+      node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="123"/>')
+      session = MiniTest::Mock.new
+      session.expect(:resume, nil, [@stream, node])
+      Vines::Stream::Http::Sessions['123'] = session
+      @state.node(node)
+      assert @stream.verify
+      assert session.verify
+      EM.stop
+    end
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/parser_test.rb b/test/stream/parser_test.rb
new file mode 100644
index 0000000..fc04979
--- /dev/null
+++ b/test/stream/parser_test.rb
@@ -0,0 +1,122 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::Parser do
+  STREAM_START = '<stream:stream to="wonderland.lit" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams">'.freeze
+
+  before do
+    @events = []
+    @parser = Vines::Stream::Parser.new.tap do |p|
+      p.stream_open  {|el| @events << el }
+      p.stream_close {     @events << :close }
+      p.stanza       {|el| @events << el }
+    end
+  end
+
+  def test_xpath_to_subclass
+    expected = []
+    stanzas = [
+      ['<message></message>', Vines::Stanza::Message],
+      ['<presence/>', Vines::Stanza::Presence],
+      ['<presence type="bogus"/>', Vines::Stanza::Presence],
+      ['<presence type="error"/>', Vines::Stanza::Presence::Error],
+      ['<presence type="probe"/>', Vines::Stanza::Presence::Probe],
+      ['<presence type="subscribe"/>', Vines::Stanza::Presence::Subscribe],
+      ['<presence type="subscribed"/>', Vines::Stanza::Presence::Subscribed],
+      ['<presence type="unavailable"/>', Vines::Stanza::Presence::Unavailable],
+      ['<presence type="unsubscribe"/>', Vines::Stanza::Presence::Unsubscribe],
+      ['<presence type="unsubscribed"/>', Vines::Stanza::Presence::Unsubscribed],
+      ['<iq id="42" type="get"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>', Vines::Stanza::Iq::Query::DiscoInfo],
+      ['<iq id="42" type="get"><query xmlns="http://jabber.org/protocol/disco#items"></query></iq>', Vines::Stanza::Iq::Query::DiscoItems],
+      ['<iq id="42" type="error"></iq>', Vines::Stanza::Iq::Error],
+      ['<iq id="42" type="get"><query xmlns="jabber:iq:private"/></iq>', Vines::Stanza::Iq::PrivateStorage],
+      ['<iq id="42" type="set"><query xmlns="jabber:iq:private"/></iq>', Vines::Stanza::Iq::PrivateStorage],
+      ['<iq id="42" type="get"><ping xmlns="urn:xmpp:ping"/></iq>', Vines::Stanza::Iq::Ping],
+      ['<iq id="42" type="result"></iq>', Vines::Stanza::Iq::Result],
+      ['<iq id="42" type="get"><query xmlns="jabber:iq:roster"/></iq>', Vines::Stanza::Iq::Query::Roster],
+      ['<iq id="42" type="set"><query xmlns="jabber:iq:roster"/></iq>', Vines::Stanza::Iq::Query::Roster],
+      ['<iq id="42" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>', Vines::Stanza::Iq::Session],
+      ['<iq id="42" type="get"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq::Vcard],
+      ['<iq type="get"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
+      ['<iq id="42"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
+      ['<iq><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
+      ['<bogus/>', NilClass],
+    ]
+    @parser << STREAM_START
+    stanzas.each do |stanza, klass|
+      @parser << stanza
+      expected << klass
+    end
+    @parser << '</stream:stream>'
+    assert_equal 'stream', @events.shift.name
+    assert_equal :close, @events.pop
+    assert_equal expected.size, @events.size
+    @events.each_with_index do |ev, ix|
+      assert_equal expected[ix], Vines::Stanza.from_node(ev, nil).class
+    end
+  end
+
+  def test_stream_namespace_with_default_prefix
+    @parser << STREAM_START
+    assert_equal 1, @events.size
+    stream = @events.shift
+    assert_equal 'stream', stream.name
+    refute_nil stream.namespace
+    assert_equal 'stream', stream.namespace.prefix
+    assert_equal 'http://etherx.jabber.org/streams', stream.namespace.href
+    expected = {'xmlns' => 'jabber:client', 'xmlns:stream' => 'http://etherx.jabber.org/streams'}
+    assert_equal expected, stream.namespaces
+  end
+
+  def test_stanzas_ignore_default_namespace
+    @parser << STREAM_START
+    @parser << '<message to="alice at wonderland.lit">hello!</message>'
+    assert_equal 2, @events.size
+    @events.shift # discard stream
+    msg = @events.shift
+    assert_equal 'message', msg.name
+    assert msg.namespaces.empty?
+    assert_nil msg.namespace
+  end
+
+  def test_nested_elements_have_namespace
+    @parser << STREAM_START
+    @parser << %q{
+      <iq from='alice at wonderland.lit/tea' id='42' type='set'>
+        <query xmlns='jabber:iq:roster'>
+          <item jid='hatter at wonderland.lit' name='Mad Hatter'>
+            <group>Tea Party</group>
+          </item>
+        </query>
+      </iq>
+    }
+    assert_equal 2, @events.size
+    @events.shift # discard stream
+    iq = @events.shift
+    assert_equal 'iq', iq.name
+    assert iq.namespaces.empty?
+    assert_nil iq.namespace
+
+    query = iq.elements.first
+    refute_nil query.namespace
+    assert_nil query.namespace.prefix
+    assert_equal 'jabber:iq:roster', query.namespace.href
+    expected = {'xmlns' => 'jabber:iq:roster'}
+    assert_equal expected, query.namespaces
+  end
+
+  def test_error_stanzas_have_stream_namespace
+    @parser << STREAM_START
+    @parser << '<stream:error><not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>'
+    assert_equal 2, @events.size
+    @events.shift # discard stream
+    error = @events.shift
+    assert_equal 'error', error.name
+    refute_nil error.namespace
+    assert_equal 'stream', error.namespace.prefix
+    assert_equal 'http://etherx.jabber.org/streams', error.namespace.href
+    expected = {'xmlns:stream' => 'http://etherx.jabber.org/streams'}
+    assert_equal expected, error.namespaces
+  end
+end
diff --git a/test/stream/sasl_test.rb b/test/stream/sasl_test.rb
new file mode 100644
index 0000000..2c35ec3
--- /dev/null
+++ b/test/stream/sasl_test.rb
@@ -0,0 +1,195 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::Stream::SASL do
+  subject       { Vines::Stream::SASL.new(stream) }
+  let(:stream)  { MiniTest::Mock.new }
+  let(:storage) { MiniTest::Mock.new }
+  let(:romeo)   { Vines::User.new(jid: 'romeo at verona.lit') }
+
+  before do
+    def subject.log
+      Class.new do
+        def method_missing(*args)
+          # do nothing
+        end
+      end.new
+    end
+  end
+
+  describe '#plain_auth' do
+    before do
+      stream.expect :domain, 'verona.lit'
+    end
+
+    it 'fails with empty input' do
+      -> { subject.plain_auth(nil) }.must_raise Vines::SaslErrors::IncorrectEncoding
+      -> { subject.plain_auth('') }.must_raise Vines::SaslErrors::NotAuthorized
+    end
+
+    it 'fails with plain text' do
+      -> { subject.plain_auth('bogus') }.must_raise Vines::SaslErrors::IncorrectEncoding
+    end
+
+    it 'fails with incorrectly encoded base64 text' do
+      -> { subject.plain_auth('=dmVyb25hLmxpdA==') }.must_raise Vines::SaslErrors::IncorrectEncoding
+      -> { subject.plain_auth("dmVyb25hLmxpdA==\n") }.must_raise Vines::SaslErrors::IncorrectEncoding
+    end
+
+    it 'fails when authzid does not match authcid username' do
+      encoded = Base64.strict_encode64("juliet at verona.lit\x00romeo\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::InvalidAuthzid
+      stream.verify
+    end
+
+    it 'fails when authzid does not match authcid domain' do
+      encoded = Base64.strict_encode64("romeo at wonderland.lit\x00romeo\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::InvalidAuthzid
+      stream.verify
+    end
+
+    it 'fails when username and password are missing' do
+      encoded = Base64.strict_encode64("\x00\x00")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+    end
+
+    it 'fails when username is missing' do
+      encoded = Base64.strict_encode64("\x00\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+    end
+
+    it 'fails when password is missing with delimiter' do
+      encoded = Base64.strict_encode64("\x00romeo\x00")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+    end
+
+    it 'fails when password is missing' do
+      encoded = Base64.strict_encode64("\x00romeo")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+    end
+
+    it 'fails with invalid jid' do
+      encoded = Base64.strict_encode64("\x00#{'a' * 1024}\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+      stream.verify
+    end
+
+    it 'fails with invalid password' do
+      storage.expect :authenticate, nil, [romeo.jid, 'secr3t']
+      stream.expect :storage, storage
+
+      encoded = Base64.strict_encode64("\x00romeo\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::NotAuthorized
+
+      stream.verify
+      storage.verify
+    end
+
+    it 'passes with valid password' do
+      storage.expect :authenticate, romeo, [romeo.jid, 'secr3t']
+      stream.expect :storage, storage
+
+      encoded = Base64.strict_encode64("\x00romeo\x00secr3t")
+      subject.plain_auth(encoded).must_equal romeo
+
+      stream.verify
+      storage.verify
+    end
+
+    it 'passes with valid password and unicode jid' do
+      user = Vines::User.new(jid: 'piñata at verona.lit')
+      storage.expect :authenticate, user, [user.jid, 'secr3t']
+      stream.expect :storage, storage
+
+      encoded = Base64.strict_encode64("\x00piñata\x00secr3t")
+      subject.plain_auth(encoded).must_equal user
+
+      stream.verify
+      storage.verify
+    end
+
+    it 'passes with valid password and authzid provided by strophe and blather' do
+      storage.expect :authenticate, romeo, [romeo.jid, 'secr3t']
+      stream.expect :storage, storage
+
+      encoded = Base64.strict_encode64("romeo at Verona.LIT\x00romeo\x00secr3t")
+      subject.plain_auth(encoded).must_equal romeo
+
+      stream.verify
+      storage.verify
+    end
+
+    it 'passes with valid password and authzid provided by smack' do
+      storage.expect :authenticate, romeo, [romeo.jid, 'secr3t']
+      stream.expect :storage, storage
+
+      encoded = Base64.strict_encode64("romeo\x00romeo\x00secr3t")
+      subject.plain_auth(encoded).must_equal romeo
+
+      stream.verify
+      storage.verify
+    end
+
+    it 'raises temporary-auth-failure when storage backend fails' do
+      storage = Class.new do
+        def authenticate(*args)
+          raise 'boom'
+        end
+      end.new
+
+      stream.expect :storage, storage
+      encoded = Base64.strict_encode64("\x00romeo\x00secr3t")
+      -> { subject.plain_auth(encoded) }.must_raise Vines::SaslErrors::TemporaryAuthFailure
+      stream.verify
+    end
+  end
+
+  describe '#external_auth' do
+    it 'fails with empty input' do
+      stream.expect :remote_domain, 'verona.lit'
+      -> { subject.external_auth(nil) }.must_raise Vines::SaslErrors::IncorrectEncoding
+      -> { subject.external_auth('') }.must_raise Vines::SaslErrors::InvalidAuthzid
+      stream.verify
+    end
+
+    it 'fails with plain text' do
+      -> { subject.external_auth('bogus') }.must_raise Vines::SaslErrors::IncorrectEncoding
+      stream.verify
+    end
+
+    it 'fails with incorrectly encoded base64 text' do
+      -> { subject.external_auth('=dmVyb25hLmxpdA==') }.must_raise Vines::SaslErrors::IncorrectEncoding
+      -> { subject.external_auth("dmVyb25hLmxpdA==\n") }.must_raise Vines::SaslErrors::IncorrectEncoding
+      stream.verify
+    end
+
+    it 'passes with empty authzid and matching cert' do
+      stream.expect :remote_domain, 'verona.lit'
+      stream.expect :cert_domain_matches?, true, ['verona.lit']
+      subject.external_auth('=').must_equal true
+      stream.verify
+    end
+
+    it 'fails with empty authzid and non-matching cert' do
+      stream.expect :remote_domain, 'verona.lit'
+      stream.expect :cert_domain_matches?, false, ['verona.lit']
+      -> { subject.external_auth('=') }.must_raise Vines::SaslErrors::NotAuthorized
+      stream.verify
+    end
+
+    it 'fails when authzid does not match stream from address' do
+      stream.expect :remote_domain, 'not.verona.lit'
+      -> { subject.external_auth('dmVyb25hLmxpdA==') }.must_raise Vines::SaslErrors::InvalidAuthzid
+      stream.verify
+    end
+
+    it 'passes when authzid matches stream from address' do
+      stream.expect :remote_domain, 'verona.lit'
+      stream.expect :remote_domain, 'verona.lit'
+      stream.expect :cert_domain_matches?, true, ['verona.lit']
+      subject.external_auth('dmVyb25hLmxpdA==').must_equal true
+      stream.verify
+    end
+  end
+end
diff --git a/test/stream/server/auth_method_test.rb b/test/stream/server/auth_method_test.rb
new file mode 100644
index 0000000..b0636c1
--- /dev/null
+++ b/test/stream/server/auth_method_test.rb
@@ -0,0 +1,124 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+class OperatorWrapper
+  def <<(stream)
+    [stream]
+  end
+end
+
+describe Vines::Stream::Server::AuthMethod do
+  before do
+    @result = {from: "hostA.org", to: "hostB.org", token: "1234"}
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::AuthMethod.new(@stream)
+  end
+
+  def test_invalid_element
+    EM.run {
+      node = node("<message/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_tls_element
+    EM.run {
+      node = node(%(<message xmlns="#{Vines::NAMESPACES[:tls]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_dialback_element
+    EM.run {
+      node = node(%(<message xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_tls_namespace
+    EM.run {
+      node = node("<starttls/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_no_dialback_payload
+    EM.run {
+      node = node("<db:result/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_tls_namespace
+    EM.run {
+      node = node(%(<starttls xmlns="#{Vines::NAMESPACES[:legacy_dialback]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_tls_certificate
+    EM.run {
+      @stream.expect(:encrypt?, false)
+      @stream.expect(:close_connection_after_writing, nil)
+      failure = %(<failure xmlns="#{Vines::NAMESPACES[:tls]}"/>)
+      node = node(%(<starttls xmlns="#{Vines::NAMESPACES[:tls]}"/>))
+      @stream.expect(:write, nil, [failure])
+      @stream.expect(:write, nil, ["</stream:stream>"])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_tls
+    EM.run {
+      @stream.expect(:encrypt?, true)
+      @stream.expect(:encrypt, nil)
+      @stream.expect(:reset, nil)
+      @stream.expect(:advance, nil, [Vines::Stream::Server::AuthRestart.new(@stream)])
+      success = %(<proceed xmlns="#{Vines::NAMESPACES[:tls]}"/>)
+      node = node(%(<starttls xmlns="#{Vines::NAMESPACES[:tls]}"/>))
+      @stream.expect(:write, nil, [success])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_dialback
+    EM.run {
+      @stream.expect(:config, Vines::Config)
+      @stream.expect(:router, OperatorWrapper.new)
+      @stream.expect(:close_connection_after_writing, nil)
+      node = node(
+        %(<db:result xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}" ) +
+        %(from="#{@result[:from]}" to="#{@result[:to]}">#{@result[:token]}</db:result>)
+      )
+      @stream.expect(:authoritative_dialback, nil, [node])
+      assert_nothing_raised {
+        @state.node(node)
+      }.must_equal(true)
+      EM.stop
+    }
+  end
+
+  private
+
+  def assert_nothing_raised
+    yield
+      true
+  rescue
+    $!
+  end
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/auth_test.rb b/test/stream/server/auth_test.rb
new file mode 100644
index 0000000..bf8aca7
--- /dev/null
+++ b/test/stream/server/auth_test.rb
@@ -0,0 +1,70 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Auth do
+  # disable logging for tests
+  Class.new.extend(Vines::Log).log.level = Logger::FATAL
+
+  subject      { Vines::Stream::Server::Auth.new(stream) }
+  let(:stream) { MiniTest::Mock.new }
+
+  before do
+    class << stream
+      attr_accessor :remote_domain
+    end
+    stream.remote_domain = "wonderland.lit"
+  end
+
+  describe "when given a valid authzid" do
+    before do
+      stream.expect :cert_domain_matches?, true, ["wonderland.lit"]
+      stream.expect :write, nil, [%(<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>)]
+      stream.expect :advance, nil, [Vines::Stream::Server::FinalRestart]
+      stream.expect :reset, nil
+      stream.expect :authentication_mechanisms, ["EXTERNAL"]
+    end
+
+    it "passes external auth with empty authzid" do
+      EM.run {
+        node = external("=")
+        subject.node(node)
+        stream.verify
+        EM.stop
+      }
+    end
+
+    it "passes external auth with authzid matching from domain" do
+      EM.run {
+        node = external(Base64.strict_encode64("wonderland.lit"))
+        subject.node(node)
+        stream.verify
+        EM.stop
+      }
+    end
+  end
+
+  describe "when given an invalid authzid" do
+    before do
+      stream.expect :write, nil, ["</stream:stream>"]
+      stream.expect :close_connection_after_writing, nil
+      stream.expect :error, nil, [Vines::SaslErrors::InvalidAuthzid]
+      stream.expect :authentication_mechanisms, ["EXTERNAL"]
+    end
+
+    it "fails external auth with mismatched from domain" do
+      EM.run {
+        node = external(Base64.strict_encode64("verona.lit"))
+        subject.node(node)
+        stream.verify
+        EM.stop
+      }
+    end
+  end
+
+  private
+
+  def external(authzid)
+    node(%(<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="EXTERNAL">#{authzid}</auth>))
+  end
+end
diff --git a/test/stream/server/outbound/auth_dialback_result_test.rb b/test/stream/server/outbound/auth_dialback_result_test.rb
new file mode 100644
index 0000000..14a94fa
--- /dev/null
+++ b/test/stream/server/outbound/auth_dialback_result_test.rb
@@ -0,0 +1,52 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Outbound::AuthDialbackResult do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Outbound::AuthDialbackResult.new(@stream)
+  end
+
+  def test_invalid_stanza
+    EM.run {
+      node = node("<message/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_invalid_result
+    EM.run {
+      node = node(
+        %(<db:result xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}" ) +
+        %(from="remote.host" to="local.host" type="invalid"/>)
+      )
+      @stream.expect(:close_connection, nil)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_result
+    EM.run {
+      node = node(
+        %(<db:result xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}" ) +
+        %(from="remote.host" to="local.host" type="valid"/>)
+      )
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Ready])
+      @stream.expect(:notify_connected, nil)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/outbound/auth_external_test.rb b/test/stream/server/outbound/auth_external_test.rb
new file mode 100644
index 0000000..483afd0
--- /dev/null
+++ b/test/stream/server/outbound/auth_external_test.rb
@@ -0,0 +1,105 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Outbound::AuthExternal do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Outbound::AuthExternal.new(@stream)
+  end
+
+  def test_invalid_element
+    EM.run {
+      node = node("<message/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_sasl_element
+    EM.run {
+      node = node(%(<message xmlns="#{Vines::NAMESPACES[:sasl]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_namespace
+    EM.run {
+      node = node("<stream:features/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_namespace
+    EM.run {
+      node = node(%(<stream:features xmlns="bogus"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_mechanisms
+    EM.run {
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_mechanisms_namespace
+    EM.run {
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}"><mechanisms/></stream:features>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_mechanism
+    EM.run {
+      mechanisms = %(<mechanisms xmlns="#{Vines::NAMESPACES[:sasl]}"/>)
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">#{mechanisms}</stream:features>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_missing_mechanism_text
+    EM.run {
+      mechanisms = %(<mechanisms xmlns="#{Vines::NAMESPACES[:sasl]}"><mechanism></mechanism></mechanisms>)
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">#{mechanisms}</stream:features>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_mechanism_text
+    EM.run {
+      mechanisms = %(<mechanisms xmlns="#{Vines::NAMESPACES[:sasl]}"><mechanism>BOGUS</mechanism></mechanisms>)
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">#{mechanisms}</stream:features>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_valid_mechanism
+    EM.run {
+      @stream.expect(:domain, "wonderland.lit")
+      expected = %(<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="EXTERNAL">d29uZGVybGFuZC5saXQ=</auth>)
+      @stream.expect(:write, nil, [expected])
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::AuthExternalResult.new(@stream)])
+      mechanisms = %(<mechanisms xmlns="#{Vines::NAMESPACES[:sasl]}"><mechanism>EXTERNAL</mechanism></mechanisms>)
+      node = node(%(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">#{mechanisms}</stream:features>))
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/outbound/auth_restart_test.rb b/test/stream/server/outbound/auth_restart_test.rb
new file mode 100644
index 0000000..86e924b
--- /dev/null
+++ b/test/stream/server/outbound/auth_restart_test.rb
@@ -0,0 +1,77 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Outbound::AuthRestart do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Outbound::AuthRestart.new(@stream)
+  end
+
+  def test_missing_namespace
+    EM.run {
+      node = node("<stream:stream/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_invalid_namespace
+    EM.run {
+      node = node(%(<stream:stream xmlns="#{Vines::NAMESPACES[:stream]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_valid_stream
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" xmlns:stream="#{Vines::NAMESPACES[:stream]}" ) +
+        %(xml:lang="en" id="1234" from="host.com" version="1.0">)
+      )
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::AuthExternal])
+      @stream.expect(:dialback_retry?, false)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_stream_restart
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" xmlns:stream="#{Vines::NAMESPACES[:stream]}" ) +
+        %(xml:lang="en" id="1234" from="host.com" version="1.0">)
+      )
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::Auth])
+      @stream.expect(:outbound_tls_required?, false)
+      @stream.expect(:dialback_retry?, true)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_stream_required_tls
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" xmlns:stream="#{Vines::NAMESPACES[:stream]}") +
+        %( xml:lang="en" id="1234" from="host.com" version="1.0">)
+      )
+      @stream.expect(:close_connection, nil)
+      @stream.expect(:outbound_tls_required?, true)
+      @stream.expect(:dialback_retry?, true)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/outbound/auth_test.rb b/test/stream/server/outbound/auth_test.rb
new file mode 100644
index 0000000..9951e3a
--- /dev/null
+++ b/test/stream/server/outbound/auth_test.rb
@@ -0,0 +1,113 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+class OperatorWrapper
+  def <<(stream)
+    [stream]
+  end
+end
+
+class StateWrapper
+  def dialback_secret=(secret); end
+end
+
+module Vines
+  module Kit
+    def auth_token; "1234"; end
+  end
+end
+
+module Boolean; end
+class TrueClass; include Boolean; end
+class FalseClass; include Boolean; end
+class NilClass; include Boolean; end
+
+describe Vines::Stream::Server::Outbound::Auth do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Outbound::Auth.new(@stream)
+  end
+
+  def test_missing_children
+    EM.run {
+      node = node("<stream:features/>")
+      @stream.expect(:dialback_verify_key?, false)
+      @stream.expect(:outbound_tls_required, nil, [Boolean])
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_invalid_children
+    EM.run {
+      node = node(%(<stream:features><message/></stream:features>))
+      @stream.expect(:dialback_verify_key?, false)
+      @stream.expect(:outbound_tls_required, nil, [Boolean])
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_stream_features
+    EM.run {
+      node = node(
+        %(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">) +
+        %(<starttls xmlns="#{Vines::NAMESPACES[:tls]}"><required/></starttls>) +
+        %(<dialback xmlns="#{Vines::NAMESPACES[:dialback]}"/></stream:features>)
+      )
+      starttls = %(<starttls xmlns='#{Vines::NAMESPACES[:tls]}'/>)
+      @stream.expect(:dialback_verify_key?, false)
+      @stream.expect(:outbound_tls_required, nil, [Boolean])
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::TLSResult])
+      @stream.expect(:write, nil, [starttls])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_dialback_feature_only
+    EM.run {
+      node = node(
+        %(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">) +
+        %(<dialback xmlns="#{Vines::NAMESPACES[:dialback]}"/></stream:features>)
+      )
+      @stream.expect(:dialback_verify_key?, false)
+      @stream.expect(:router, OperatorWrapper.new)
+      @stream.expect(:domain, "local.host")
+      @stream.expect(:remote_domain, "remote.host")
+      @stream.expect(:domain, "local.host")
+      @stream.expect(:remote_domain, "remote.host")
+      @stream.expect(:id, "1234")
+      @stream.expect(:write, nil, [String])
+      @stream.expect(:outbound_tls_required, nil, [Boolean])
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::AuthDialbackResult])
+      @stream.expect(:state, StateWrapper.new)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_dialback_verify_key
+    EM.run {
+      node = node("<stream:stream/>")
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::Authoritative])
+      @stream.expect(:dialback_verify_key?, true)
+      @stream.expect(:callback!, nil)
+      @stream.expect(:outbound_tls_required, nil, [Boolean])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/outbound/authoritative_test.rb b/test/stream/server/outbound/authoritative_test.rb
new file mode 100644
index 0000000..7611bd3
--- /dev/null
+++ b/test/stream/server/outbound/authoritative_test.rb
@@ -0,0 +1,86 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+class RouterWrapper
+  def initialize(stream); @stream = stream; end
+  def stream_by_id(id); @stream; end
+end
+
+describe Vines::Stream::Server::Outbound::Authoritative do
+  before do
+    @stream = MiniTest::Mock.new
+    @router = RouterWrapper.new(@stream)
+    @state = Vines::Stream::Server::Outbound::Authoritative.new(@stream)
+  end
+
+  def test_invalid_stanza
+    EM.run {
+      node = node("<message/>")
+      @stream.expect(:router, @router)
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_invalid_token
+    EM.run {
+      node = node("<db:verify/>")
+      router = RouterWrapper.new(nil)
+      @stream.expect(:router, router)
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_verification
+    EM.run {
+      node = node(
+        %(<db:verify xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}" ) +
+        %(from="remote.host" to="local.host" id="1234" type="valid"/>)
+      )
+      result = %(<db:result xmlns:db='#{Vines::NAMESPACES[:legacy_dialback]}' ) +
+        %(from='#{node[:to]}' to='#{node[:from]}' type='#{node[:type]}'/>)
+      @stream.expect(:router, @router)
+      # NOTE this tests the "inbound" stream var
+      @stream.expect(:write, nil, [result])
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Ready])
+      @stream.expect(:notify_connected, nil)
+      # end
+      @stream.expect(:nil?, false)
+      @stream.expect(:close_connection, nil)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_invalid_verification
+    EM.run {
+      node = node(
+        %(<db:verify xmlns:db="#{Vines::NAMESPACES[:legacy_dialback]}" ) +
+        %(from="remote.host" to="local.host" id="1234" type="invalid"/>)
+      )
+      result = %(<db:result xmlns:db='#{Vines::NAMESPACES[:legacy_dialback]}' ) +
+        %(from='#{node[:to]}' to='#{node[:from]}' type='#{node[:type]}'/>)
+      @stream.expect(:router, @router)
+      # NOTE this tests the "inbound" stream var
+      @stream.expect(:close_connection_after_writing, nil)
+      @stream.expect(:write, nil, [result])
+      # end
+      @stream.expect(:nil?, false)
+      @stream.expect(:close_connection, nil)
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/outbound/start_test.rb b/test/stream/server/outbound/start_test.rb
new file mode 100644
index 0000000..2e9fbae
--- /dev/null
+++ b/test/stream/server/outbound/start_test.rb
@@ -0,0 +1,45 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Outbound::Start do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Outbound::Start.new(@stream)
+  end
+
+  def test_missing_namespace
+    EM.run {
+      node = node("<stream:stream/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_namespace
+    EM.run {
+      node = node(%(<stream:stream xmlns="#{Vines::NAMESPACES[:stream]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_valid_stream
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" xmlns:stream="#{Vines::NAMESPACES[:stream]}" ) +
+        %(xml:lang="en" id="1234" from="host.com" version="1.0">)
+      )
+      @stream.expect(:advance, nil, [Vines::Stream::Server::Outbound::Auth])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/ready_test.rb b/test/stream/server/ready_test.rb
new file mode 100644
index 0000000..3a5dd6f
--- /dev/null
+++ b/test/stream/server/ready_test.rb
@@ -0,0 +1,122 @@
+# encoding: UTF-8
+
+require "test_helper"
+
+describe Vines::Stream::Server::Ready do
+  subject      { Vines::Stream::Server::Ready.new(stream, nil) }
+  let(:stream) { MiniTest::Mock.new }
+
+  SERVER_STANZAS = []
+
+  before do
+    def subject.to_stanza(node)
+      Vines::Stanza.from_node(node, stream).tap do |stanza|
+        def stanza.process
+          SERVER_STANZAS << self
+        end if stanza
+      end
+    end
+  end
+
+  after do
+    SERVER_STANZAS.clear
+  end
+
+  it "processes a valid node" do
+    EM.run {
+      config = MiniTest::Mock.new
+      config.expect(:local_jid?, true, [Vines::JID.new("romeo at verona.lit")])
+
+      stream.expect(:config, config)
+      stream.expect(:remote_domain, "wonderland.lit")
+      stream.expect(:domain, "verona.lit")
+      stream.expect(:user=, nil, [Vines::User.new(jid: "alice at wonderland.lit")])
+
+      node = node(%(<message from="alice at wonderland.lit" to="romeo at verona.lit"/>))
+      subject.node(node)
+      assert_equal 1, SERVER_STANZAS.size
+      assert stream.verify
+      assert config.verify
+      EM.stop
+    }
+  end
+
+  it "raises unsupported-stanza-type stream error" do
+    EM.run {
+      node = node("<bogus/>")
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::UnsupportedStanzaType
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises improper-addressing stream error when to address is missing" do
+    EM.run {
+      node = node(%(<message from="alice at wonderland.lit"/>))
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises jid-malformed stanza error when to address is invalid" do
+    EM.run {
+      node = node(%(<message from="alice at wonderland.lit" to=" "/>))
+      -> { subject.node(node) }.must_raise Vines::StanzaErrors::JidMalformed
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises improper-addressing stream error" do
+    EM.run {
+      node = node(%(<message to="romeo at verona.lit"/>))
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises jid-malformed stanza error for invalid from address" do
+    EM.run {
+      node = node(%(<message from=" " to="romeo at verona.lit"/>))
+      -> { subject.node(node) }.must_raise Vines::StanzaErrors::JidMalformed
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises invalid-from stream error" do
+    EM.run {
+      stream.expect(:remote_domain, "wonderland.lit")
+      node = node(%(<message from="alice at bogus.lit" to="romeo at verona.lit"/>))
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::InvalidFrom
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  it "raises host-unknown stream error" do
+    EM.run {
+      stream.expect(:remote_domain, "wonderland.lit")
+      stream.expect(:domain, "verona.lit")
+      node = node(%(<message from="alice at wonderland.lit" to="romeo at bogus.lit"/>))
+      -> { subject.node(node) }.must_raise Vines::StreamErrors::HostUnknown
+      assert SERVER_STANZAS.empty?
+      assert stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/stream/server/start_test.rb b/test/stream/server/start_test.rb
new file mode 100644
index 0000000..808ec76
--- /dev/null
+++ b/test/stream/server/start_test.rb
@@ -0,0 +1,105 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+class VhostWrapper
+  def initialize(force = false)
+    @force_s2s_encryption = force
+  end
+  def force_s2s_encryption?
+    @force_s2s_encryption
+  end
+end
+
+describe Vines::Stream::Server::AuthMethod do
+  before do
+    @stream = MiniTest::Mock.new
+    @state = Vines::Stream::Server::Start.new(@stream)
+  end
+
+  def test_missing_namespace
+    EM.run {
+      node = node("<stream:stream/>")
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_invalid_namespace
+    EM.run {
+      node = node(%(<stream:stream xmlns="#{Vines::NAMESPACES[:stream]}"/>))
+      assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
+      EM.stop
+    }
+  end
+
+  def test_valid_stream_tls_required
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" ) +
+        %(xmlns:stream="#{Vines::NAMESPACES[:stream]}" to="host.com" version="1.0"/>)
+      )
+      features = node(
+        %(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">) +
+        %(<starttls xmlns="#{Vines::NAMESPACES[:tls]}"/>) +
+        %(<dialback xmlns="#{Vines::NAMESPACES[:dialback]}"/></stream:features>)
+      )
+      @stream.expect(:start, nil, [node])
+      @stream.expect(:vhost, VhostWrapper.new(false))
+      @stream.expect(:advance, nil, [Vines::Stream::Server::AuthMethod])
+      @stream.expect(:dialback_retry?, false)
+      @stream.expect(:write, nil, [features])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_stream_with_dialback_flag
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" ) +
+        %(xmlns:stream="#{Vines::NAMESPACES[:stream]}" to="host.com" version="1.0"/>)
+      )
+      features = node(
+        %(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">) +
+        %(<dialback xmlns="#{Vines::NAMESPACES[:dialback]}"/></stream:features>)
+      )
+      @stream.expect(:start, nil, [node])
+      @stream.expect(:advance, nil, [Vines::Stream::Server::AuthMethod])
+      @stream.expect(:dialback_retry?, true)
+      @stream.expect(:write, nil, [features])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  def test_valid_stream
+    EM.run {
+      node = node(
+        %(<stream:stream xmlns="jabber:client" ) +
+        %(xmlns:stream="#{Vines::NAMESPACES[:stream]}" to="host.com" version="1.0"/>)
+      )
+      features = node(
+        %(<stream:features xmlns:stream="#{Vines::NAMESPACES[:stream]}">) +
+        %(<starttls xmlns="#{Vines::NAMESPACES[:tls]}"><required/></starttls>) +
+        %(<dialback xmlns="#{Vines::NAMESPACES[:dialback]}"/></stream:features>)
+      )
+      @stream.expect(:start, nil, [node])
+      @stream.expect(:vhost, VhostWrapper.new(true))
+      @stream.expect(:advance, nil, [Vines::Stream::Server::AuthMethod])
+      @stream.expect(:dialback_retry?, false)
+      @stream.expect(:write, nil, [features])
+      @state.node(node)
+      assert @stream.verify
+      EM.stop
+    }
+  end
+
+  private
+
+  def node(xml)
+    Nokogiri::XML(xml).root
+  end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..9f4aca3
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,51 @@
+# encoding: UTF-8
+
+require 'tmpdir'
+require 'vines'
+require 'ext/nokogiri'
+require 'minitest/autorun'
+require 'rails/all'
+
+# A simple hook allowing you to run a block of code after everything is done running
+# In this case we want to delete the old sqlite database in case we run rake-test twice
+Minitest.after_run {
+  db_file = "test.db"
+  File.delete(db_file) if File.exist?(db_file)
+  puts "After_run hook deleted #{db_file}"
+}
+
+class MiniTest::Spec
+
+  # Build an <iq> xml node with the given attributes. This is useful as a
+  # quick way to build a node to use as expected stanza output from a
+  # Stream#write call.
+  #
+  # options - The Hash of xml attributes to include on the iq element. Attribute
+  #           values of nil or empty? are excluded from the generated element.
+  #           :body - The String xml content to include in the iq element.
+  #
+  # Examples
+  #
+  #   iq(from: from, id: 42, to: to, type: 'result', body: card)
+  #
+  # Returns a Nokogiri::XML::Node.
+  def iq(options)
+    body = options.delete(:body)
+    options.delete_if {|k, v| v.nil? || v.to_s.empty? }
+    attrs = options.map {|k, v| "#{k}=\"#{v}\"" }.join(' ')
+    node("<iq #{attrs}>#{body}</iq>")
+  end
+
+  # Parse xml into a nokogiri node. Strip excessive whitespace from the xml
+  # content before parsing because it affects comparisons in MiniTest::Mock
+  # expectations.
+  #
+  # xml - The String of xml content to parse.
+  #
+  # Returns a Nokogiri::XML::Node.
+  def node(xml)
+    xml = xml.strip.gsub(/\n|\s{2,}/, '')
+    Nokogiri::XML(xml).root
+  end
+end
+
diff --git a/test/token_bucket_test.rb b/test/token_bucket_test.rb
new file mode 100644
index 0000000..ab9d8ba
--- /dev/null
+++ b/test/token_bucket_test.rb
@@ -0,0 +1,44 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::TokenBucket do
+  subject { Vines::TokenBucket.new(10, 1) }
+
+  it 'raises with invalid capacity and rate values' do
+    -> { Vines::TokenBucket.new(0, 1) }.must_raise ArgumentError
+    -> { Vines::TokenBucket.new(1, 0) }.must_raise ArgumentError
+    -> { Vines::TokenBucket.new(-1, 1) }.must_raise ArgumentError
+    -> { Vines::TokenBucket.new(1, -1) }.must_raise ArgumentError
+  end
+
+  it 'does not allow taking a negative number of tokens' do
+    -> { subject.take(-1) }.must_raise ArgumentError
+  end
+
+  it 'does not allow taking more tokens than its capacity' do
+    refute subject.take(11)
+  end
+
+  it 'allows taking all tokens, but no more' do
+    assert subject.take(10)
+    refute subject.take(1)
+  end
+
+  it 'refills over time' do
+    assert subject.take(10)
+    refute subject.take(1)
+    Time.stub(:new, Time.now + 1) do
+      assert subject.take(1)
+      refute subject.take(1)
+    end
+  end
+
+  it 'does not refill over capacity' do
+    assert subject.take(10)
+    refute subject.take(1)
+    Time.stub(:new, Time.now + 15) do
+      refute subject.take(11)
+    end
+  end
+end
diff --git a/test/user_test.rb b/test/user_test.rb
new file mode 100644
index 0000000..b0adc36
--- /dev/null
+++ b/test/user_test.rb
@@ -0,0 +1,101 @@
+# encoding: UTF-8
+
+require 'test_helper'
+
+describe Vines::User do
+  subject { Vines::User.new(jid: 'alice at wonderland.lit', name: 'Alice', password: 'secr3t') }
+
+  describe 'user equality checks' do
+    let(:alice)  { Vines::User.new(jid: 'alice at wonderland.lit') }
+    let(:hatter) { Vines::User.new(jid: 'hatter at wonderland.lit') }
+
+    it 'uses class in equality check' do
+      (subject <=> 42).must_be_nil
+    end
+
+    it 'is equal to itself' do
+      assert subject == subject
+      assert subject.eql?(subject)
+      assert subject.hash == subject.hash
+    end
+
+    it 'is equal to another user with the same jid' do
+      assert subject == alice
+      assert subject.eql?(alice)
+      assert subject.hash == alice.hash
+    end
+
+    it 'is not equal to a different jid' do
+      refute subject == hatter
+      refute subject.eql?(hatter)
+      refute subject.hash == hatter.hash
+    end
+  end
+
+  describe 'initialize' do
+    it 'raises when not given a jid' do
+      -> { Vines::User.new }.must_raise ArgumentError
+      -> { Vines::User.new(jid: '') }.must_raise ArgumentError
+    end
+
+    it 'has an empty roster' do
+      subject.roster.wont_be_nil
+      subject.roster.size.must_equal 0
+    end
+  end
+
+  describe '#update_from' do
+    let(:updated) { Vines::User.new(jid: 'alice2 at wonderland.lit', name: 'Alice 2', password: "secr3t 2") }
+
+    before do
+      subject.roster << Vines::Contact.new(jid: 'hatter at wonderland.lit', name: "Hatter")
+      updated.roster << Vines::Contact.new(jid: 'cat at wonderland.lit', name: "Cheshire")
+    end
+
+    it 'updates jid, name, and password' do
+      subject.update_from(updated)
+      subject.jid.to_s.must_equal 'alice at wonderland.lit'
+      subject.name.must_equal 'Alice 2'
+      subject.password.must_equal 'secr3t 2'
+    end
+
+    it 'overwrites the entire roster' do
+      subject.update_from(updated)
+      subject.roster.size.must_equal 1
+      subject.roster.first.must_equal updated.roster.first
+    end
+
+    it 'clones roster entries' do
+      subject.update_from(updated)
+      updated.roster.first.name = 'Updated Contact 2'
+      subject.roster.first.name.must_equal 'Cheshire'
+    end
+  end
+
+  describe '#to_roster_xml' do
+    let(:expected) do
+      node(%q{
+        <iq id="42" type="result">
+          <query xmlns="jabber:iq:roster">
+            <item jid="a at wonderland.lit" name="Contact 1" subscription="none" from_diaspora="false">
+              <group>A</group>
+              <group>B</group>
+            </item>
+            <item jid="b at wonderland.lit" name="Contact 2" subscription="none" from_diaspora="false">
+              <group>C</group>
+            </item>
+          </query>
+        </iq>
+      })
+    end
+
+    before do
+      subject.roster << Vines::Contact.new(jid: 'b at wonderland.lit', name: "Contact 2", groups: %w[C])
+      subject.roster << Vines::Contact.new(jid: 'a at wonderland.lit', name: "Contact 1", groups: %w[B A])
+    end
+
+    it 'sorts group names' do
+      subject.to_roster_xml(42).must_equal expected
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-diaspora-vines.git



More information about the Pkg-ruby-extras-commits mailing list