zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit b6bb0a03fb499220a8d4ee204be57172e15b787a
parent 9ede7d19797918e07561332975926d1a749c62c0
Author: Alberto Lerda <30939098+albertolerda@users.noreply.github.com>
Date:   Wed, 21 Sep 2022 13:09:24 +0200

Merge pull request #16 from dyne/alberto/http-client

Intruduce an generic HTTP client and make Restroom use it.
Diffstat:
A.deps/castore/.fetch | 0
A.deps/castore/.hex | 0
A.deps/castore/README.md | 48++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/castore/hex_metadata.config | 12++++++++++++
A.deps/castore/lib/castore.ex | 24++++++++++++++++++++++++
A.deps/castore/mix.exs | 48++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/castore/priv/cacerts.pem | 3460+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/.fetch | 0
A.deps/hpax/.formatter.exs | 5+++++
A.deps/hpax/.hex | 0
A.deps/hpax/CHANGELOG.md | 9+++++++++
A.deps/hpax/LICENSE.txt | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/README.md | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/hex_metadata.config | 15+++++++++++++++
A.deps/hpax/lib/hpax.ex | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/lib/hpax/huffman.ex | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/lib/hpax/huffman_table | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/lib/hpax/table.ex | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/lib/hpax/types.ex | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/hpax/mix.exs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/.fetch | 0
A.deps/mint/.formatter.exs | 11+++++++++++
A.deps/mint/.hex | 0
A.deps/mint/CHANGELOG.md | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/LICENSE.txt | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/README.md | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/hex_metadata.config | 34++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/application.ex | 14++++++++++++++
A.deps/mint/lib/mint/core/conn.ex | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/core/transport.ex | 36++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/core/transport/ssl.ex | 725+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/core/transport/tcp.ex | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/core/util.ex | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http.ex | 965+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http1.ex | 1063+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http1/parse.ex | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http1/request.ex | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http1/response.ex | 43+++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http2.ex | 2197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http2/frame.ex | 465+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/http_error.ex | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/negotiate.ex | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/transport_error.ex | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/tunnel_proxy.ex | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/types.ex | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/lib/mint/unsafe_proxy.ex | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/mix.exs | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.deps/mint/src/mint_shims.erl | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconf/runtime.exs | 4++--
Mdocs/configuration-guide.md | 7+++----
Mmix.exs | 2++
Mmix.lock | 3+++
Msrc/zenflows/application.ex | 2++
Asrc/zenflows/httpc.ex | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/restroom.ex | 54++++++++++++++++++++++++++++--------------------------
55 files changed, 12583 insertions(+), 32 deletions(-)

diff --git a/.deps/castore/.fetch b/.deps/castore/.fetch diff --git a/.deps/castore/.hex b/.deps/castore/.hex Binary files differ. diff --git a/.deps/castore/README.md b/.deps/castore/README.md @@ -0,0 +1,48 @@ +# CAStore + +Up-to-date CA certificate store. + +## Installation + +In your `mix.exs`: + +```elixir +def deps do + [ + {:castore, "~> 0.1.0"} + ] +end +``` + +Then, run `$ mix deps.get`. + +## Usage + +This is a micro-library whose only job is storing an up-to-date CA certificate store. The only provided function is `CAStore.file_path/0`, which returns the path of the CA certificate store file. + +```elixir +CAStore.file_path() +#=> /Users/me/castore/_build/dev/lib/castore/priv/cacerts.pem" +``` + +See [the documentation](https://hexdocs.pm/castore). + +## Contributing + +If you want to locally update the CA certificate store file bundled with this library, run the `mix certdata` from the root of this library. + +## License + +Copyright 2018 Eric Meadows-Jönsson and Andrea Leopardi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.deps/castore/hex_metadata.config b/.deps/castore/hex_metadata.config @@ -0,0 +1,12 @@ +{<<"app">>,<<"castore">>}. +{<<"build_tools">>,[<<"mix">>]}. +{<<"description">>,<<"Up-to-date CA certificate store.">>}. +{<<"elixir">>,<<"~> 1.0">>}. +{<<"files">>, + [<<"lib/castore.ex">>,<<"priv">>,<<"priv/cacerts.pem">>,<<"mix.exs">>, + <<"README.md">>]}. +{<<"licenses">>,[<<"Apache-2.0">>]}. +{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-mint/castore">>}]}. +{<<"name">>,<<"castore">>}. +{<<"requirements">>,[]}. +{<<"version">>,<<"0.1.18">>}. diff --git a/.deps/castore/lib/castore.ex b/.deps/castore/lib/castore.ex @@ -0,0 +1,24 @@ +defmodule CAStore do + @moduledoc """ + Functionality to retrieve the up-to-date CA certificate store. + + The only purpose of this library is to keep an up-to-date CA certificate store file. + This is why this module only provides one function, `file_path/0`, to access the path of + the CA certificate store file. You can then read this file and use its contents for your + own purposes. + """ + + @doc """ + Returns the path to the CA certificate store PEM file. + + ## Examples + + CAStore.file_path() + #=> /Users/me/castore/_build/dev/lib/castore/priv/cacerts.pem" + + """ + @spec file_path() :: Path.t() + def file_path() do + Application.app_dir(:castore, "priv/cacerts.pem") + end +end diff --git a/.deps/castore/mix.exs b/.deps/castore/mix.exs @@ -0,0 +1,48 @@ +defmodule CAStore.MixProject do + use Mix.Project + + @version "0.1.18" + @repo_url "https://github.com/elixir-mint/castore" + + def project do + [ + app: :castore, + version: @version, + elixir: "~> 1.0", + start_permanent: Mix.env() == :prod, + deps: deps(), + xref: [exclude: [:public_key]], + + # Hex + package: package(), + description: "Up-to-date CA certificate store.", + + # Docs + name: "CAStore", + docs: [ + source_ref: "v#{@version}", + source_url: @repo_url + ] + ] + end + + def application do + [ + extra_applications: [:logger] + ] + end + + defp deps do + [ + {:ex_doc, "~> 0.22", only: :dev} + ] + end + + defp package do + [ + files: ["lib/castore.ex", "priv", "mix.exs", "README.md"], + licenses: ["Apache-2.0"], + links: %{"GitHub" => @repo_url} + ] + end +end diff --git a/.deps/castore/priv/cacerts.pem b/.deps/castore/priv/cacerts.pem @@ -0,0 +1,3460 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Jul 19 03:28:20 2022 GMT +## +## 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: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## 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. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 9bf3799611fb58197f61d45e71ce3dc19f30e7dd73731915872ce5108a7bb066 +## + + +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----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----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----- + +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----- + +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----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +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 +-----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----- + +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----- + +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----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +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 +-----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----- + +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----- + +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----- + +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----- + +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----- + +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----- + +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----- + +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----- + +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----- + +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----- +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 +-----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----- + +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----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +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 +-----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----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +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 +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +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 +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +TrustCor RootCert CA-1 +====================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx +MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu +YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe +VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy +dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq +jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4 +pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0 +JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h +gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw +/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j +BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5 +mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C +qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P +3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +TrustCor RootCert CA-2 +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +TrustCor ECA-1 +============== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw +N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5 +MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y +IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR +MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23 +xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc +p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+ +fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj +YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL +f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF +AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u +/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs +J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC +jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +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 +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +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 +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +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 +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +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+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +E-Tugra Global Root CA RSA v3 +============================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +E-Tugra Global Root CA ECC v3 +============================= +-----BEGIN CERTIFICATE----- +MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV +BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB +IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP +MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 +Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 +w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 +Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ +zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W +Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 +-----END CERTIFICATE----- diff --git a/.deps/hpax/.fetch b/.deps/hpax/.fetch diff --git a/.deps/hpax/.formatter.exs b/.deps/hpax/.formatter.exs @@ -0,0 +1,5 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + import_deps: [:stream_data] +] diff --git a/.deps/hpax/.hex b/.deps/hpax/.hex Binary files differ. diff --git a/.deps/hpax/CHANGELOG.md b/.deps/hpax/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## v0.1.2 + + * Fix `use Bitwise` deprecation warning. + +## v0.1.1 + + * Improve checking of dynamic resize updates. diff --git a/.deps/hpax/LICENSE.txt b/.deps/hpax/LICENSE.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/.deps/hpax/README.md b/.deps/hpax/README.md @@ -0,0 +1,74 @@ +# HPAX + +![CI](https://github.com/elixir-mint/hpax/actions/workflows/main.yml/badge.svg) +[![Docs](https://img.shields.io/badge/api-docs-green.svg?style=flat)](https://hexdocs.pm/hpax) +[![Hex.pm Version](http://img.shields.io/hexpm/v/hpax.svg?style=flat)](https://hex.pm/packages/hpax) +[![Coverage Status](https://coveralls.io/repos/github/elixir-mint/hpax/badge.svg?branch=main)](https://coveralls.io/github/elixir-mint/hpax?branch=main) + +HPAX is an Elixir implementation of the HPACK header compression algorithm as used in HTTP/2 and +defined in RFC 7541. HPAX is used by several Elixir projects, including the +[Mint](https://github.com/elixir-mint/mint) HTTP client and +[bandit](https://github.com/mtrudel/bandit) HTTP server projects. + +## Installation + +To install HPAX, add it to your `mix.exs` file. + +```elixir +defp deps do + [ + {:hpax, "~> 0.1.0"} + ] +end +``` + +Then, run `$ mix deps.get`. + +## Usage + +HPAX is designed to be used in both encoding and decoding scenarios. In both cases, a context is +used to maintain state internal to the HPACK algorithm. In the common use case of using HPAX +within HTTP/2, this context is called a **table** and must be shared between any +subsequent encoding/decoding calls within +an endpoint. Note that the contexts used for encoding and decoding within HTTP/2 are completely +distinct from one another, even though they are structurally identical. + +To encode a set of headers into a binary with HPAX: + +```elixir +context = HPAX.new(4096) +headers = [{:store, ":status", "201"}, {:store, "location", "http://example.com"}] +{encoded_headers, context} = HPAX.encode(headers, context) +#=> {iodata, updated_context} +``` + +To decode a binary into a set of headers with HPAX: + +```elixir +context = HPAX.new(4096) +encoded_headers = <<...>> +{:ok, headers, context} = HPAX.decode(encoded_headers, context) +#=> {:ok, [{:store, ":status", "201"}, {:store, "location", "http://example.com"}], updated_context} +``` + +For complete usage information, please see the HPAX [documentation](https://hex.pm/packages/hpax). + +## Contributing + +If you wish to contribute check out the [issue list](https://github.com/elixir-mint/hpax/issues) and let us know what you want to work on so we can discuss it and reduce duplicate work. + +## License + +Copyright 2021 Eric Meadows-Jönsson and Andrea Leopardi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.deps/hpax/hex_metadata.config b/.deps/hpax/hex_metadata.config @@ -0,0 +1,15 @@ +{<<"app">>,<<"hpax">>}. +{<<"build_tools">>,[<<"mix">>]}. +{<<"description">>, + <<"Implementation of the HPACK protocol (RFC 7541) for Elixir">>}. +{<<"elixir">>,<<"~> 1.5">>}. +{<<"files">>, + [<<"lib">>,<<"lib/hpax">>,<<"lib/hpax/huffman_table">>, + <<"lib/hpax/types.ex">>,<<"lib/hpax/huffman.ex">>,<<"lib/hpax/table.ex">>, + <<"lib/hpax.ex">>,<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>, + <<"LICENSE.txt">>,<<"CHANGELOG.md">>]}. +{<<"licenses">>,[<<"Apache-2.0">>]}. +{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-mint/hpax">>}]}. +{<<"name">>,<<"hpax">>}. +{<<"requirements">>,[]}. +{<<"version">>,<<"0.1.2">>}. diff --git a/.deps/hpax/lib/hpax.ex b/.deps/hpax/lib/hpax.ex @@ -0,0 +1,283 @@ +defmodule HPAX do + @moduledoc """ + Support for the HPACK header compression algorithm. + + This module provides support for the HPACK header compression algorithm used mainly in HTTP/2. + + ## Encoding and decoding contexts + + The HPACK algorithm requires both + + * an encoding context on the encoder side + * a decoding context on the decoder side + + These contexts are semantically different but structurally the same. In HPACK they are + implemented as **HPACK tables**. This library uses the name "tables" everywhere internally + + HPACK tables can be created through the `new/1` function. + """ + + alias HPAX.{Table, Types} + + @typedoc """ + An HPACK header name. + """ + @type header_name() :: binary() + + @typedoc """ + An HPACK header value. + """ + @type header_value() :: binary() + + @valid_header_actions [:store, :store_name, :no_store, :never_store] + + @doc """ + Create a new HPACK table that can be used as encoding or decoding context. + + See the "Encoding and decoding contexts" section in the module documentation. + + `max_table_size` is the maximum table size (in bytes) for the newly created table. + + ## Examples + + encoding_context = HPAX.new(4096) + + """ + @spec new(non_neg_integer()) :: Table.t() + def new(max_table_size) when is_integer(max_table_size) and max_table_size >= 0 do + Table.new(max_table_size) + end + + @doc """ + Resizes the given table to the given size. + + ## Examples + + decoding_context = HPAX.new(4096) + HPAX.resize(decoding_context, 8192) + + """ + @spec resize(Table.t(), non_neg_integer()) :: Table.t() + defdelegate resize(table, new_size), to: Table + + @doc """ + Decodes a header block fragment (HBF) through a given table. + + If decoding is successful, this function returns a `{:ok, headers, updated_table}` tuple where + `headers` is a list of decoded headers, and `updated_table` is the updated table. If there's + an error in decoding, this function returns `{:error, reason}`. + + ## Examples + + decoding_context = HPAX.new(1000) + hbf = get_hbf_from_somewhere() + HPAX.decode(hbf, decoding_context) + #=> {:ok, [{":method", "GET"}], decoding_context} + + """ + @spec decode(binary(), Table.t()) :: + {:ok, [{header_name(), header_value()}], Table.t()} | {:error, term()} + + # Dynamic resizes must occur only at the start of a block + # https://datatracker.ietf.org/doc/html/rfc7541#section-4.2 + def decode(<<0b001::3, rest::bitstring>>, %Table{} = table) do + {new_size, rest} = decode_integer(rest, 5) + + # Dynamic resizes must be less than max table size + # https://datatracker.ietf.org/doc/html/rfc7541#section-6.3 + if new_size <= table.max_table_size do + decode(rest, Table.resize(table, new_size)) + else + {:error, :protocol_error} + end + end + + def decode(block, %Table{} = table) when is_binary(block) do + decode_headers(block, table, _acc = []) + catch + :throw, {:hpax, error} -> {:error, error} + end + + @doc """ + Encodes a list of headers through the given table. + + Returns a two-element tuple where the first element is a binary representing the encoded headers + and the second element is an updated table. + + ## Examples + + headers = [{:store, ":authority", "https://example.com"}] + encoding_context = HPAX.new(1000) + HPAX.encode(headers, encoding_context) + #=> {iodata, updated_encoding_context} + + """ + @spec encode([header], Table.t()) :: {iodata(), Table.t()} + when header: {action, header_name(), header_value()}, + action: :store | :store_name | :no_store | :never_store + def encode(headers, %Table{} = table) when is_list(headers) do + encode_headers(headers, table, _acc = []) + end + + ## Helpers + + defp decode_headers(<<>>, table, acc) do + {:ok, Enum.reverse(acc), table} + end + + # Indexed header field + # http://httpwg.org/specs/rfc7541.html#rfc.section.6.1 + defp decode_headers(<<0b1::1, rest::bitstring>>, table, acc) do + {index, rest} = decode_integer(rest, 7) + decode_headers(rest, table, [lookup_by_index!(table, index) | acc]) + end + + # Literal header field with incremental indexing + # http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1 + defp decode_headers(<<0b01::2, rest::bitstring>>, table, acc) do + {name, value, rest} = + case rest do + # The header name is a string. + <<0::6, rest::binary>> -> + {name, rest} = decode_binary(rest) + {value, rest} = decode_binary(rest) + {name, value, rest} + + # The header name is an index to be looked up in the table. + _other -> + {index, rest} = decode_integer(rest, 6) + {value, rest} = decode_binary(rest) + {name, _value} = lookup_by_index!(table, index) + {name, value, rest} + end + + decode_headers(rest, Table.add(table, name, value), [{name, value} | acc]) + end + + # Literal header field without indexing + # http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2 + defp decode_headers(<<0b0000::4, rest::bitstring>>, table, acc) do + {name, value, rest} = + case rest do + <<0::4, rest::binary>> -> + {name, rest} = decode_binary(rest) + {value, rest} = decode_binary(rest) + {name, value, rest} + + _other -> + {index, rest} = decode_integer(rest, 4) + {value, rest} = decode_binary(rest) + {name, _value} = lookup_by_index!(table, index) + {name, value, rest} + end + + decode_headers(rest, table, [{name, value} | acc]) + end + + # Literal header field never indexed + # http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3 + defp decode_headers(<<0b0001::4, rest::bitstring>>, table, acc) do + {name, value, rest} = + case rest do + <<0::4, rest::binary>> -> + {name, rest} = decode_binary(rest) + {value, rest} = decode_binary(rest) + {name, value, rest} + + _other -> + {index, rest} = decode_integer(rest, 4) + {value, rest} = decode_binary(rest) + {name, _value} = lookup_by_index!(table, index) + {name, value, rest} + end + + # TODO: enforce the "never indexed" part somehow. + decode_headers(rest, table, [{name, value} | acc]) + end + + defp decode_headers(_other, _table, _acc) do + throw({:hpax, :protocol_error}) + end + + defp lookup_by_index!(table, index) do + case Table.lookup_by_index(table, index) do + {:ok, header} -> header + :error -> throw({:hpax, {:index_not_found, index}}) + end + end + + defp decode_integer(bitstring, prefix) do + case Types.decode_integer(bitstring, prefix) do + {:ok, int, rest} -> {int, rest} + :error -> throw({:hpax, :bad_integer_encoding}) + end + end + + defp decode_binary(binary) do + case Types.decode_binary(binary) do + {:ok, binary, rest} -> {binary, rest} + :error -> throw({:hpax, :bad_binary_encoding}) + end + end + + defp encode_headers([], table, acc) do + {acc, table} + end + + defp encode_headers([{action, name, value} | rest], table, acc) + when action in @valid_header_actions and is_binary(name) and is_binary(value) do + {encoded, table} = + case Table.lookup_by_header(table, name, value) do + {:full, index} -> + {encode_indexed_header(index), table} + + {:name, index} when action == :store -> + {encode_literal_header_with_indexing(index, value), Table.add(table, name, value)} + + {:name, index} when action in [:store_name, :no_store] -> + {encode_literal_header_without_indexing(index, value), table} + + {:name, index} when action == :never_store -> + {encode_literal_header_never_indexed(index, value), table} + + :not_found when action in [:store, :store_name] -> + {encode_literal_header_with_indexing(name, value), Table.add(table, name, value)} + + :not_found when action == :no_store -> + {encode_literal_header_without_indexing(name, value), table} + + :not_found when action == :never_store -> + {encode_literal_header_never_indexed(name, value), table} + end + + encode_headers(rest, table, [acc, encoded]) + end + + defp encode_indexed_header(index) do + <<1::1, Types.encode_integer(index, 7)::bitstring>> + end + + defp encode_literal_header_with_indexing(index, value) when is_integer(index) do + [<<1::2, Types.encode_integer(index, 6)::bitstring>>, Types.encode_binary(value, false)] + end + + defp encode_literal_header_with_indexing(name, value) when is_binary(name) do + [<<1::2, 0::6>>, Types.encode_binary(name, false), Types.encode_binary(value, false)] + end + + defp encode_literal_header_without_indexing(index, value) when is_integer(index) do + [<<0::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, false)] + end + + defp encode_literal_header_without_indexing(name, value) when is_binary(name) do + [<<0::4, 0::4>>, Types.encode_binary(name, false), Types.encode_binary(value, false)] + end + + defp encode_literal_header_never_indexed(index, value) when is_integer(index) do + [<<1::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, false)] + end + + defp encode_literal_header_never_indexed(name, value) when is_binary(name) do + [<<1::4, 0::4>>, Types.encode_binary(name, false), Types.encode_binary(value, false)] + end +end diff --git a/.deps/hpax/lib/hpax/huffman.ex b/.deps/hpax/lib/hpax/huffman.ex @@ -0,0 +1,94 @@ +defmodule HPAX.Huffman do + @moduledoc false + + import Bitwise, only: [>>>: 2] + + # This file is downloaded from the spec directly. + # http://httpwg.org/specs/rfc7541.html#huffman.code + table_file = Path.absname("huffman_table", __DIR__) + @external_resource table_file + + entries = + Enum.map(File.stream!(table_file), fn line -> + [byte_value, bits, _hex, bit_count] = + line + |> case do + <<?', _, ?', ?\s, rest::binary>> -> rest + "EOS " <> rest -> rest + _other -> line + end + |> String.replace(["|", "(", ")", "[", "]"], "") + |> String.split() + + byte_value = String.to_integer(byte_value) + bits = String.to_integer(bits, 2) + bit_count = String.to_integer(bit_count) + + {byte_value, bits, bit_count} + end) + + {regular_entries, [eos_entry]} = Enum.split(entries, -1) + {_eos_byte_value, eos_bits, eos_bit_count} = eos_entry + + ## Encoding + + @spec encode(binary()) :: binary() + def encode(binary) do + encode(binary, _acc = <<>>) + end + + for {byte_value, bits, bit_count} <- regular_entries do + defp encode(<<unquote(byte_value), rest::binary>>, acc) do + encode(rest, <<acc::bitstring, unquote(bits)::size(unquote(bit_count))>>) + end + end + + defp encode(<<>>, acc) do + overflowing_bits = rem(bit_size(acc), 8) + + if overflowing_bits == 0 do + acc + else + bits_to_add = 8 - overflowing_bits + + value_of_bits_to_add = + take_significant_bits(unquote(eos_bits), unquote(eos_bit_count), bits_to_add) + + <<acc::bitstring, value_of_bits_to_add::size(bits_to_add)>> + end + end + + ## Decoding + + @spec decode(binary()) :: binary() + def decode(binary) + + for {byte_value, bits, bit_count} <- regular_entries do + def decode(<<unquote(bits)::size(unquote(bit_count)), rest::bitstring>>) do + <<unquote(byte_value), decode(rest)::binary>> + end + end + + def decode(<<>>) do + <<>> + end + + # Use binary syntax for single match context optimization. + def decode(<<padding::bitstring>>) when bit_size(padding) in 1..7 do + padding_size = bit_size(padding) + <<padding::size(padding_size)>> = padding + + if take_significant_bits(unquote(eos_bits), unquote(eos_bit_count), padding_size) == padding do + <<>> + else + throw({:hpax, {:protocol_error, :invalid_huffman_encoding}}) + end + end + + ## Helpers + + @compile {:inline, take_significant_bits: 3} + defp take_significant_bits(value, bit_count, bits_to_take) do + value >>> (bit_count - bits_to_take) + end +end diff --git a/.deps/hpax/lib/hpax/huffman_table b/.deps/hpax/lib/hpax/huffman_table @@ -0,0 +1,257 @@ +( 0) |11111111|11000 1ff8 [13] +( 1) |11111111|11111111|1011000 7fffd8 [23] +( 2) |11111111|11111111|11111110|0010 fffffe2 [28] +( 3) |11111111|11111111|11111110|0011 fffffe3 [28] +( 4) |11111111|11111111|11111110|0100 fffffe4 [28] +( 5) |11111111|11111111|11111110|0101 fffffe5 [28] +( 6) |11111111|11111111|11111110|0110 fffffe6 [28] +( 7) |11111111|11111111|11111110|0111 fffffe7 [28] +( 8) |11111111|11111111|11111110|1000 fffffe8 [28] +( 9) |11111111|11111111|11101010 ffffea [24] +( 10) |11111111|11111111|11111111|111100 3ffffffc [30] +( 11) |11111111|11111111|11111110|1001 fffffe9 [28] +( 12) |11111111|11111111|11111110|1010 fffffea [28] +( 13) |11111111|11111111|11111111|111101 3ffffffd [30] +( 14) |11111111|11111111|11111110|1011 fffffeb [28] +( 15) |11111111|11111111|11111110|1100 fffffec [28] +( 16) |11111111|11111111|11111110|1101 fffffed [28] +( 17) |11111111|11111111|11111110|1110 fffffee [28] +( 18) |11111111|11111111|11111110|1111 fffffef [28] +( 19) |11111111|11111111|11111111|0000 ffffff0 [28] +( 20) |11111111|11111111|11111111|0001 ffffff1 [28] +( 21) |11111111|11111111|11111111|0010 ffffff2 [28] +( 22) |11111111|11111111|11111111|111110 3ffffffe [30] +( 23) |11111111|11111111|11111111|0011 ffffff3 [28] +( 24) |11111111|11111111|11111111|0100 ffffff4 [28] +( 25) |11111111|11111111|11111111|0101 ffffff5 [28] +( 26) |11111111|11111111|11111111|0110 ffffff6 [28] +( 27) |11111111|11111111|11111111|0111 ffffff7 [28] +( 28) |11111111|11111111|11111111|1000 ffffff8 [28] +( 29) |11111111|11111111|11111111|1001 ffffff9 [28] +( 30) |11111111|11111111|11111111|1010 ffffffa [28] +( 31) |11111111|11111111|11111111|1011 ffffffb [28] +' ' ( 32) |010100 14 [ 6] +'!' ( 33) |11111110|00 3f8 [10] +'"' ( 34) |11111110|01 3f9 [10] +'#' ( 35) |11111111|1010 ffa [12] +'$' ( 36) |11111111|11001 1ff9 [13] +'%' ( 37) |010101 15 [ 6] +'&' ( 38) |11111000 f8 [ 8] +''' ( 39) |11111111|010 7fa [11] +'(' ( 40) |11111110|10 3fa [10] +')' ( 41) |11111110|11 3fb [10] +'*' ( 42) |11111001 f9 [ 8] +'+' ( 43) |11111111|011 7fb [11] +',' ( 44) |11111010 fa [ 8] +'-' ( 45) |010110 16 [ 6] +'.' ( 46) |010111 17 [ 6] +'/' ( 47) |011000 18 [ 6] +'0' ( 48) |00000 0 [ 5] +'1' ( 49) |00001 1 [ 5] +'2' ( 50) |00010 2 [ 5] +'3' ( 51) |011001 19 [ 6] +'4' ( 52) |011010 1a [ 6] +'5' ( 53) |011011 1b [ 6] +'6' ( 54) |011100 1c [ 6] +'7' ( 55) |011101 1d [ 6] +'8' ( 56) |011110 1e [ 6] +'9' ( 57) |011111 1f [ 6] +':' ( 58) |1011100 5c [ 7] +';' ( 59) |11111011 fb [ 8] +'<' ( 60) |11111111|1111100 7ffc [15] +'=' ( 61) |100000 20 [ 6] +'>' ( 62) |11111111|1011 ffb [12] +'?' ( 63) |11111111|00 3fc [10] +'@' ( 64) |11111111|11010 1ffa [13] +'A' ( 65) |100001 21 [ 6] +'B' ( 66) |1011101 5d [ 7] +'C' ( 67) |1011110 5e [ 7] +'D' ( 68) |1011111 5f [ 7] +'E' ( 69) |1100000 60 [ 7] +'F' ( 70) |1100001 61 [ 7] +'G' ( 71) |1100010 62 [ 7] +'H' ( 72) |1100011 63 [ 7] +'I' ( 73) |1100100 64 [ 7] +'J' ( 74) |1100101 65 [ 7] +'K' ( 75) |1100110 66 [ 7] +'L' ( 76) |1100111 67 [ 7] +'M' ( 77) |1101000 68 [ 7] +'N' ( 78) |1101001 69 [ 7] +'O' ( 79) |1101010 6a [ 7] +'P' ( 80) |1101011 6b [ 7] +'Q' ( 81) |1101100 6c [ 7] +'R' ( 82) |1101101 6d [ 7] +'S' ( 83) |1101110 6e [ 7] +'T' ( 84) |1101111 6f [ 7] +'U' ( 85) |1110000 70 [ 7] +'V' ( 86) |1110001 71 [ 7] +'W' ( 87) |1110010 72 [ 7] +'X' ( 88) |11111100 fc [ 8] +'Y' ( 89) |1110011 73 [ 7] +'Z' ( 90) |11111101 fd [ 8] +'[' ( 91) |11111111|11011 1ffb [13] +'\' ( 92) |11111111|11111110|000 7fff0 [19] +']' ( 93) |11111111|11100 1ffc [13] +'^' ( 94) |11111111|111100 3ffc [14] +'_' ( 95) |100010 22 [ 6] +'`' ( 96) |11111111|1111101 7ffd [15] +'a' ( 97) |00011 3 [ 5] +'b' ( 98) |100011 23 [ 6] +'c' ( 99) |00100 4 [ 5] +'d' (100) |100100 24 [ 6] +'e' (101) |00101 5 [ 5] +'f' (102) |100101 25 [ 6] +'g' (103) |100110 26 [ 6] +'h' (104) |100111 27 [ 6] +'i' (105) |00110 6 [ 5] +'j' (106) |1110100 74 [ 7] +'k' (107) |1110101 75 [ 7] +'l' (108) |101000 28 [ 6] +'m' (109) |101001 29 [ 6] +'n' (110) |101010 2a [ 6] +'o' (111) |00111 7 [ 5] +'p' (112) |101011 2b [ 6] +'q' (113) |1110110 76 [ 7] +'r' (114) |101100 2c [ 6] +'s' (115) |01000 8 [ 5] +'t' (116) |01001 9 [ 5] +'u' (117) |101101 2d [ 6] +'v' (118) |1110111 77 [ 7] +'w' (119) |1111000 78 [ 7] +'x' (120) |1111001 79 [ 7] +'y' (121) |1111010 7a [ 7] +'z' (122) |1111011 7b [ 7] +'{' (123) |11111111|1111110 7ffe [15] +'|' (124) |11111111|100 7fc [11] +'}' (125) |11111111|111101 3ffd [14] +'~' (126) |11111111|11101 1ffd [13] +(127) |11111111|11111111|11111111|1100 ffffffc [28] +(128) |11111111|11111110|0110 fffe6 [20] +(129) |11111111|11111111|010010 3fffd2 [22] +(130) |11111111|11111110|0111 fffe7 [20] +(131) |11111111|11111110|1000 fffe8 [20] +(132) |11111111|11111111|010011 3fffd3 [22] +(133) |11111111|11111111|010100 3fffd4 [22] +(134) |11111111|11111111|010101 3fffd5 [22] +(135) |11111111|11111111|1011001 7fffd9 [23] +(136) |11111111|11111111|010110 3fffd6 [22] +(137) |11111111|11111111|1011010 7fffda [23] +(138) |11111111|11111111|1011011 7fffdb [23] +(139) |11111111|11111111|1011100 7fffdc [23] +(140) |11111111|11111111|1011101 7fffdd [23] +(141) |11111111|11111111|1011110 7fffde [23] +(142) |11111111|11111111|11101011 ffffeb [24] +(143) |11111111|11111111|1011111 7fffdf [23] +(144) |11111111|11111111|11101100 ffffec [24] +(145) |11111111|11111111|11101101 ffffed [24] +(146) |11111111|11111111|010111 3fffd7 [22] +(147) |11111111|11111111|1100000 7fffe0 [23] +(148) |11111111|11111111|11101110 ffffee [24] +(149) |11111111|11111111|1100001 7fffe1 [23] +(150) |11111111|11111111|1100010 7fffe2 [23] +(151) |11111111|11111111|1100011 7fffe3 [23] +(152) |11111111|11111111|1100100 7fffe4 [23] +(153) |11111111|11111110|11100 1fffdc [21] +(154) |11111111|11111111|011000 3fffd8 [22] +(155) |11111111|11111111|1100101 7fffe5 [23] +(156) |11111111|11111111|011001 3fffd9 [22] +(157) |11111111|11111111|1100110 7fffe6 [23] +(158) |11111111|11111111|1100111 7fffe7 [23] +(159) |11111111|11111111|11101111 ffffef [24] +(160) |11111111|11111111|011010 3fffda [22] +(161) |11111111|11111110|11101 1fffdd [21] +(162) |11111111|11111110|1001 fffe9 [20] +(163) |11111111|11111111|011011 3fffdb [22] +(164) |11111111|11111111|011100 3fffdc [22] +(165) |11111111|11111111|1101000 7fffe8 [23] +(166) |11111111|11111111|1101001 7fffe9 [23] +(167) |11111111|11111110|11110 1fffde [21] +(168) |11111111|11111111|1101010 7fffea [23] +(169) |11111111|11111111|011101 3fffdd [22] +(170) |11111111|11111111|011110 3fffde [22] +(171) |11111111|11111111|11110000 fffff0 [24] +(172) |11111111|11111110|11111 1fffdf [21] +(173) |11111111|11111111|011111 3fffdf [22] +(174) |11111111|11111111|1101011 7fffeb [23] +(175) |11111111|11111111|1101100 7fffec [23] +(176) |11111111|11111111|00000 1fffe0 [21] +(177) |11111111|11111111|00001 1fffe1 [21] +(178) |11111111|11111111|100000 3fffe0 [22] +(179) |11111111|11111111|00010 1fffe2 [21] +(180) |11111111|11111111|1101101 7fffed [23] +(181) |11111111|11111111|100001 3fffe1 [22] +(182) |11111111|11111111|1101110 7fffee [23] +(183) |11111111|11111111|1101111 7fffef [23] +(184) |11111111|11111110|1010 fffea [20] +(185) |11111111|11111111|100010 3fffe2 [22] +(186) |11111111|11111111|100011 3fffe3 [22] +(187) |11111111|11111111|100100 3fffe4 [22] +(188) |11111111|11111111|1110000 7ffff0 [23] +(189) |11111111|11111111|100101 3fffe5 [22] +(190) |11111111|11111111|100110 3fffe6 [22] +(191) |11111111|11111111|1110001 7ffff1 [23] +(192) |11111111|11111111|11111000|00 3ffffe0 [26] +(193) |11111111|11111111|11111000|01 3ffffe1 [26] +(194) |11111111|11111110|1011 fffeb [20] +(195) |11111111|11111110|001 7fff1 [19] +(196) |11111111|11111111|100111 3fffe7 [22] +(197) |11111111|11111111|1110010 7ffff2 [23] +(198) |11111111|11111111|101000 3fffe8 [22] +(199) |11111111|11111111|11110110|0 1ffffec [25] +(200) |11111111|11111111|11111000|10 3ffffe2 [26] +(201) |11111111|11111111|11111000|11 3ffffe3 [26] +(202) |11111111|11111111|11111001|00 3ffffe4 [26] +(203) |11111111|11111111|11111011|110 7ffffde [27] +(204) |11111111|11111111|11111011|111 7ffffdf [27] +(205) |11111111|11111111|11111001|01 3ffffe5 [26] +(206) |11111111|11111111|11110001 fffff1 [24] +(207) |11111111|11111111|11110110|1 1ffffed [25] +(208) |11111111|11111110|010 7fff2 [19] +(209) |11111111|11111111|00011 1fffe3 [21] +(210) |11111111|11111111|11111001|10 3ffffe6 [26] +(211) |11111111|11111111|11111100|000 7ffffe0 [27] +(212) |11111111|11111111|11111100|001 7ffffe1 [27] +(213) |11111111|11111111|11111001|11 3ffffe7 [26] +(214) |11111111|11111111|11111100|010 7ffffe2 [27] +(215) |11111111|11111111|11110010 fffff2 [24] +(216) |11111111|11111111|00100 1fffe4 [21] +(217) |11111111|11111111|00101 1fffe5 [21] +(218) |11111111|11111111|11111010|00 3ffffe8 [26] +(219) |11111111|11111111|11111010|01 3ffffe9 [26] +(220) |11111111|11111111|11111111|1101 ffffffd [28] +(221) |11111111|11111111|11111100|011 7ffffe3 [27] +(222) |11111111|11111111|11111100|100 7ffffe4 [27] +(223) |11111111|11111111|11111100|101 7ffffe5 [27] +(224) |11111111|11111110|1100 fffec [20] +(225) |11111111|11111111|11110011 fffff3 [24] +(226) |11111111|11111110|1101 fffed [20] +(227) |11111111|11111111|00110 1fffe6 [21] +(228) |11111111|11111111|101001 3fffe9 [22] +(229) |11111111|11111111|00111 1fffe7 [21] +(230) |11111111|11111111|01000 1fffe8 [21] +(231) |11111111|11111111|1110011 7ffff3 [23] +(232) |11111111|11111111|101010 3fffea [22] +(233) |11111111|11111111|101011 3fffeb [22] +(234) |11111111|11111111|11110111|0 1ffffee [25] +(235) |11111111|11111111|11110111|1 1ffffef [25] +(236) |11111111|11111111|11110100 fffff4 [24] +(237) |11111111|11111111|11110101 fffff5 [24] +(238) |11111111|11111111|11111010|10 3ffffea [26] +(239) |11111111|11111111|1110100 7ffff4 [23] +(240) |11111111|11111111|11111010|11 3ffffeb [26] +(241) |11111111|11111111|11111100|110 7ffffe6 [27] +(242) |11111111|11111111|11111011|00 3ffffec [26] +(243) |11111111|11111111|11111011|01 3ffffed [26] +(244) |11111111|11111111|11111100|111 7ffffe7 [27] +(245) |11111111|11111111|11111101|000 7ffffe8 [27] +(246) |11111111|11111111|11111101|001 7ffffe9 [27] +(247) |11111111|11111111|11111101|010 7ffffea [27] +(248) |11111111|11111111|11111101|011 7ffffeb [27] +(249) |11111111|11111111|11111111|1110 ffffffe [28] +(250) |11111111|11111111|11111101|100 7ffffec [27] +(251) |11111111|11111111|11111101|101 7ffffed [27] +(252) |11111111|11111111|11111101|110 7ffffee [27] +(253) |11111111|11111111|11111101|111 7ffffef [27] +(254) |11111111|11111111|11111110|000 7fffff0 [27] +(255) |11111111|11111111|11111011|10 3ffffee [26] +EOS (256) |11111111|11111111|11111111|111111 3fffffff [30] diff --git a/.deps/hpax/lib/hpax/table.ex b/.deps/hpax/lib/hpax/table.ex @@ -0,0 +1,277 @@ +defmodule HPAX.Table do + @moduledoc false + + defstruct [ + :max_table_size, + entries: [], + size: 0, + length: 0 + ] + + @type t() :: %__MODULE__{ + max_table_size: non_neg_integer(), + entries: [{binary(), binary()}], + size: non_neg_integer(), + length: non_neg_integer() + } + + @static_table [ + {":authority", nil}, + {":method", "GET"}, + {":method", "POST"}, + {":path", "/"}, + {":path", "/index.html"}, + {":scheme", "http"}, + {":scheme", "https"}, + {":status", "200"}, + {":status", "204"}, + {":status", "206"}, + {":status", "304"}, + {":status", "400"}, + {":status", "404"}, + {":status", "500"}, + {"accept-charset", nil}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", nil}, + {"accept-ranges", nil}, + {"accept", nil}, + {"access-control-allow-origin", nil}, + {"age", nil}, + {"allow", nil}, + {"authorization", nil}, + {"cache-control", nil}, + {"content-disposition", nil}, + {"content-encoding", nil}, + {"content-language", nil}, + {"content-length", nil}, + {"content-location", nil}, + {"content-range", nil}, + {"content-type", nil}, + {"cookie", nil}, + {"date", nil}, + {"etag", nil}, + {"expect", nil}, + {"expires", nil}, + {"from", nil}, + {"host", nil}, + {"if-match", nil}, + {"if-modified-since", nil}, + {"if-none-match", nil}, + {"if-range", nil}, + {"if-unmodified-since", nil}, + {"last-modified", nil}, + {"link", nil}, + {"location", nil}, + {"max-forwards", nil}, + {"proxy-authenticate", nil}, + {"proxy-authorization", nil}, + {"range", nil}, + {"referer", nil}, + {"refresh", nil}, + {"retry-after", nil}, + {"server", nil}, + {"set-cookie", nil}, + {"strict-transport-security", nil}, + {"transfer-encoding", nil}, + {"user-agent", nil}, + {"vary", nil}, + {"via", nil}, + {"www-authenticate", nil} + ] + + @static_table_size length(@static_table) + @dynamic_table_start @static_table_size + 1 + + @doc """ + Creates a new HPACK table with the given maximum size. + + The maximum size is not the maximum number of entries but rather the maximum size as defined in + http://httpwg.org/specs/rfc7541.html#maximum.table.size. + """ + @spec new(non_neg_integer()) :: t() + def new(max_table_size) do + %__MODULE__{max_table_size: max_table_size} + end + + @doc """ + Adds the given header to the given table. + + If the new entry does not fit within the max table size then the oldest entries will be evicted. + + Header names should be lowercase when added to the HPACK table + as per the [HTTP/2 spec](https://http2.github.io/http2-spec/#rfc.section.8.1.2): + + > header field names MUST be converted to lowercase prior to their encoding in HTTP/2 + + """ + @spec add(t(), binary(), binary()) :: t() + def add(%__MODULE__{} = table, name, value) do + %{max_table_size: max_table_size, size: size} = table + entry_size = entry_size(name, value) + + cond do + # An attempt to add an entry larger than the maximum size causes the table to be emptied of + # all existing entries and results in an empty table. + entry_size > max_table_size -> + %{table | entries: [], size: 0, length: 0} + + size + entry_size > max_table_size -> + table + |> resize(max_table_size - entry_size) + |> add_header(name, value, entry_size) + + true -> + add_header(table, name, value, entry_size) + end + end + + defp add_header(%__MODULE__{} = table, name, value, entry_size) do + %{entries: entries, size: size, length: length} = table + %{table | entries: [{name, value} | entries], size: size + entry_size, length: length + 1} + end + + @doc """ + Looks up a header by index `index` in the given `table`. + + Returns `{:ok, {name, value}}` if a header is found at the given `index`, otherwise returns + `:error`. `value` can be a binary in case both the header name and value are present in the + table, or `nil` if only the name is present (this can only happen in the static table). + """ + @spec lookup_by_index(t(), pos_integer()) :: {:ok, {binary(), binary() | nil}} | :error + def lookup_by_index(table, index) + + # Static table + for {header, index} <- Enum.with_index(@static_table, 1) do + def lookup_by_index(%__MODULE__{}, unquote(index)), do: {:ok, unquote(header)} + end + + def lookup_by_index(%__MODULE__{length: 0}, _index) do + :error + end + + def lookup_by_index(%__MODULE__{entries: entries, length: length}, index) + when index in @dynamic_table_start..(@dynamic_table_start + length - 1) do + {:ok, Enum.at(entries, index - @dynamic_table_start)} + end + + def lookup_by_index(%__MODULE__{}, _index) do + :error + end + + @doc """ + Looks up the index of a header by its name and value. + + It returns: + + * `{:full, index}` if the full header (name and value) are present in the table at `index` + + * `{:name, index}` if `name` is present in the table but with a different value than `value` + + * `:not_found` if the header name is not in the table at all + + Header names should be lowercase when looked up in the HPACK table + as per the [HTTP/2 spec](https://http2.github.io/http2-spec/#rfc.section.8.1.2): + + > header field names MUST be converted to lowercase prior to their encoding in HTTP/2 + + """ + @spec lookup_by_header(t(), binary(), binary() | nil) :: + {:full, pos_integer()} | {:name, pos_integer()} | :not_found + def lookup_by_header(table, name, value) + + def lookup_by_header(%__MODULE__{entries: entries}, name, value) do + case static_lookup_by_header(name, value) do + {:full, _index} = result -> + result + + {:name, index} -> + # Check if we get full match in the dynamic tabble + case dynamic_lookup_by_header(entries, name, value, @dynamic_table_start, nil) do + {:full, _index} = result -> result + _other -> {:name, index} + end + + :not_found -> + dynamic_lookup_by_header(entries, name, value, @dynamic_table_start, nil) + end + end + + for {{name, value}, index} when is_binary(value) <- Enum.with_index(@static_table, 1) do + defp static_lookup_by_header(unquote(name), unquote(value)) do + {:full, unquote(index)} + end + end + + static_table_names = + @static_table + |> Enum.map(&elem(&1, 0)) + |> Enum.with_index(1) + |> Enum.uniq_by(&elem(&1, 0)) + + for {name, index} <- static_table_names do + defp static_lookup_by_header(unquote(name), _value) do + {:name, unquote(index)} + end + end + + defp static_lookup_by_header(_name, _value) do + :not_found + end + + defp dynamic_lookup_by_header([{name, value} | _rest], name, value, index, _name_index) do + {:full, index} + end + + defp dynamic_lookup_by_header([{name, _} | rest], name, value, index, _name_index) do + dynamic_lookup_by_header(rest, name, value, index + 1, index) + end + + defp dynamic_lookup_by_header([_other | rest], name, value, index, name_index) do + dynamic_lookup_by_header(rest, name, value, index + 1, name_index) + end + + defp dynamic_lookup_by_header([], _name, _value, _index, name_index) do + if name_index, do: {:name, name_index}, else: :not_found + end + + @doc """ + Resizes the table. + + If the existing entries do not fit in the new table size the oldest entries are evicted. + """ + @spec resize(t(), non_neg_integer()) :: t() + def resize(%__MODULE__{entries: entries, size: size} = table, new_size) do + {new_entries_reversed, new_size} = evict_towards_size(Enum.reverse(entries), size, new_size) + + %{ + table + | entries: Enum.reverse(new_entries_reversed), + size: new_size, + length: length(new_entries_reversed) + } + end + + defp evict_towards_size([{name, value} | rest], size, max_target_size) do + new_size = size - entry_size(name, value) + + if new_size <= max_target_size do + {rest, new_size} + else + evict_towards_size(rest, new_size, max_target_size) + end + end + + defp evict_towards_size([], 0, _max_target_size) do + {[], 0} + end + + defp entry_size(name, value) do + byte_size(name) + byte_size(value) + 32 + end + + # Made public to be used in tests. + @doc false + def __static_table__() do + @static_table + end +end diff --git a/.deps/hpax/lib/hpax/types.ex b/.deps/hpax/lib/hpax/types.ex @@ -0,0 +1,89 @@ +defmodule HPAX.Types do + @moduledoc false + + import Bitwise, only: [<<<: 2] + + alias HPAX.Huffman + + # This is used as a macro and not an inlined function because we want to be able to use it in + # guards. + defmacrop power_of_two(n) do + quote do: 1 <<< unquote(n) + end + + ## Encoding + + @spec encode_integer(non_neg_integer(), 1..8) :: bitstring() + def encode_integer(integer, prefix) + + def encode_integer(integer, prefix) when integer < power_of_two(prefix) - 1 do + <<integer::size(prefix)>> + end + + def encode_integer(integer, prefix) do + initial = power_of_two(prefix) - 1 + remaining = integer - initial + <<initial::size(prefix), encode_remaining_integer(remaining)::binary>> + end + + defp encode_remaining_integer(remaining) when remaining >= 128 do + first = rem(remaining, 128) + 128 + <<first::8, encode_remaining_integer(div(remaining, 128))::binary>> + end + + defp encode_remaining_integer(remaining) do + <<remaining::8>> + end + + @spec encode_binary(binary(), boolean()) :: iodata() + def encode_binary(binary, huffman?) do + binary = if huffman?, do: Huffman.encode(binary), else: binary + huffman_bit = if huffman?, do: 1, else: 0 + binary_size = encode_integer(byte_size(binary), 7) + [<<huffman_bit::1, binary_size::bitstring>>, binary] + end + + ## Decoding + + @spec decode_integer(bitstring, 1..8) :: {:ok, non_neg_integer(), binary()} | :error + def decode_integer(bitstring, prefix) when is_bitstring(bitstring) and prefix in 1..8 do + with <<value::size(prefix), rest::binary>> <- bitstring do + if value < power_of_two(prefix) - 1 do + {:ok, value, rest} + else + decode_remaining_integer(rest, value, 0) + end + else + _ -> :error + end + end + + defp decode_remaining_integer(<<0::1, value::7, rest::binary>>, int, m) do + {:ok, int + (value <<< m), rest} + end + + defp decode_remaining_integer(<<1::1, value::7, rest::binary>>, int, m) do + decode_remaining_integer(rest, int + (value <<< m), m + 7) + end + + defp decode_remaining_integer(_, _, _) do + :error + end + + @spec decode_binary(binary) :: {:ok, binary(), binary()} | :error + def decode_binary(binary) when is_binary(binary) do + with <<huffman_bit::1, rest::bitstring>> <- binary, + {:ok, length, rest} <- decode_integer(rest, 7), + <<contents::binary-size(length), rest::binary>> <- rest do + contents = + case huffman_bit do + 0 -> contents + 1 -> Huffman.decode(contents) + end + + {:ok, contents, rest} + else + _ -> :error + end + end +end diff --git a/.deps/hpax/mix.exs b/.deps/hpax/mix.exs @@ -0,0 +1,53 @@ +defmodule HPAX.MixProject do + use Mix.Project + + @version "0.1.2" + @repo_url "https://github.com/elixir-mint/hpax" + + def project do + [ + app: :hpax, + version: @version, + elixir: "~> 1.5", + start_permanent: Mix.env() == :prod, + deps: deps(), + + # Tests + test_coverage: [tool: ExCoveralls], + + # Hex + package: package(), + description: "Implementation of the HPACK protocol (RFC 7541) for Elixir", + + # Docs + name: "HPAX", + docs: [ + source_ref: "v#{@version}", + source_url: @repo_url + ] + ] + end + + def application do + [ + extra_applications: [] + ] + end + + defp deps do + [ + {:ex_doc, "~> 0.20", only: :dev}, + {:hpack, ">= 0.0.0", hex: :hpack_erl, only: :test}, + {:stream_data, "~> 0.5.0", only: [:dev, :test]}, + {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, + {:excoveralls, "~> 0.14", only: :test} + ] + end + + defp package do + [ + licenses: ["Apache-2.0"], + links: %{"GitHub" => @repo_url} + ] + end +end diff --git a/.deps/mint/.fetch b/.deps/mint/.fetch diff --git a/.deps/mint/.formatter.exs b/.deps/mint/.formatter.exs @@ -0,0 +1,11 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{lib,test}/**/*.{ex,exs}"], + import_deps: [:stream_data], + locals_without_parens: [ + assert_round_trip: 1, + assert_recv_frames: 1, + assert_http2_error: 2, + assert_transport_error: 2 + ] +] diff --git a/.deps/mint/.hex b/.deps/mint/.hex Binary files differ. diff --git a/.deps/mint/CHANGELOG.md b/.deps/mint/CHANGELOG.md @@ -0,0 +1,150 @@ +# Changelog + +## v1.4.2 + +### Bug fixes and improvements + + * Properly handle interim responses (informational `1xx` status codes) in + HTTP/2. Now you might get zero or more sequences of `:status` and `:headers` + responses with status `1xx` before the *final response* (with status + non-`1xx`). + +## v1.4.1 + +### Bug fixes and improvements + + * Emit the remaining buffer as a `:data` response when switching protocols + from HTTP/1. + * Respect closed-for-writing when streaming data frames in HTTP/2. + * Fix handling of HTTP/2 frames of an unknown type. + +## v1.4.0 + +### Bug fixes and improvements + + * Add support for `SETTINGS_ENABLE_CONNECT_PROTOCOL` HTTP/2 server setting. + * Omit the `:scheme` and `:path` pseudo headers for HTTP/2 CONNECT. + * Fix invalid connection state when data can't be sent. + * Skip expired certs in partial chain hook. + * Add `Mint.HTTP.get_proxy_headers/1`. + * Add `Mint.HTTP.module/1`. + +## v1.3.0 + +### Bug fixes and improvements + + * Improve compatibility with OTP 24. + * Support HTTP/1 pipelining when streaming requests. + * Add `Mint.HTTP.get_socket/1` for returning the connection socket. + * Improve compatibility with TLS 1.3. + +## v1.2.1 + +### Bug fixes and improvements + + * Fix a bug where we were not ignoring the return value of `:ssl.close/1` and `:gen_tcp.close/1`. + * Fix a bug where we were not properly handling transport errors when doing ALPN protocol negotiation. + * Fix a bug where we were not handling connection closed errors in a few places. + +## v1.2.0 + +### Bug fixes and improvements + + * Fix a few bugs with passing the Mint connection around. + * Add IPv6 support with `inet6: true` in the transport options. + * Cache the `:cacertfile` option for faster certificate lookup and decoding. + * Add TLS 1.3 to default versions. + +## v1.1.0 + +### Bug fixes and improvements + + * Concatenate values in one `cookie` header if the `cookie` header is provided more than once in HTTP/2. + * Fix headers merging in `Mint.UnsafeProxy`. + * Remove some `Logger.debug/1` calls from the codebase. + * Assume the HTTP/2 protocol on TCP connections if using `Mint.HTTP2`. + * Fix a bug where we would send `WINDOW_UPDATE` frames with an increment of `0` in HTTP/2. + * Make the empty body chunk a no-op for `Mint.HTTP.stream_request_body/3` (only for HTTP/1). + * Add the `Mint.HTTP.is_connection_message/2` guard. + * Fix wildcard certificate verification in OTP 23. + +## v1.0.0 + +### Breaking changes + + * Remove the deprecated `Mint.HTTP.request/4`, `Mint.HTTP1.request/4`, and `Mint.HTTP2.request/4`. + +## v0.5.0 + +### Bug fixes and improvements + + * Deprecate `Mint.HTTP.request/4` in favor of explicitly passing the body every time in `Mint.HTTP.request/5`. Same for `Mint.HTTP1` and `Mint.HTTP2`. + * Don't include port in the `authority` header if it's the default port for the used protocol. + * Add a default `content-length` header in HTTP/2 + * Allow passing headers to proxies with the `:proxy_headers` option. + * Fix a bug with HTTP/1 chunking. + +## v0.4.0 + +### Bug fixes and improvements + + * Fix a small bug with double "wrapping" of some `Mint.TransportError`s. + * Prevent unnecessary buffer allocations in the connections (less memory waste!). + * Add support for chunked transfer-encoding in HTTP/1 requests when you don't use `content-encoding`/`transfer-encoding` yourself. + * Add support for trailing headers in HTTP/* requests through `stream_request_body/3`. + * Add a page about decompressing responses in the guides. + +## v0.3.0 + +### Breaking changes + + * Remove `Mint.HTTP1.get_socket/1`, `Mint.HTTP2.get_socket/1`, and `Mint.HTTP.get_socket/1`. + +### Bug fixes and improvements + + * Downcase all headers in HTTP/2 to mimic the behavior in HTTP/1.1. + + * Add `Mint.HTTP.set_mode/2`, `Mint.HTTP1.set_mode/2`, and `Mint.HTTP2.set_mode/2` to change the mode of a socket between active and passive. + + * Add a `:mode` option to the `connect/4` functions to start the socket in active or passive mode. + + * Add `Mint.HTTP.recv/3`, `Mint.HTTP1.recv/3`, and `Mint.HTTP2.recv/3` to receive data from a passive socket in a blocking way. + + * Add `Mint.HTTP.controlling_process/2`, `Mint.HTTP1.controlling_process/2`, and `Mint.HTTP2.controlling_process/2` to change the controlling process of a connection. + + * Support trailing response headers in HTTP/2 connections. + +## v0.2.1 + +### Bug fixes and improvements + + * Fix a bug with requests exceeding the window size in HTTP/2. We were sending the headers of a request even if the body was larger than the window size. Now, if the body is larger than the window size, we error out right away. + + * Fix a bug in the HTTP/2 handshake that would crash the connection in case the server sent unforeseen frames. + + * Improve buffering of body chunks in HTTP/1. + +## v0.2.0 + +### Breaking changes + + * Add the `Mint.TransportError` and `Mint.HTTPError` exceptions. Change all the connection functions so that they return these error structs instead of generic terms. + * Remove `Mint.HTTP2.get_setting/2` in favour of `Mint.HTTP2.get_server_setting/2` and `Mint.HTTP2.get_client_setting/2`. + +### Bug fixes and enhancements + + * Add support for HTTP/2 server push with the new `:push_promise` response. + * Add `Mint.HTTP2.cancel_request/5`. + * Add `Mint.HTTP2.get_window_size/2`. + * Add `open_request_count/1` function to `Mint.HTTP`, and `Mint.HTTP1`, `Mint.HTTP2`. + * Add `open?/2` function to `Mint.HTTP`, and `Mint.HTTP1`, `Mint.HTTP2`. + * Make the `Mint.HTTP2.HPACK` module private. + * Take into account the max header list size advertised by the server in HTTP/2 connections. + * Improve error handling in a bunch of `Mint.HTTP2` functions. + * Fix flow control on `WINDOW_UPDATE` frames at the connection level in `Mint.HTTP2`. + * Correctly return timeout errors when connecting. + * Treat HTTP/1 header keys as case-insensitive. + * Prohibit users from streaming on unknown requests in HTTP/2. + * Prohibit the server from violating the client's max concurrent streams setting in HTTP/2. + * Strip whitespace when parsing the `content-length` header in HTTP/1. + * Fix path validation when building HTTP/1 requests, fixes paths with `%NN` escapes. diff --git a/.deps/mint/LICENSE.txt b/.deps/mint/LICENSE.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/.deps/mint/README.md b/.deps/mint/README.md @@ -0,0 +1,134 @@ +# Mint 🌱 + +[![Build Status](https://travis-ci.org/elixir-mint/mint.svg?branch=master)](https://travis-ci.org/elixir-mint/mint) +[![Docs](https://img.shields.io/badge/api-docs-green.svg?style=flat)](https://hexdocs.pm/mint) +[![Hex.pm Version](http://img.shields.io/hexpm/v/mint.svg?style=flat)](https://hex.pm/packages/mint) +[![Coverage Status](https://coveralls.io/repos/github/elixir-mint/mint/badge.svg?branch=main)](https://coveralls.io/github/elixir-mint/mint?branch=main) + +> Functional HTTP client for Elixir with support for HTTP/1 and HTTP/2. + +## Installation + +To install Mint, add it to your `mix.exs` file. Unless you're using your own SSL certificate store, also add the [CAStore][castore] library to your dependencies. + +```elixir +defp deps do + [ + {:castore, "~> 0.1.0"}, + {:mint, "~> 1.0"} + ] +end +``` + +Then, run `$ mix deps.get`. + +## Usage + +Mint is different from most Erlang and Elixir HTTP clients because it provides a process-less architecture. Instead, Mint is based on a functional and immutable data structure that represents an HTTP connection. This data structure wraps a TCP or SSL socket. This allows for more fine-tailored architectures where the developer is responsible for wrapping the connection struct, such as having one process handle multiple connections or having different kinds of processes handle connections. + +Below is an example of a basic interaction with Mint. First, we start a connection through `Mint.HTTP.connect/3`: + +```elixir +iex> {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80) +``` + +This transparently chooses between HTTP/1 and HTTP/2. Requests are sent with: + +```elixir +iex> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], "") +``` + +The connection socket runs in [*active mode*](http://erlang.org/doc/man/inet.html#setopts-2) (with `active: :once`), which means that the user of the library needs to handle [TCP messages](http://erlang.org/doc/man/gen_tcp.html#connect-4) and [SSL messages](http://erlang.org/doc/man/ssl.html#id66002): + +```elixir +iex> flush() +{:tcp, #Port<0.8>, + "HTTP/1.1 200 OK\r\n" <> _} +``` + +To handle such messages, Mint provides a `stream/2` function that turns messages into HTTP responses. Responses are streamed back to the user in parts through response parts `:status`, `:headers`, `:data`, and finally `:done`. + + +```elixir +iex> {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80) +iex> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], "") +iex> receive do +...> message -> +...> {:ok, conn, responses} = Mint.HTTP.stream(conn, message) +...> IO.inspect responses +...> end +[ + {:status, #Reference<...>, 200}, + {:headers, #Reference<...>, [{"connection", "keep-alive"}, ...}, + {:data, #Reference<...>, "<!DOCTYPE html>..."}, + {:done, #Reference<...>} +] +``` + +The connection API is stateless, this means that you need to make sure to always save the returned `conn`: + +```elixir +# Wrong +{:ok, _conn, ref} = Mint.HTTP.request(conn, "GET", "/foo", [], "") +{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/bar", [], "") + +# Correct +{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/foo", [], "") +{:ok, conn, ref} = Mint.HTTP.request(conn, "GET", "/bar", [], "") +``` + +For more information, see [the documentation][documentation]. + +### SSL certificates + +When using SSL, you can pass in your own CA certificate store or use one provided by Mint. Mint doesn't ship with the certificate store itself, but it has an optional dependency on [CAStore][castore], which provides an up-to-date certificate store. If you don't want to use your own certificate store, just add `:castore` to your dependencies. + +```elixir +def deps do + [ + {:castore, "~> 0.1.0"}, + {:mint, "~> 0.4.0"} + ] +end +``` + +### WebSocket Support + +Mint itself does not support the WebSocket protocol, but it can be used as the foundation to build a WebSocket client on top of. If you need WebSocket support, you can use [mint_web_socket]. + +### Connection Management and Pooling + +Mint is a low-level client. If you need higher-level features such as connection management, pooling, metrics, and more, check out [Finch], a project built on top of Mint that provides those things. + +## Contributing + +If you wish to contribute check out the [issue list](https://github.com/elixir-mint/mint/issues) and let us know what you want to work on so we can discuss it and reduce duplicate work. + +Tests are organized with tags. Integration tests that hit real websites over the internet are tagged with `:requires_internet_connection`. Proxy tests are tagged with `:proxy` and require that you run `docker-compose up` from the Mint root directory in order to run (they are excluded by default when you run `$ mix test`). A few examples of running tests: + + * `$ mix test` to run the test suite without caring about Docker and `docker-compose up`. + + * `$ mix test --exclude integration` to only run local tests (for example, you don't have an internet connection available). + + * `$ mix test --include proxy` to run all tests, including proxy tests. + +## License + +Copyright 2018 Eric Meadows-Jönsson and Andrea Leopardi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[castore]: https://github.com/elixir-mint/castore +[documentation]: https://hexdocs.pm/mint +[mint_web_socket]: https://github.com/elixir-mint/mint_web_socket +[Finch]: https://github.com/sneako/finch diff --git a/.deps/mint/hex_metadata.config b/.deps/mint/hex_metadata.config @@ -0,0 +1,34 @@ +{<<"app">>,<<"mint">>}. +{<<"build_tools">>,[<<"mix">>]}. +{<<"description">>,<<"Small and composable HTTP client.">>}. +{<<"elixir">>,<<"~> 1.5">>}. +{<<"files">>, + [<<"lib">>,<<"lib/mint">>,<<"lib/mint/http1">>, + <<"lib/mint/http1/response.ex">>,<<"lib/mint/http1/request.ex">>, + <<"lib/mint/http1/parse.ex">>,<<"lib/mint/http2.ex">>, + <<"lib/mint/tunnel_proxy.ex">>,<<"lib/mint/core">>, + <<"lib/mint/core/util.ex">>,<<"lib/mint/core/transport">>, + <<"lib/mint/core/transport/ssl.ex">>,<<"lib/mint/core/transport/tcp.ex">>, + <<"lib/mint/core/transport.ex">>,<<"lib/mint/core/conn.ex">>, + <<"lib/mint/types.ex">>,<<"lib/mint/http_error.ex">>,<<"lib/mint/http2">>, + <<"lib/mint/http2/frame.ex">>,<<"lib/mint/negotiate.ex">>, + <<"lib/mint/http1.ex">>,<<"lib/mint/application.ex">>, + <<"lib/mint/http.ex">>,<<"lib/mint/unsafe_proxy.ex">>, + <<"lib/mint/transport_error.ex">>,<<".formatter.exs">>,<<"mix.exs">>, + <<"README.md">>,<<"LICENSE.txt">>,<<"CHANGELOG.md">>,<<"src">>, + <<"src/mint_shims.erl">>]}. +{<<"licenses">>,[<<"Apache-2.0">>]}. +{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-mint/mint">>}]}. +{<<"name">>,<<"mint">>}. +{<<"requirements">>, + [[{<<"app">>,<<"castore">>}, + {<<"name">>,<<"castore">>}, + {<<"optional">>,true}, + {<<"repository">>,<<"hexpm">>}, + {<<"requirement">>,<<"~> 0.1.0">>}], + [{<<"app">>,<<"hpax">>}, + {<<"name">>,<<"hpax">>}, + {<<"optional">>,false}, + {<<"repository">>,<<"hexpm">>}, + {<<"requirement">>,<<"~> 0.1.1">>}]]}. +{<<"version">>,<<"1.4.2">>}. diff --git a/.deps/mint/lib/mint/application.ex b/.deps/mint/lib/mint/application.ex @@ -0,0 +1,14 @@ +defmodule Mint.Application do + @moduledoc false + use Application + + def start(_type, _args) do + persistent_term = + Code.ensure_loaded?(:persistent_term) and function_exported?(:persistent_term, :get, 2) + + Application.put_env(:mint, :persistent_term, persistent_term) + + opts = [strategy: :one_for_one, name: Mint.Supervisor] + Supervisor.start_link([], opts) + end +end diff --git a/.deps/mint/lib/mint/core/conn.ex b/.deps/mint/lib/mint/core/conn.ex @@ -0,0 +1,61 @@ +defmodule Mint.Core.Conn do + @moduledoc false + + alias Mint.Types + + @type conn() :: term() + + @callback initiate( + module(), + Mint.Types.socket(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, conn()} | {:error, Types.error()} + + @callback open?(conn(), :read | :write | :read_write) :: boolean() + + @callback close(conn()) :: {:ok, conn()} + + @callback request( + conn(), + method :: String.t(), + path :: String.t(), + Types.headers(), + body :: iodata() | nil | :stream + ) :: + {:ok, conn(), Types.request_ref()} + | {:error, conn(), Types.error()} + + @callback stream_request_body( + conn(), + Types.request_ref(), + body_chunk :: iodata() | :eof | {:eof, trailing_headers :: Types.headers()} + ) :: + {:ok, conn()} | {:error, conn(), Types.error()} + + @callback stream(conn(), term()) :: + {:ok, conn(), [Types.response()]} + | {:error, conn(), Types.error(), [Types.response()]} + | :unknown + + @callback open_request_count(conn()) :: non_neg_integer() + + @callback recv(conn(), byte_count :: non_neg_integer(), timeout()) :: + {:ok, conn(), [Types.response()]} + | {:error, conn(), Types.error(), [Types.response()]} + + @callback set_mode(conn(), :active | :passive) :: {:ok, conn()} | {:error, Types.error()} + + @callback controlling_process(conn(), pid()) :: {:ok, conn()} | {:error, Types.error()} + + @callback put_private(conn(), key :: atom(), value :: term()) :: conn() + + @callback get_private(conn(), key :: atom(), default_value :: term()) :: term() + + @callback delete_private(conn(), key :: atom()) :: conn() + + @callback get_socket(conn()) :: Mint.Types.socket() + + @callback get_proxy_headers(conn()) :: Mint.Types.headers() +end diff --git a/.deps/mint/lib/mint/core/transport.ex b/.deps/mint/lib/mint/core/transport.ex @@ -0,0 +1,36 @@ +defmodule Mint.Core.Transport do + @moduledoc false + + @type error() :: {:error, %Mint.TransportError{}} + + alias Mint.Types + + @callback connect(address :: Types.address(), port :: :inet.port_number(), opts :: keyword()) :: + {:ok, Types.socket()} | error() + + @callback upgrade( + Types.socket(), + original_scheme :: Types.scheme(), + hostname :: String.t(), + :inet.port_number(), + opts :: keyword() + ) :: {:ok, Types.socket()} | error() + + @callback negotiated_protocol(Types.socket()) :: + {:ok, protocol :: binary()} | {:error, :protocol_not_negotiated} + + @callback send(Types.socket(), payload :: iodata()) :: :ok | error() + + @callback close(Types.socket()) :: :ok | error() + + @callback recv(Types.socket(), bytes :: non_neg_integer(), timeout()) :: + {:ok, binary()} | error() + + @callback controlling_process(Types.socket(), pid()) :: :ok | error() + + @callback setopts(Types.socket(), opts :: keyword()) :: :ok | error() + + @callback getopts(Types.socket(), opts :: keyword()) :: {:ok, opts :: keyword()} | error() + + @callback wrap_error(reason :: term()) :: %Mint.TransportError{} +end diff --git a/.deps/mint/lib/mint/core/transport/ssl.ex b/.deps/mint/lib/mint/core/transport/ssl.ex @@ -0,0 +1,725 @@ +defmodule Mint.Core.Transport.SSL do + @moduledoc false + + require Logger + require Record + + @behaviour Mint.Core.Transport + + # From RFC7540 appendix A + @blocked_ciphers MapSet.new([ + {:null, :null, :null}, + {:rsa, :null, :md5}, + {:rsa, :null, :sha}, + {:rsa_export, :rc4_40, :md5}, + {:rsa, :rc4_128, :md5}, + {:rsa, :rc4_128, :sha}, + {:rsa_export, :rc2_cbc_40, :md5}, + {:rsa, :idea_cbc, :sha}, + {:rsa_export, :des40_cbc, :sha}, + {:rsa, :des_cbc, :sha}, + {:rsa, :"3des_ede_cbc", :sha}, + {:dh_dss_export, :des40_cbc, :sha}, + {:dh_dss, :des_cbc, :sha}, + {:dh_dss, :"3des_ede_cbc", :sha}, + {:dh_rsa_export, :des40_cbc, :sha}, + {:dh_rsa, :des_cbc, :sha}, + {:dh_rsa, :"3des_ede_cbc", :sha}, + {:dhe_dss_export, :des40_cbc, :sha}, + {:dhe_dss, :des_cbc, :sha}, + {:dhe_dss, :"3des_ede_cbc", :sha}, + {:dhe_rsa_export, :des40_cbc, :sha}, + {:dhe_rsa, :des_cbc, :sha}, + {:dhe_rsa, :"3des_ede_cbc", :sha}, + {:dh_anon_export, :rc4_40, :md5}, + {:dh_anon, :rc4_128, :md5}, + {:dh_anon_export, :des40_cbc, :sha}, + {:dh_anon, :des_cbc, :sha}, + {:dh_anon, :"3des_ede_cbc", :sha}, + {:krb5, :des_cbc, :sha}, + {:krb5, :"3des_ede_cbc", :sha}, + {:krb5, :rc4_128, :sha}, + {:krb5, :idea_cbc, :sha}, + {:krb5, :des_cbc, :md5}, + {:krb5, :"3des_ede_cbc", :md5}, + {:krb5, :rc4_128, :md5}, + {:krb5, :idea_cbc, :md5}, + {:krb5_export, :des_cbc_40, :sha}, + {:krb5_export, :rc2_cbc_40, :sha}, + {:krb5_export, :rc4_40, :sha}, + {:krb5_export, :des_cbc_40, :md5}, + {:krb5_export, :rc2_cbc_40, :md5}, + {:krb5_export, :rc4_40, :md5}, + {:psk, :null, :sha}, + {:dhe_psk, :null, :sha}, + {:rsa_psk, :null, :sha}, + {:rsa, :aes_128_cbc, :sha}, + {:dh_dss, :aes_128_cbc, :sha}, + {:dh_rsa, :aes_128_cbc, :sha}, + {:dhe_dss, :aes_128_cbc, :sha}, + {:dhe_rsa, :aes_128_cbc, :sha}, + {:dh_anon, :aes_128_cbc, :sha}, + {:rsa, :aes_256_cbc, :sha}, + {:dh_dss, :aes_256_cbc, :sha}, + {:dh_rsa, :aes_256_cbc, :sha}, + {:dhe_dss, :aes_256_cbc, :sha}, + {:dhe_rsa, :aes_256_cbc, :sha}, + {:dh_anon, :aes_256_cbc, :sha}, + {:rsa, :null, :sha256}, + {:rsa, :aes_128_cbc, :sha256}, + {:rsa, :aes_256_cbc, :sha256}, + {:dh_dss, :aes_128_cbc, :sha256}, + {:dh_rsa, :aes_128_cbc, :sha256}, + {:dhe_dss, :aes_128_cbc, :sha256}, + {:rsa, :camellia_128_cbc, :sha}, + {:dh_dss, :camellia_128_cbc, :sha}, + {:dh_rsa, :camellia_128_cbc, :sha}, + {:dhe_dss, :camellia_128_cbc, :sha}, + {:dhe_rsa, :camellia_128_cbc, :sha}, + {:dh_anon, :camellia_128_cbc, :sha}, + {:dhe_rsa, :aes_128_cbc, :sha256}, + {:dh_dss, :aes_256_cbc, :sha256}, + {:dh_rsa, :aes_256_cbc, :sha256}, + {:dhe_dss, :aes_256_cbc, :sha256}, + {:dhe_rsa, :aes_256_cbc, :sha256}, + {:dh_anon, :aes_128_cbc, :sha256}, + {:dh_anon, :aes_256_cbc, :sha256}, + {:rsa, :camellia_256_cbc, :sha}, + {:dh_dss, :camellia_256_cbc, :sha}, + {:dh_rsa, :camellia_256_cbc, :sha}, + {:dhe_dss, :camellia_256_cbc, :sha}, + {:dhe_rsa, :camellia_256_cbc, :sha}, + {:dh_anon, :camellia_256_cbc, :sha}, + {:psk, :rc4_128, :sha}, + {:psk, :"3des_ede_cbc", :sha}, + {:psk, :aes_128_cbc, :sha}, + {:psk, :aes_256_cbc, :sha}, + {:dhe_psk, :rc4_128, :sha}, + {:dhe_psk, :"3des_ede_cbc", :sha}, + {:dhe_psk, :aes_128_cbc, :sha}, + {:dhe_psk, :aes_256_cbc, :sha}, + {:rsa_psk, :rc4_128, :sha}, + {:rsa_psk, :"3des_ede_cbc", :sha}, + {:rsa_psk, :aes_128_cbc, :sha}, + {:rsa_psk, :aes_256_cbc, :sha}, + {:rsa, :seed_cbc, :sha}, + {:dh_dss, :seed_cbc, :sha}, + {:dh_rsa, :seed_cbc, :sha}, + {:dhe_dss, :seed_cbc, :sha}, + {:dhe_rsa, :seed_cbc, :sha}, + {:dh_anon, :seed_cbc, :sha}, + {:rsa, :aes_128_gcm, :sha256}, + {:rsa, :aes_256_gcm, :sha384}, + {:dh_rsa, :aes_128_gcm, :sha256}, + {:dh_rsa, :aes_256_gcm, :sha384}, + {:dh_dss, :aes_128_gcm, :sha256}, + {:dh_dss, :aes_256_gcm, :sha384}, + {:dh_anon, :aes_128_gcm, :sha256}, + {:dh_anon, :aes_256_gcm, :sha384}, + {:psk, :aes_128_gcm, :sha256}, + {:psk, :aes_256_gcm, :sha384}, + {:rsa_psk, :aes_128_gcm, :sha256}, + {:rsa_psk, :aes_256_gcm, :sha384}, + {:psk, :aes_128_cbc, :sha256}, + {:psk, :aes_256_cbc, :sha384}, + {:psk, :null, :sha256}, + {:psk, :null, :sha384}, + {:dhe_psk, :aes_128_cbc, :sha256}, + {:dhe_psk, :aes_256_cbc, :sha384}, + {:dhe_psk, :null, :sha256}, + {:dhe_psk, :null, :sha384}, + {:rsa_psk, :aes_128_cbc, :sha256}, + {:rsa_psk, :aes_256_cbc, :sha384}, + {:rsa_psk, :null, :sha256}, + {:rsa_psk, :null, :sha384}, + {:rsa, :camellia_128_cbc, :sha256}, + {:dh_dss, :camellia_128_cbc, :sha256}, + {:dh_rsa, :camellia_128_cbc, :sha256}, + {:dhe_dss, :camellia_128_cbc, :sha256}, + {:dhe_rsa, :camellia_128_cbc, :sha256}, + {:dh_anon, :camellia_128_cbc, :sha256}, + {:rsa, :camellia_256_cbc, :sha256}, + {:dh_dss, :camellia_256_cbc, :sha256}, + {:dh_rsa, :camellia_256_cbc, :sha256}, + {:dhe_dss, :camellia_256_cbc, :sha256}, + {:dhe_rsa, :camellia_256_cbc, :sha256}, + {:dh_anon, :camellia_256_cbc, :sha256}, + {:ecdh_ecdsa, :null, :sha}, + {:ecdh_ecdsa, :rc4_128, :sha}, + {:ecdh_ecdsa, :"3des_ede_cbc", :sha}, + {:ecdh_ecdsa, :aes_128_cbc, :sha}, + {:ecdh_ecdsa, :aes_256_cbc, :sha}, + {:ecdhe_ecdsa, :null, :sha}, + {:ecdhe_ecdsa, :rc4_128, :sha}, + {:ecdhe_ecdsa, :"3des_ede_cbc", :sha}, + {:ecdhe_ecdsa, :aes_128_cbc, :sha}, + {:ecdhe_ecdsa, :aes_256_cbc, :sha}, + {:ecdh_rsa, :null, :sha}, + {:ecdh_rsa, :rc4_128, :sha}, + {:ecdh_rsa, :"3des_ede_cbc", :sha}, + {:ecdh_rsa, :aes_128_cbc, :sha}, + {:ecdh_rsa, :aes_256_cbc, :sha}, + {:ecdhe_rsa, :null, :sha}, + {:ecdhe_rsa, :rc4_128, :sha}, + {:ecdhe_rsa, :"3des_ede_cbc", :sha}, + {:ecdhe_rsa, :aes_128_cbc, :sha}, + {:ecdhe_rsa, :aes_256_cbc, :sha}, + {:ecdh_anon, :null, :sha}, + {:ecdh_anon, :rc4_128, :sha}, + {:ecdh_anon, :"3des_ede_cbc", :sha}, + {:ecdh_anon, :aes_128_cbc, :sha}, + {:ecdh_anon, :aes_256_cbc, :sha}, + {:srp_sha, :"3des_ede_cbc", :sha}, + {:srp_sha_rsa, :"3des_ede_cbc", :sha}, + {:srp_sha_dss, :"3des_ede_cbc", :sha}, + {:srp_sha, :aes_128_cbc, :sha}, + {:srp_sha_rsa, :aes_128_cbc, :sha}, + {:srp_sha_dss, :aes_128_cbc, :sha}, + {:srp_sha, :aes_256_cbc, :sha}, + {:srp_sha_rsa, :aes_256_cbc, :sha}, + {:srp_sha_dss, :aes_256_cbc, :sha}, + {:ecdhe_ecdsa, :aes_128_cbc, :sha256}, + {:ecdhe_ecdsa, :aes_256_cbc, :sha384}, + {:ecdh_ecdsa, :aes_128_cbc, :sha256}, + {:ecdh_ecdsa, :aes_256_cbc, :sha384}, + {:ecdhe_rsa, :aes_128_cbc, :sha256}, + {:ecdhe_rsa, :aes_256_cbc, :sha384}, + {:ecdh_rsa, :aes_128_cbc, :sha256}, + {:ecdh_rsa, :aes_256_cbc, :sha384}, + {:ecdh_ecdsa, :aes_128_gcm, :sha256}, + {:ecdh_ecdsa, :aes_256_gcm, :sha384}, + {:ecdh_rsa, :aes_128_gcm, :sha256}, + {:ecdh_rsa, :aes_256_gcm, :sha384}, + {:ecdhe_psk, :rc4_128, :sha}, + {:ecdhe_psk, :"3des_ede_cbc", :sha}, + {:ecdhe_psk, :aes_128_cbc, :sha}, + {:ecdhe_psk, :aes_256_cbc, :sha}, + {:ecdhe_psk, :aes_128_cbc, :sha256}, + {:ecdhe_psk, :aes_256_cbc, :sha384}, + {:ecdhe_psk, :null, :sha}, + {:ecdhe_psk, :null, :sha256}, + {:ecdhe_psk, :null, :sha384}, + {:rsa, :aria_128_cbc, :sha256}, + {:rsa, :aria_256_cbc, :sha384}, + {:dh_dss, :aria_128_cbc, :sha256}, + {:dh_dss, :aria_256_cbc, :sha384}, + {:dh_rsa, :aria_128_cbc, :sha256}, + {:dh_rsa, :aria_256_cbc, :sha384}, + {:dhe_dss, :aria_128_cbc, :sha256}, + {:dhe_dss, :aria_256_cbc, :sha384}, + {:dhe_rsa, :aria_128_cbc, :sha256}, + {:dhe_rsa, :aria_256_cbc, :sha384}, + {:dh_anon, :aria_128_cbc, :sha256}, + {:dh_anon, :aria_256_cbc, :sha384}, + {:ecdhe_ecdsa, :aria_128_cbc, :sha256}, + {:ecdhe_ecdsa, :aria_256_cbc, :sha384}, + {:ecdh_ecdsa, :aria_128_cbc, :sha256}, + {:ecdh_ecdsa, :aria_256_cbc, :sha384}, + {:ecdhe_rsa, :aria_128_cbc, :sha256}, + {:ecdhe_rsa, :aria_256_cbc, :sha384}, + {:ecdh_rsa, :aria_128_cbc, :sha256}, + {:ecdh_rsa, :aria_256_cbc, :sha384}, + {:rsa, :aria_128_gcm, :sha256}, + {:rsa, :aria_256_gcm, :sha384}, + {:dh_rsa, :aria_128_gcm, :sha256}, + {:dh_rsa, :aria_256_gcm, :sha384}, + {:dh_dss, :aria_128_gcm, :sha256}, + {:dh_dss, :aria_256_gcm, :sha384}, + {:dh_anon, :aria_128_gcm, :sha256}, + {:dh_anon, :aria_256_gcm, :sha384}, + {:ecdh_ecdsa, :aria_128_gcm, :sha256}, + {:ecdh_ecdsa, :aria_256_gcm, :sha384}, + {:ecdh_rsa, :aria_128_gcm, :sha256}, + {:ecdh_rsa, :aria_256_gcm, :sha384}, + {:psk, :aria_128_cbc, :sha256}, + {:psk, :aria_256_cbc, :sha384}, + {:dhe_psk, :aria_128_cbc, :sha256}, + {:dhe_psk, :aria_256_cbc, :sha384}, + {:rsa_psk, :aria_128_cbc, :sha256}, + {:rsa_psk, :aria_256_cbc, :sha384}, + {:psk, :aria_128_gcm, :sha256}, + {:psk, :aria_256_gcm, :sha384}, + {:rsa_psk, :aria_128_gcm, :sha256}, + {:rsa_psk, :aria_256_gcm, :sha384}, + {:ecdhe_psk, :aria_128_cbc, :sha256}, + {:ecdhe_psk, :aria_256_cbc, :sha384}, + {:ecdhe_ecdsa, :camellia_128_cbc, :sha256}, + {:ecdhe_ecdsa, :camellia_256_cbc, :sha384}, + {:ecdh_ecdsa, :camellia_128_cbc, :sha256}, + {:ecdh_ecdsa, :camellia_256_cbc, :sha384}, + {:ecdhe_rsa, :camellia_128_cbc, :sha256}, + {:ecdhe_rsa, :camellia_256_cbc, :sha384}, + {:ecdh_rsa, :camellia_128_cbc, :sha256}, + {:ecdh_rsa, :camellia_256_cbc, :sha384}, + {:rsa, :camellia_128_gcm, :sha256}, + {:rsa, :camellia_256_gcm, :sha384}, + {:dh_rsa, :camellia_128_gcm, :sha256}, + {:dh_rsa, :camellia_256_gcm, :sha384}, + {:dh_dss, :camellia_128_gcm, :sha256}, + {:dh_dss, :camellia_256_gcm, :sha384}, + {:dh_anon, :camellia_128_gcm, :sha256}, + {:dh_anon, :camellia_256_gcm, :sha384}, + {:ecdh_ecdsa, :camellia_128_gcm, :sha256}, + {:ecdh_ecdsa, :camellia_256_gcm, :sha384}, + {:ecdh_rsa, :camellia_128_gcm, :sha256}, + {:ecdh_rsa, :camellia_256_gcm, :sha384}, + {:psk, :camellia_128_gcm, :sha256}, + {:psk, :camellia_256_gcm, :sha384}, + {:rsa_psk, :camellia_128_gcm, :sha256}, + {:rsa_psk, :camellia_256_gcm, :sha384}, + {:psk, :camellia_128_cbc, :sha256}, + {:psk, :camellia_256_cbc, :sha384}, + {:dhe_psk, :camellia_128_cbc, :sha256}, + {:dhe_psk, :camellia_256_cbc, :sha384}, + {:rsa_psk, :camellia_128_cbc, :sha256}, + {:rsa_psk, :camellia_256_cbc, :sha384}, + {:ecdhe_psk, :camellia_128_cbc, :sha256}, + {:ecdhe_psk, :camellia_256_cbc, :sha384}, + {:rsa, :aes_128, :ccm}, + {:rsa, :aes_256, :ccm}, + {:rsa, :aes_128, :ccm_8}, + {:rsa, :aes_256, :ccm_8}, + {:psk, :aes_128, :ccm}, + {:psk, :aes_256, :ccm}, + {:psk, :aes_128, :ccm_8}, + {:psk, :aes_256, :ccm_8} + ]) + + @transport_opts [ + packet: :raw, + mode: :binary, + active: false + ] + + @default_versions [:"tlsv1.3", :"tlsv1.2"] + @default_timeout 30_000 + + Record.defrecordp( + :certificate, + :Certificate, + Record.extract(:Certificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + Record.defrecordp( + :tbs_certificate, + :OTPTBSCertificate, + Record.extract(:OTPTBSCertificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + # TODO: Document how to enable revocation checking: + # crl_check: true + # crl_cache: {:ssl_crl_cache, {:internal, [http: 30_000]}} + + @impl true + def connect(address, port, opts) do + hostname = Mint.Core.Util.hostname(opts, address) + opts = Keyword.delete(opts, :hostname) + + connect(address, hostname, port, opts) + end + + defp connect(address, hostname, port, opts) when is_binary(address), + do: connect(String.to_charlist(address), hostname, port, opts) + + defp connect(address, hostname, port, opts) do + timeout = Keyword.get(opts, :timeout, @default_timeout) + inet6? = Keyword.get(opts, :inet6, false) + + opts = ssl_opts(String.to_charlist(hostname), opts) + + if inet6? do + # Try inet6 first, then fall back to the defaults provided by + # ssl/gen_tcp if connection fails. + case :ssl.connect(address, port, [:inet6 | opts], timeout) do + {:ok, sslsocket} -> + {:ok, sslsocket} + + _error -> + wrap_err(:ssl.connect(address, port, opts, timeout)) + end + else + # Use the defaults provided by ssl/gen_tcp. + wrap_err(:ssl.connect(address, port, opts, timeout)) + end + end + + @impl true + def upgrade(socket, :http, hostname, _port, opts) do + hostname = String.to_charlist(hostname) + timeout = Keyword.get(opts, :timeout, @default_timeout) + + # Seems like this is not set in :ssl.connect/2 correctly, so set it explicitly + Mint.Core.Transport.TCP.setopts(socket, active: false) + + wrap_err(:ssl.connect(socket, ssl_opts(hostname, opts), timeout)) + end + + def upgrade(_socket, :https, _hostname, _port, _opts) do + raise "nested SSL sessions are not supported" + end + + @impl true + def negotiated_protocol(socket) do + wrap_err(:ssl.negotiated_protocol(socket)) + end + + @impl true + def send(socket, payload) do + wrap_err(:ssl.send(socket, payload)) + end + + @impl true + def close(socket) do + wrap_err(:ssl.close(socket)) + end + + @impl true + def recv(socket, bytes, timeout) do + wrap_err(:ssl.recv(socket, bytes, timeout)) + end + + @impl true + def controlling_process(socket, pid) do + # We do this dance because it's what gen_tcp does in Erlang. However, ssl + # doesn't do this so we need to do it ourselves. Implementation roughly + # taken from this: + # https://github.com/erlang/otp/blob/fc1f0444e32b039194189af97fb3d5358a2b91e3/lib/kernel/src/inet.erl#L1696-L1754 + with {:ok, active: active} <- getopts(socket, [:active]), + :ok <- setopts(socket, active: false), + :ok <- forward_messages_to_new_controlling_process(socket, pid), + :ok <- wrap_err(:ssl.controlling_process(socket, pid)) do + if(active == :once, do: setopts(socket, active: :once), else: :ok) + end + end + + defp forward_messages_to_new_controlling_process(socket, pid) do + receive do + {:ssl, ^socket, _data} = message -> + Kernel.send(pid, message) + forward_messages_to_new_controlling_process(socket, pid) + + {:ssl_error, ^socket, error} -> + {:error, error} + + {:ssl_closed, ^socket} -> + {:error, :closed} + after + 0 -> + :ok + end + end + + @impl true + def setopts(socket, opts) do + wrap_err(:ssl.setopts(socket, opts)) + end + + @impl true + def getopts(socket, opts) do + wrap_err(:ssl.getopts(socket, opts)) + end + + @impl true + def wrap_error(reason) do + %Mint.TransportError{reason: reason} + end + + defp ssl_opts(hostname, opts) do + default_ssl_opts(hostname) + |> Keyword.merge(opts) + |> Keyword.merge(@transport_opts) + |> Keyword.drop([:timeout, :inet6]) + |> add_verify_opts(hostname) + |> remove_incompatible_ssl_opts() + |> add_ciphers_opt() + end + + defp add_verify_opts(opts, hostname) do + verify = Keyword.get(opts, :verify) + + if verify == :verify_peer do + opts + |> add_cacerts() + |> add_partial_chain_fun() + |> customize_hostname_check(hostname) + else + opts + end + end + + defp remove_incompatible_ssl_opts(opts) do + # These are the TLS versions that are compatible with :reuse_sessions and :secure_renegotiate + # If none of the compatible TLS versions are present in the transport options, then + # :reuse_sessions and :secure_renegotiate will be removed from the transport options. + compatible_versions = [:tlsv1, :"tlsv1.1", :"tlsv1.2"] + versions_opt = Keyword.get(opts, :versions, []) + + if Enum.any?(compatible_versions, &(&1 in versions_opt)) do + opts + else + opts + |> Keyword.delete(:reuse_sessions) + |> Keyword.delete(:secure_renegotiate) + end + end + + defp customize_hostname_check(opts, host_or_ip) do + if ssl_version() >= [9, 0] do + # From OTP 20.0 use built-in support for custom hostname checks + add_customize_hostname_check(opts) + else + # Before OTP 20.0 use mint_shims for hostname check, from a custom + # verify_fun + add_verify_fun(opts, host_or_ip) + end + end + + defp add_customize_hostname_check(opts) do + Keyword.put_new(opts, :customize_hostname_check, match_fun: &match_fun/2) + end + + defp add_verify_fun(opts, host_or_ip) do + Keyword.put_new_lazy(opts, :verify_fun, fn -> + reference_ids = [dns_id: host_or_ip, ip: host_or_ip] + {&verify_fun/3, reference_ids} + end) + end + + def verify_fun(_, {:bad_cert, _} = reason, _), do: {:fail, reason} + def verify_fun(_, {:extension, _}, state), do: {:unknown, state} + def verify_fun(_, :valid, state), do: {:valid, state} + + def verify_fun(cert, :valid_peer, state) do + if :mint_shims.pkix_verify_hostname(cert, state, match_fun: &match_fun/2) do + {:valid, state} + else + {:fail, {:bad_cert, :hostname_check_failed}} + end + end + + # Wildcard domain handling for DNS ID entries in the subjectAltName X.509 + # extension. Note that this is a subset of the wildcard patterns implemented + # by OTP when matching against the subject CN attribute, but this is the only + # wildcard usage defined by the CA/Browser Forum's Baseline Requirements, and + # therefore the only pattern used in commercially issued certificates. + defp match_fun({:dns_id, reference}, {:dNSName, [?*, ?. | presented]}) do + case domain_without_host(reference) do + '' -> + :default + + domain -> + # TODO: replace with `:string.casefold/1` eventually + :string.to_lower(domain) == :string.to_lower(presented) + end + end + + defp match_fun(_reference, _presented), do: :default + + defp domain_without_host([]), do: [] + defp domain_without_host([?. | domain]), do: domain + defp domain_without_host([_ | more]), do: domain_without_host(more) + + defp add_ciphers_opt(opts) do + Keyword.put_new_lazy(opts, :ciphers, fn -> + versions = opts[:versions] + get_ciphers_for_versions(versions) + end) + end + + defp default_ssl_opts(hostname) do + # TODO: Add revocation check + + # Note: the :ciphers option is added once the :versions option + # has been merged with the user-specified value + [ + server_name_indication: hostname, + versions: ssl_versions(), + verify: :verify_peer, + depth: 4, + secure_renegotiate: true, + reuse_sessions: true + ] + end + + @doc false + def ssl_versions() do + available_versions = :ssl.versions()[:available] + versions = Enum.filter(@default_versions, &(&1 in available_versions)) + + # Remove buggy TLS 1.3 versions + if ssl_version() < [10, 0] do + versions -- [:"tlsv1.3"] + else + versions + end + end + + defp add_cacerts(opts) do + if Keyword.has_key?(opts, :cacertfile) or Keyword.has_key?(opts, :cacerts) do + opts + else + raise_on_missing_castore!() + Keyword.put(opts, :cacertfile, CAStore.file_path()) + end + end + + defp add_partial_chain_fun(opts) do + if Keyword.has_key?(opts, :partial_chain) do + opts + else + case Keyword.fetch(opts, :cacerts) do + {:ok, cacerts} -> + cacerts = decode_cacerts(cacerts) + fun = &partial_chain(cacerts, &1) + Keyword.put(opts, :partial_chain, fun) + + :error -> + path = Keyword.fetch!(opts, :cacertfile) + cacerts = get_cacertfile(path) + fun = &partial_chain(cacerts, &1) + Keyword.put(opts, :partial_chain, fun) + end + end + end + + defp get_cacertfile(path) do + if Application.get_env(:mint, :persistent_term) do + case :persistent_term.get({:mint, {:cacertfile, path}}, :error) do + {:ok, cacerts} -> + cacerts + + :error -> + cacerts = decode_cacertfile(path) + :persistent_term.put({:mint, {:cacertfile, path}}, {:ok, cacerts}) + cacerts + end + else + decode_cacertfile(path) + end + end + + defp decode_cacertfile(path) do + path + |> File.read!() + |> :public_key.pem_decode() + |> Enum.filter(&match?({:Certificate, _, :not_encrypted}, &1)) + |> Enum.map(&:public_key.pem_entry_decode/1) + end + + defp decode_cacerts(certs) do + Enum.map(certs, &:public_key.pkix_decode_cert(&1, :plain)) + end + + def partial_chain(cacerts, certs) do + # TODO: Shim this with OTP 21.1 implementation? + + certs = + certs + |> Enum.map(&{&1, :public_key.pkix_decode_cert(&1, :plain)}) + |> Enum.drop_while(&cert_expired?/1) + + trusted = + Enum.find_value(certs, fn {der, cert} -> + trusted? = + Enum.find(cacerts, fn cacert -> + extract_public_key_info(cacert) == extract_public_key_info(cert) + end) + + if trusted?, do: der + end) + + if trusted do + {:trusted_ca, trusted} + else + :unknown_ca + end + end + + defp cert_expired?({_der, cert}) do + now = DateTime.utc_now() + {not_before, not_after} = extract_validity(cert) + + DateTime.compare(now, not_before) == :lt or + DateTime.compare(now, not_after) == :gt + end + + defp extract_validity(cert) do + {:Validity, not_before, not_after} = + cert + |> certificate(:tbsCertificate) + |> tbs_certificate(:validity) + + {to_datetime!(not_before), to_datetime!(not_after)} + end + + defp extract_public_key_info(cert) do + cert + |> certificate(:tbsCertificate) + |> tbs_certificate(:subjectPublicKeyInfo) + end + + defp to_datetime!({:utcTime, time}) do + "20#{time}" + |> to_datetime!() + end + + defp to_datetime!({:generalTime, time}) do + time + |> to_string() + |> to_datetime!() + end + + defp to_datetime!( + <<year::binary-size(4), month::binary-size(2), day::binary-size(2), hour::binary-size(2), + minute::binary-size(2), second::binary-size(2), "Z"::binary>> + ) do + {:ok, datetime, _} = + DateTime.from_iso8601("#{year}-#{month}-#{day}T#{hour}:#{minute}:#{second}Z") + + datetime + end + + defp blocked_cipher?(%{cipher: cipher, key_exchange: kex, prf: prf}), + do: blocked_cipher?({kex, cipher, prf}) + + defp blocked_cipher?({kex, cipher, _mac, prf}), do: blocked_cipher?({kex, cipher, prf}) + defp blocked_cipher?({_kex, _cipher, _prf} = suite), do: suite in @blocked_ciphers + + defp raise_on_missing_castore! do + Code.ensure_loaded?(CAStore) || + raise """ + default CA trust store not available; please add `:castore` to your project's \ + dependencies or specify the trust store using the :cacertfile/:cacerts option \ + within :transport_options. From OTP 25, you can also use: + + * :public_key.cacerts_get/0 to get certificates that you loaded from files or + * from the OS with :public_key.cacerts_load/0,1 + + See: https://www.erlang.org/blog/my-otp-25-highlights/#ca-certificates-can-be-fetched-from-the-os-standard-place + """ + end + + defp wrap_err({:error, reason}), do: {:error, wrap_error(reason)} + defp wrap_err(other), do: other + + @doc false + def ssl_version() do + Application.spec(:ssl, :vsn) + |> List.to_string() + |> String.split(".") + |> Enum.map(&String.to_integer/1) + end + + @doc false + def get_ciphers_for_versions(versions) do + if ssl_version() >= [8, 2, 4] do + # :ssl.filter_cipher_suites/2 is available in ssl v8.2.4+ + versions + |> Enum.flat_map(&:ssl.filter_cipher_suites(:ssl.cipher_suites(:all, &1), [])) + |> Enum.uniq() + else + :ssl.cipher_suites(:all) + end + |> Enum.reject(&blocked_cipher?/1) + end +end diff --git a/.deps/mint/lib/mint/core/transport/tcp.ex b/.deps/mint/lib/mint/core/transport/tcp.ex @@ -0,0 +1,88 @@ +defmodule Mint.Core.Transport.TCP do + @moduledoc false + + @behaviour Mint.Core.Transport + + @transport_opts [ + packet: :raw, + mode: :binary, + active: false + ] + + @default_timeout 30_000 + + @impl true + def connect(address, port, opts) when is_binary(address), + do: connect(String.to_charlist(address), port, opts) + + def connect(address, port, opts) do + opts = Keyword.delete(opts, :hostname) + + timeout = Keyword.get(opts, :timeout, @default_timeout) + inet6? = Keyword.get(opts, :inet6, false) + + opts = + opts + |> Keyword.merge(@transport_opts) + |> Keyword.drop([:alpn_advertised_protocols, :timeout, :inet6]) + + if inet6? do + # Try inet6 first, then fall back to the defaults provided by + # gen_tcp if connection fails. + case :gen_tcp.connect(address, port, [:inet6 | opts], timeout) do + {:ok, socket} -> + {:ok, socket} + + _error -> + wrap_err(:gen_tcp.connect(address, port, opts, timeout)) + end + else + # Use the defaults provided by gen_tcp. + wrap_err(:gen_tcp.connect(address, port, opts, timeout)) + end + end + + @impl true + def upgrade(socket, _scheme, _hostname, _port, _opts) do + {:ok, socket} + end + + @impl true + def negotiated_protocol(_socket), do: wrap_err({:error, :protocol_not_negotiated}) + + @impl true + def send(socket, payload) do + wrap_err(:gen_tcp.send(socket, payload)) + end + + @impl true + defdelegate close(socket), to: :gen_tcp + + @impl true + def recv(socket, bytes, timeout) do + wrap_err(:gen_tcp.recv(socket, bytes, timeout)) + end + + @impl true + def controlling_process(socket, pid) do + wrap_err(:gen_tcp.controlling_process(socket, pid)) + end + + @impl true + def setopts(socket, opts) do + wrap_err(:inet.setopts(socket, opts)) + end + + @impl true + def getopts(socket, opts) do + wrap_err(:inet.getopts(socket, opts)) + end + + @impl true + def wrap_error(reason) do + %Mint.TransportError{reason: reason} + end + + defp wrap_err({:error, reason}), do: {:error, wrap_error(reason)} + defp wrap_err(other), do: other +end diff --git a/.deps/mint/lib/mint/core/util.ex b/.deps/mint/lib/mint/core/util.ex @@ -0,0 +1,158 @@ +defmodule Mint.Core.Util do + @moduledoc false + + @unallowed_trailing_headers MapSet.new([ + "content-encoding", + "content-length", + "content-range", + "content-type", + "trailer", + "transfer-encoding", + + # Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1) + "cache-control", + "expect", + "host", + "max-forwards", + "pragma", + "range", + "te", + + # Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2) + "if-match", + "if-none-match", + "if-modified-since", + "if-unmodified-since", + "if-range", + + # Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3) + "authorization", + "proxy-authenticate", + "proxy-authorization", + "www-authenticate", + + # Cookie management (https://tools.ietf.org/html/rfc6265) + "cookie", + "set-cookie", + + # Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1) + "age", + "cache-control", + "expires", + "date", + "location", + "retry-after", + "vary", + "warning" + ]) + + # We have to do this if/else dance inside the macro because defguard + # is not available in Elixir 1.5, and macro expansion would raise + # when expanding the if even if we were on Elixir 1.5. This way, we + # only expand to the defguard code if we are on Elixir 1.10 and on + # (which is where this macro is supported). + defmacro define_is_connection_message_guard do + # TODO: Remove the conditional definition when we depend on Elixir 1.10+ + # TODO: Use is_struct/2 and map.field access when we depend on Elixir 1.11+ + if Version.match?(System.version(), ">= 1.10.0") do + quote do + @doc since: "1.1.0" + defguard is_connection_message(conn, message) + when is_map(conn) and + is_tuple(message) and + is_map_key(conn, :__struct__) and + is_map_key(conn, :socket) and + is_atom(:erlang.map_get(:__struct__, conn)) and + elem(message, 1) == :erlang.map_get(:socket, conn) and + ((elem(message, 0) in [:ssl, :tcp] and tuple_size(message) == 3) or + (elem(message, 0) in [:ssl_closed, :tcp_closed] and + tuple_size(message) == 2) or + (elem(message, 0) in [:ssl_error, :tcp_error] and + tuple_size(message) == 3)) + end + else + quote do + defmacro is_connection_message(_conn, _message) do + raise ArgumentError, + "the is_connection_message/2 macro is only available with Elixir 1.10+" + end + end + end + end + + def hostname(opts, address) do + case Keyword.fetch(opts, :hostname) do + {:ok, hostname} -> + hostname + + :error when is_binary(address) -> + address + + :error -> + raise ArgumentError, "the :hostname option is required when address is not a binary" + end + end + + def inet_opts(transport, socket) do + with {:ok, opts} <- transport.getopts(socket, [:sndbuf, :recbuf, :buffer]), + buffer = calculate_buffer(opts), + :ok <- transport.setopts(socket, buffer: buffer) do + :ok + end + end + + def scheme_to_transport(:http), do: Mint.Core.Transport.TCP + def scheme_to_transport(:https), do: Mint.Core.Transport.SSL + def scheme_to_transport(module) when is_atom(module), do: module + + defp calculate_buffer(opts) do + Keyword.fetch!(opts, :buffer) + |> max(Keyword.fetch!(opts, :sndbuf)) + |> max(Keyword.fetch!(opts, :recbuf)) + end + + # Adds a header to the list of headers unless it's nil or it's already there. + def put_new_header(headers, name, value) + + def put_new_header(headers, _name, nil) do + headers + end + + def put_new_header(headers, name, value) do + if List.keymember?(headers, name, 0) do + headers + else + [{name, value} | headers] + end + end + + def put_new_header_lazy(headers, name, fun) do + if List.keymember?(headers, name, 0) do + headers + else + [{name, fun.()} | headers] + end + end + + # Lowercases an ASCII string more efficiently than + # String.downcase/1. + def downcase_ascii(string), + do: for(<<char <- string>>, do: <<downcase_ascii_char(char)>>, into: "") + + def downcase_ascii_char(char) when char in ?A..?Z, do: char + 32 + def downcase_ascii_char(char) when char in 0..127, do: char + + # If the buffer is empty, reusing the incoming data saves + # a potentially large allocation of memory. + # This should be fixed in a subsequent OTP release. + def maybe_concat(<<>>, data), do: data + def maybe_concat(buffer, data) when is_binary(buffer), do: buffer <> data + + def find_unallowed_trailing_header(headers) do + Enum.find(headers, fn {name, _value} -> name in @unallowed_trailing_headers end) + end + + def remove_unallowed_trailing_headers(headers) do + Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailing_headers end) + end +end diff --git a/.deps/mint/lib/mint/http.ex b/.deps/mint/lib/mint/http.ex @@ -0,0 +1,965 @@ +defmodule Mint.HTTP do + @moduledoc """ + Processless HTTP connection data structure and functions. + + Single interface for `Mint.HTTP1` and `Mint.HTTP2` with support for version + negotiation and proxies. + + ## Usage + + To establish a connection with a given server, use `connect/4`. This will + return an opaque data structure that represents the connection + to the server. To send a request, you can use `request/5`. Sending a request + does not take care of the response to that request, instead we use `Mint.HTTP.stream/2` + to process the response, which we will look at in just a bit. The connection is a + wrapper around a TCP (`:gen_tcp` module) or SSL (`:ssl` module) socket that is + set in **active mode** (with `active: :once`). This means that TCP/SSL messages + will be delivered to the process that started the connection. + + The process that owns the connection is responsible for receiving the messages + (for example, a GenServer is responsible for defining `handle_info/2`). However, + `Mint.HTTP` makes it easy to identify TCP/SSL messages that are coming from the + connection with the server with the `stream/2` function. This function takes the + connection and a term and returns `:unknown` if the term is not a TCP/SSL message + belonging to the connection. If the term *is* a message for the connection, then + a response and a new connection are returned. It's important to store the new + returned connection data structure over the old one since the connection is an + immutable data structure. + + Let's see an example of a common workflow of connecting to a server, sending a + request, and processing the response. We start by using `connect/3` to connect + to a server. + + {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80) + + `conn` is a data structure that represents the connection. + + To send a request, we use `request/5`. + + {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], nil) + + As you can see, sending a request returns a new updated `conn` struct and a + `request_ref`. The updated connection struct is returned because the connection + is an immutable structure keeping the connection state, so every action we do on it must return a new, + possibly updated, connection that we're responsible for storing over the old + one. `request_ref` is a unique reference that can be used to identify which + request a given response belongs to. + + Now that we sent our request, we're responsible for receiving the messages that + the TCP/SSL socket will send to our process. For example, in a GenServer + we would do that with a `handle_info/2` callback. In our case, we're going to + use a simple `receive`. `Mint.HTTP` provides a way to tell if a message comes + from the socket wrapped by our connection or not: the `stream/2` function. If + the message we pass to it is not destined for our connection, this function returns + `:unknown`. Otherwise, it returns an updated connection and one or more responses. + + receive do + message -> + case Mint.HTTP.stream(conn, message) do + :unknown -> handle_normal_message(message) + {:ok, conn, responses} -> handle_responses(conn, responses) + end + end + + `responses` is a list of possible responses. The most common responses are: + + * `{:status, request_ref, status_code}` for the status code + * `{:headers, request_ref, headers}` for the response headers + * `{:data, request_ref, binary}` for pieces of the response body + * `{:done, request_ref}` for the end of the response + + As you can see, all responses have the unique request reference as the second + element of the tuple, so that we know which request the response belongs to. + See `t:Mint.Types.response/0` for the full list of responses returned by `Mint.HTTP.stream/2`. + + ## Architecture + + A processless architecture like the one here requires a few modifications to how + we use this HTTP client. Usually, you will want to create this data structure + in a process that acts as *connection manager*. Sometimes, you might want to + have a single process responsible for multiple connections, either to just one + host or multiple hosts. For more discussion on architectures based off of this + HTTP client, see the [*Architecture*](architecture.html) page in the docs. + + ## SSL certificates + + When using SSL, you can pass in your own CA certificate store or use one provided by Mint. Mint + doesn't ship with the certificate store itself, but it has an optional dependency on + [CAStore](https://github.com/elixir-mint/castore), which provides an up-to-date certificate store. If + you don't want to use your own certificate store, just add `:castore` to your dependencies. + + Starting [from OTP + 25](https://www.erlang.org/blog/my-otp-25-highlights/#ca-certificates-can-be-fetched-from-the-os-standard-place), + you can also load certificates from a file + ([`:public_key.cacerts_load/1`](https://www.erlang.org/doc/man/public_key.html#cacerts_load-1)) + or from the OS + ([`:public_key.cacerts_load/0`](https://www.erlang.org/doc/man/public_key.html#cacerts_load-0)). + You can then use the certificates with + [`:public_key.cacerts_get/0`](https://www.erlang.org/doc/man/public_key.html#cacerts_get-0): + + Mint.connect(:https, host, port, transport_opts: [cacerts: :public_key.cacerts_get()]) + + ## Mode + + By default Mint operates in **active mode** meaning that the process that started the + connection receives socket messages. Mint also supports **passive mode**, where no messages + are sent to the process and the process needs to fetch data out of the socket manually. + The mode can be controlled at connection time through the `:mode` option in `connect/4` + or changed dynamically through `set_mode/2`. Passive mode is generally only recommended + for special use cases. + """ + + import Mint.Core.Util + + alias Mint.{Types, TunnelProxy, UnsafeProxy} + alias Mint.Core.Transport + + @behaviour Mint.Core.Conn + + @opaque t() :: Mint.HTTP1.t() | Mint.HTTP2.t() + + @doc """ + Macro to check that a given received `message` is intended for the given connection `conn`. + + This guard is useful in `receive` loops or in callbacks that handle generic messages (such as a + `c:GenServer.handle_info/2` callback) so that you don't have to hand the `message` to + `Mint.HTTP.stream/2` and check for the `:unknown_message` return value. + + This macro can be used in guards. + + **Note**: this macro is only available if you compile Mint with Elixir 1.10.0 or greater (and + OTP 21+, which is required by Elixir 1.10.0 and on). + + ## Examples + + require Mint.HTTP + + {:ok, conn, request_ref} = Mint.HTTP.request(conn, "POST", "/", headers, "") + + receive do + message when Mint.HTTP.is_connection_message(conn, message) -> + Mint.HTTP.stream(conn, message) + + other -> + # This message is related to something else or to some other connection + end + + """ + define_is_connection_message_guard() + + @doc """ + Creates a new connection to a given server. + + Creates a new connection struct and establishes the connection to the given server, + identified by the given `host` and `port` combination. Both HTTP and HTTPS are supported + by passing respectively `:http` and `:https` as the `scheme`. + + The connection struct wraps a socket, which is created once the connection + is established inside this function. If HTTP is used, then the created socket is a TCP + socket and the `:gen_tcp` module is used to create that socket. If HTTPS is used, then + the created socket is an SSL socket and the `:ssl` module is used to create that socket. + The socket is created in active mode (with `active: :once`), which is why it is important + to know the type of the socket: messages from the socket will be delivered directly to the + process that creates the connection and tagged appropriately by the socket module (see the + `:gen_tcp` and `:ssl` modules). See `stream/2` for more information on the messages and + how to process them and on the socket mode. + + ## Options + + * `:hostname` - (string) explicitly provide the hostname used for the `Host` header, + hostname verification, SNI, and so on. **Required when `address` is not a string.** + + * `:transport_opts` - (keyword) options to be given to the transport being used. + These options will be merged with some default options that cannot be overridden. + For more details, refer to the "Transport options" section below. + + * `:mode` - (`:active` or `:passive`) whether to set the socket to active or + passive mode. See the "Mode" section in the module documentation and `set_mode/2`. + + * `:protocols` - (list of atoms) a list of protocols to try when connecting to the + server. The possible values in the list are `:http1` for HTTP/1 and HTTP/1.1 and + `:http2` for HTTP/2. If only one protocol is present in the list, then the connection + will be forced to use that protocol. If both `:http1` and `:http2` are present in the + list, then Mint will negotiate the protocol. See the section "Protocol negotiation" + below for more information. Defaults to `[:http1, :http2]`. + + * `:proxy_headers` - a list of headers (`t:Mint.Types.headers/0`) to pass when using + a proxy. They will be used for the `CONNECT` request in tunnel proxies or merged + with every request for forward proxies. + + The following options are HTTP/1-specific and will force the connection + to be an HTTP/1 connection. + + * `:proxy` - a `{scheme, address, port, opts}` tuple that identifies a proxy to + connect to. See the "Proxying" section below for more information. + + The following options are HTTP/2-specific and will only be used on HTTP/2 connections. + + * `:client_settings` - (keyword) a list of client HTTP/2 settings to send to the + server. See `Mint.HTTP2.put_settings/2` for more information. This is only used + in HTTP/2 connections. + + ## Protocol negotiation + + If both `:http1` and `:http2` are present in the list passed in the `:protocols` option, + the protocol negotiation happens in the following way: + + * If the scheme used to connect to the server is `:http`, then HTTP/1 or HTTP/1.1 is used. + + * If the scheme is `:https`, then ALPN negotiation is used to determine the right + protocol. This means that the server will decide whether to use HTTP/1 or + HTTP/2. If the server doesn't support protocol negotiation, we will fall back to + HTTP/1. If the server negotiates a protocol that we don't know how to handle, + `{:error, {:bad_alpn_protocol, protocol}}` is returned. + + ## Proxying + + You can set up proxying through the `:proxy` option, which is a tuple + `{scheme, address, port, opts}` that identifies the proxy to connect to. + Once a proxied connection is returned, the proxy is transparent to you and you + can use the connection like a normal HTTP/1 connection. + + If the `scheme` is `:http`, we will connect to the host in the most compatible + way, supporting older proxy servers. Data will be sent in clear text. + + If the connection scheme is `:https`, we will connect to the host with a tunnel + through the proxy. Using `:https` for both the proxy and the connection scheme + is not supported, it is recommended to use `:https` for the end host connection + instead of the proxy. + + ## Transport options + + The options specified in `:transport_opts` are passed to the module that + implements the socket interface: `:gen_tcp` when the scheme is `:http`, and + `:ssl` when the scheme is `:https`. Please refer to the documentation for those + modules, as well as for `:inet.setopts/2`, for a detailed description of all + available options. + + The behaviour of some options is modified by Mint, as described below. + + A special case is the `:timeout` option, which is passed to the transport + module's `connect` function to limit the amount of time to wait for the + network connection to be established. + + Common options for `:http` and `:https`: + + * `:active` - controlled by the `:mode` option. Cannot be overridden. + + * `:mode` - set to `:binary`. Cannot be overridden. + + * `:packet` - set to `:raw`. Cannot be overridden. + + * `:timeout` - connect timeout in milliseconds. Defaults to `30_000` (30 + seconds), and may be overridden by the caller. Set to `:infinity` to + disable the connect timeout. + + Options for `:https` only: + + * `:alpn_advertised_protocols` - managed by Mint. Cannot be overridden. + + * `:cacertfile` - if `:verify` is set to `:verify_peer` (the default) and + no CA trust store is specified using the `:cacertfile` or `:cacerts` + option, Mint will attempt to use the trust store from the + [CAStore](https://github.com/elixir-mint/castore) package or raise an + exception if this package is not available. Due to caching the + `:cacertfile` option is more efficient than `:cacerts`. + + * `:ciphers` - defaults to the lists returned by + `:ssl.filter_cipher_suites(:ssl.cipher_suites(:all, version), [])` + where `version` is each value in the `:versions` setting. This list is + then filtered according to the blocklist in + [RFC7540 appendix A](https://tools.ietf.org/html/rfc7540#appendix-A); + May be overridden by the caller. See the "Supporting older cipher suites" + section below for some examples. + + * `:depth` - defaults to `4`. May be overridden by the caller. + + * `:partial_chain` - unless a custom `:partial_chain` function is specified, + Mint will enable its own partial chain handler, which accepts server + certificate chains containing a certificate that was issued by a + CA certificate in the CA trust store, even if that certificate is not + last in the chain. This improves interoperability with some servers + (for example, with a cross-signed intermediate CA or some misconfigured servers), + but is a less strict interpretation of the TLS specification than the + Erlang/OTP default behaviour. + + * `:reuse_sessions` - defaults to `true`. May be overridden by the caller. If + `:"tlsv1.3"` is the only TLS version specified, `:reuse_sessions` will be + removed from the options. + + * `:secure_renegotiate` - defaults to `true`. May be overridden by the + caller. If `:"tlsv1.3"` is the only TLS version specified, `:secure_renegotiate` + will be removed from the options. + + * `:server_name_indication` - defaults to specified destination hostname. + May be overridden by the caller. + + * `:verify` - defaults to `:verify_peer`. May be overridden by the caller. + + * `:verify_fun` - unless a custom `:verify_fun` is specified, or `:verify` + is set to `:verify_none`, Mint will enable hostname verification with + support for wildcards in the server's 'SubjectAltName' extension, similar + to the behaviour implemented in + `:public_key.pkix_verify_hostname_match_fun(:https)` in recent Erlang/OTP + releases. This improves compatibility with recently issued wildcard + certificates also on older Erlang/OTP releases. + + * `:versions` - defaults to `[:"tlsv1.2"]` (TLS v1.2 only). May be + overridden by the caller. + + ### Supporting older cipher suites + + By default only a small list of modern cipher suites is enabled, in compliance + with the HTTP/2 specification. Some servers, in particular HTTP/1 servers, may + not support any of these cipher suites, resulting in TLS handshake failures or + closed connections. + + To select the default cipher suites of Erlang/OTP (including for example + AES-CBC), use the following `:transport_opts`: + + # Erlang/OTP 20.3 or later: + transport_opts: [ciphers: :ssl.cipher_suites(:default, :"tlsv1.2")] + # Older versions: + transport_opts: [ciphers: :ssl.cipher_suites()] + + Recent Erlang/OTP releases do not enable RSA key exchange by default, due to + known weaknesses. If necessary, you can build a cipher list with RSA exchange + and use it in `:transport_opts`: + + ciphers = + :ssl.cipher_suites(:all, :"tlsv1.2") + |> :ssl.filter_cipher_suites( + key_exchange: &(&1 == :rsa), + cipher: &(&1 in [:aes_256_gcm, :aes_128_gcm, :aes_256_cbc, :aes_128_cbc]) + ) + |> :ssl.append_cipher_suites(:ssl.cipher_suites(:default, :"tlsv1.2")) + + ## Examples + + {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80) + + Using a proxy: + + proxy = {:http, "myproxy.example.com", 80, []} + {:ok, conn} = Mint.HTTP.connect(:https, "httpbin.org", 443, proxy: proxy) + + Forcing the connection to be an HTTP/2 connection: + + {:ok, conn} = Mint.HTTP.connect(:https, "httpbin.org", 443, protocols: [:http2]) + + Enable all default cipher suites of Erlang/OTP (release 20.3 or later): + + opts = [transport_opts: [ciphers: :ssl.cipher_suites(:default, :"tlsv1.2")]] + {:ok, conn} = Mint.HTTP.connect(:https, "httpbin.org", 443, opts) + + """ + @spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) :: + {:ok, t()} | {:error, Types.error()} + def connect(scheme, address, port, opts \\ []) do + case Keyword.fetch(opts, :proxy) do + {:ok, {proxy_scheme, proxy_address, proxy_port, proxy_opts}} -> + case scheme_to_transport(scheme) do + Transport.TCP -> + proxy = {proxy_scheme, proxy_address, proxy_port} + host = {scheme, address, port} + opts = Keyword.merge(opts, proxy_opts) + UnsafeProxy.connect(proxy, host, opts) + + Transport.SSL -> + proxy = {proxy_scheme, proxy_address, proxy_port, proxy_opts} + host = {scheme, address, port, opts} + TunnelProxy.connect(proxy, host) + end + + :error -> + Mint.Negotiate.connect(scheme, address, port, opts) + end + end + + @doc false + @spec upgrade( + module(), + Mint.Types.socket(), + Types.scheme(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def upgrade(old_transport, transport_state, scheme, hostname, port, opts), + do: Mint.Negotiate.upgrade(old_transport, transport_state, scheme, hostname, port, opts) + + @doc """ + Returns the protocol used by the current connection. + + ## Examples + + iex> Mint.HTTP.protocol(%Mint.HTTP1{}) + :http1 + + iex> Mint.HTTP.protocol(%Mint.HTTP2{}) + :http2 + """ + if Version.compare(System.version(), "1.7.0") in [:eq, :gt] do + @doc since: "1.4.0" + end + + @spec protocol(t()) :: :http1 | :http2 + def protocol(%Mint.HTTP1{}), do: :http1 + def protocol(%Mint.HTTP2{}), do: :http2 + def protocol(%Mint.UnsafeProxy{state: internal_conn}), do: protocol(internal_conn) + + @doc false + @impl true + @spec initiate( + module(), + Types.socket(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def initiate(transport, transport_state, hostname, port, opts), + do: Mint.Negotiate.initiate(transport, transport_state, hostname, port, opts) + + @doc """ + Closes the given connection. + + This function closes the socket wrapped by the given connection. Once the socket + is closed, the connection goes into the "closed" state and `open?/1` returns `false`. + You can throw away a closed connection. + + Closing a connection does not guarantee that data that is in flight gets delivered + to the server. + + Always returns `{:ok, conn}` where `conn` is the updated connection. + + ## Examples + + {:ok, conn} = Mint.HTTP.close(conn) + + """ + @impl true + @spec close(t()) :: {:ok, t()} + def close(conn), do: conn_module(conn).close(conn) + + @doc """ + Checks whether the connection is open. + + This function returns `true` if the connection is open, `false` otherwise. It should + be used to check that a connection is open before sending requests or performing + operations that involve talking to the server. + + The `type` argument can be used to tell whether the connection is closed only for reading, + only for writing, or for both. In HTTP/1, a closed connection is always closed for + both reading and writing. In HTTP/2, the connection can be closed only for writing but + not for reading, meaning that you cannot send any more data to the server but you can + still receive data from the server. See the "Closed connection" section in the module + documentation of `Mint.HTTP2`. + + If a connection is not open for reading and writing, it has become useless and you should + get rid of it. If you still need a connection to the server, start a new connection + with `connect/4`. + + ## Examples + + {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80) + Mint.HTTP.open?(conn) + #=> true + + """ + @impl true + @spec open?(t(), :read | :write | :read_write) :: boolean() + def open?(conn, type \\ :read_write), do: conn_module(conn).open?(conn, type) + + @doc """ + Sends a request to the connected server. + + This function sends a new request to the server that `conn` is connected to. + `method` is a string representing the method for the request, such as `"GET"` + or `"POST"`. `path` is the path on the host to send the request to. `headers` + is a list of request headers in the form `{header_name, header_value}` with + `header_name` and `header_value` being strings. `body` can have one of three + values: + + * `nil` - no body is sent with the request. + + * iodata - the body to send for the request. + + * `:stream` - when the value of the body is `:stream` the request + body can be streamed on the connection. See `stream_request_body/3`. + In HTTP/1, you can't open a request if the body of another request is + streaming. + + If the request is sent correctly, this function returns `{:ok, conn, request_ref}`. + `conn` is an updated connection that should be stored over the old connection. + `request_ref` is a unique reference that can be used to match on responses for this + request that are returned by `stream/2`. See `stream/2` for more information. + + If there's an error with sending the request, `{:error, conn, reason}` is returned. + `reason` is the cause of the error. `conn` is an updated connection. It's important + to store the returned connection over the old connection in case of errors too, because + the state of the connection might change when there are errors as well. An error when + sending a request **does not** necessarily mean that the connection is closed. Use + `open?/1` to verify that the connection is open. + + Requests can be pipelined so the full response does not have to received + before the next request can be sent. It is up to users to verify that the + server supports pipelining and that the request is safe to pipeline. + + In HTTP/1, you can't open a request if the body of another request is streaming. + See `Mint.HTTP1.request/5` for more information. + + For a quick discussion on HTTP/2 streams and requests, see the `Mint.HTTP2` module and + `Mint.HTTP2.request/5`. + + ## The `content-length` header + + If you don't set the `content-length` header and you send a body with the request (that + is, not `nil` and not `:stream`), then Mint will add a default `content-length` header + to your request. If you're using HTTP/2 and streaming the request, you may provide the + `content-length` header yourself. If you're using HTTP/1, Mint will do chunked + transfer-encoding when a content-length is not provided (see `Mint.HTTP1.request/5`). + + ## Examples + + Mint.HTTP.request(conn, "GET", "/", _headers = [], _body = nil) + Mint.HTTP.request(conn, "POST", "/path", [{"content-type", "application/json"}], "{}") + + """ + @impl true + @spec request( + t(), + method :: String.t(), + path :: String.t(), + Types.headers(), + body :: iodata() | nil | :stream + ) :: + {:ok, t(), Types.request_ref()} + | {:error, t(), Types.error()} + + def request(conn, method, path, headers, body), + do: conn_module(conn).request(conn, method, path, headers, body) + + @doc """ + Streams a chunk of the request body on the connection or signals the end of the body. + + If a request is opened (through `request/5`) with the body as `:stream`, then the + body can be streamed through this function. The function takes a `conn`, a + `request_ref` returned by `request/5` to identify the request to stream the body for, + and a chunk of body to stream. The value of chunk can be: + + * iodata - a chunk of iodata is transmitted to the server as part of the body + of the request. If the chunk is empty, in HTTP/1 it's a no-op, while in HTTP/2 + a `DATA` frame will be sent. + + * `:eof` - signals the end of the streaming of the request body for the given + request. Usually the server won't send any reply until this is sent. + + * `{:eof, trailing_headers}` - sends **trailing headers** and signals the end + of the streaming of the request body for the given request. This behaves the + same way as `:eof` but first sends the trailing headers. See the "Trailing headers" + section below. + + This function always returns an updated connection to be stored over the old connection. + + For information about transfer encoding and content length in HTTP/1, see + `Mint.HTTP1.stream_request_body/3`. + + ## Trailing headers + + HTTP trailing headers can be sent after the body of a request. The behaviour is slightly + different for HTTP/1 and HTTP/2. + + In HTTP/1, trailing headers are only supported if the transfer encoding is set to + `chunked`. See `Mint.HTTP1.stream_request_body/3` for more information on chunked + transfer encoding. + + In HTTP/2, trailing headers behave like normal headers. You don't need to care + about the transfer encoding. + + ### The `trailer` header + + As specified in [section 4.4 of RFC 7230](https://tools.ietf.org/html/rfc7230#section-4.4), + in HTTP/1 you need to specify which headers you're going to send as trailing + headers using the `trailer` header. The `trailer` header applies to both HTTP/1 + and HTTP/2. See the examples below for more information. + + ### The `te` header + + As specified in [section 4.3 of RFC 7230](https://tools.ietf.org/html/rfc7230#section-4.3), + the `te` (or `TE`) header is used to specify which transfer-encodings the client + is willing to accept (besides `chunked`). Mint supports decoding of trailing headers, + but if you want to notify the server that you are accepting trailing headers, + use the `trailers` value in the `te` header. For example: + + Mint.HTTP.request(conn, "GET", "/", [{"te", "trailers"}], "some body") + + Note that the `te` header can also be used to communicate which encodings you + support to the server. + + ## Examples + + Let's see an example of streaming an empty JSON object (`{}`) by streaming one curly + brace at a time. + + headers = [{"content-type", "application/json"}, {"content-length", "2"}] + {:ok, conn, request_ref} = Mint.HTTP.request(conn, "POST", "/", headers, :stream) + {:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, "{") + {:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, "}") + {:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, :eof) + + Here's an example of sending trailing headers: + + headers = [{"content-type", "application/json"}, {"trailer", "my-trailer, x-expires"}] + {:ok, conn, request_ref} = Mint.HTTP.request(conn, "POST", "/", headers, :stream) + + {:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, "{}") + + trailing_headers = [{"my-trailer", "xxx"}, {"x-expires", "10 days"}] + {:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, {:eof, trailing_headers}) + + """ + @impl true + @spec stream_request_body( + t(), + Types.request_ref(), + iodata() | :eof | {:eof, trailing_headers :: Types.headers()} + ) :: + {:ok, t()} | {:error, t(), Types.error()} + def stream_request_body(conn, ref, body), + do: conn_module(conn).stream_request_body(conn, ref, body) + + @doc """ + Streams the next batch of responses from the given message. + + This function processes a "message" which can be any term, but should be + a message received by the process that owns the connection. **Processing** + a message means that this function will parse it and check if it's a message + that is directed to this connection, that is, a TCP/SSL message received on the + connection's socket. If it is, then this function will parse the message, + turn it into a list of responses, and possibly take action given the responses. + As an example of an action that this function could perform, if the server sends + a ping request this function will transparently take care of pinging the server back. + + If there's no error, this function returns `{:ok, conn, responses}` where `conn` is + the updated connection and `responses` is a list of responses. See the "Responses" + section below. If there's an error, `{:error, conn, reason, responses}` is returned, + where `conn` is the updated connection, `reason` is the error reason, and `responses` + is a list of responses that were correctly parsed before the error. + + If the given `message` is not from the connection's socket, + this function returns `:unknown`. + + ## Socket mode + + Mint sets the socket in `active: :once` mode. This means that a single socket + message at a time is delivered to the process that owns the connection. After + a message is delivered, then no other messages are delivered (we say the socket + goes in *passive* mode). When `stream/2` is called to process the message that + was received, Mint sets the socket back to `active: :once`. This is good to know + in order to understand how the socket is handled by Mint, but in normal usage + it just means that you will process one message at a time with `stream/2` and not + pay too much attention to the socket mode. + + Mint also supports passive mode to avoid receiving messages. See the "Mode" section + in the module documentation. + + ## Responses + + Each possible response returned by this function is a tuple with two or more elements. + The first element is always an atom that identifies the kind of response. The second + element is a unique reference `t:Mint.Types.request_ref/0` that identifies the request + that the response belongs to. This is the term returned by `request/5`. After these + two elements, there can be response-specific terms as well, documented below. + + These are the possible responses that can be returned. + + * `{:status, request_ref, status_code}` - returned when the server replied + with a response status code. The status code is a non-negative integer. + You can have zero or more `1xx` `:status` and `:headers` responses for a + single request, but they all precede a single non-`1xx` `:status` response. + + * `{:headers, request_ref, headers}` - returned when the server replied + with a list of headers. Headers are in the form `{header_name, header_value}` + with `header_name` and `header_value` being strings. A single `:headers` response + will come after the `:status` response. A single `:headers` response may come + after all the `:data` responses if **trailing headers** are present. + + * `{:data, request_ref, binary}` - returned when the server replied with + a chunk of response body (as a binary). The request shouldn't be considered done + when a piece of body is received because multiple chunks could be received. The + request is done when the `:done` response is returned. + + * `{:done, request_ref}` - returned when the server signaled the request + as done. When this is received, the response body and headers can be considered + complete and it can be assumed that no more responses will be received for this + request. This means that for example, you can stop holding on to the request ref + for this request. + + * `{:error, request_ref, reason}` - returned when there is an error that + only affects the request and not the whole connection. For example, if the + server sends bad data on a given request, that request will be closed and an error + for that request will be returned among the responses, but the connection will + remain alive and well. + + * `{:pong, request_ref}` - returned when a server replies to a ping + request sent by the client. This response type is HTTP/2-specific + and will never be returned by an HTTP/1 connection. See `Mint.HTTP2.ping/2` + for more information. + + * `{:push_promise, request_ref, promised_request_ref, headers}` - returned when + the server sends a server push to the client. This response type is HTTP/2 specific + and will never be returned by an HTTP/1 connection. See `Mint.HTTP2` for more + information on server pushes. + + ## Examples + + Let's assume we have a function called `receive_next_and_stream/1` that takes + a connection and then receives the next message, calls `stream/2` with that message + as an argument, and then returns the result of `stream/2`: + + defp receive_next_and_stream(conn) do + receive do + message -> Mint.HTTP.stream(conn, message) + end + end + + Now, we can see an example of a workflow involving `stream/2`. + + {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", _headers = []) + + {:ok, conn, responses} = receive_next_and_stream(conn) + responses + #=> [{:status, ^request_ref, 200}] + + {:ok, conn, responses} = receive_next_and_stream(conn) + responses + #=> [{:headers, ^request_ref, [{"Content-Type", "application/json"}]}, + #=> {:data, ^request_ref, "{"}] + + {:ok, conn, responses} = receive_next_and_stream(conn) + responses + #=> [{:data, ^request_ref, "}"}, {:done, ^request_ref}] + + """ + @impl true + @spec stream(t(), term()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + | :unknown + def stream(conn, message), do: conn_module(conn).stream(conn, message) + + @doc """ + Returns the number of open requests. + + Open requests are requests that have not yet received a `:done` response. + This function returns the number of open requests for both HTTP/1 and HTTP/2, + but for HTTP/2 only client-initiated requests are considered as open requests. + See `Mint.HTTP2.open_request_count/1` for more information. + + ## Examples + + {:ok, conn, _ref} = Mint.HTTP.request(conn, "GET", "/", []) + Mint.HTTP.open_request_count(conn) + #=> 1 + + """ + @impl true + @spec open_request_count(t()) :: non_neg_integer() + def open_request_count(conn), do: conn_module(conn).open_request_count(conn) + + @doc """ + Receives data from the socket in a blocking way. + + By default Mint operates in active mode, meaning that messages are delivered + to the process that started the connection. However, Mint also supports passive + mode (see the "Mode" section in the module documentation). + + In passive mode, you'll need to manually get bytes out of the socket. You can + do that with this function. + + `byte_count` is the number of bytes you want out of the socket. If `byte_count` + is `0`, all available bytes will be returned. + + `timeout` is the maximum time to wait before returning an error. + + This function will raise an error if the socket is in active mode. + + ## Examples + + {:ok, conn, responses} = Mint.HTTP.recv(conn, 0, 5000) + + """ + @impl true + @spec recv(t(), non_neg_integer(), timeout()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + def recv(conn, byte_count, timeout), do: conn_module(conn).recv(conn, byte_count, timeout) + + @doc """ + Changes the mode of the underlying socket. + + To use the connection in *active mode*, where the process that started the + connection receives socket messages, set the mode to `:active` (see also `stream/2`). + To use the connection in *passive mode*, where you need to manually receive data + from the socket, set the mode to `:passive` (see also `recv/3`). + + The mode can also be controlled at connection time by the `:mode` option passed + to `connect/4`. + + Note that if you're switching from active to passive mode, you still might have + socket messages in the process mailbox that you need to consume before doing + any other operation on the connection. + + See the "Mode" section in the module documentation for more information on modes. + + ## Examples + + {:ok, conn} = Mint.HTTP.set_mode(conn, :passive) + + """ + @impl true + @spec set_mode(t(), :active | :passive) :: {:ok, t()} | {:error, Types.error()} + def set_mode(conn, mode), do: conn_module(conn).set_mode(conn, mode) + + @doc """ + Changes the *controlling process* of the given connection to `new_pid`. + + The **controlling process** is a concept that comes from the Erlang TCP and + SSL implementations. The controlling process of a connection is the process + that started the connection and that receives the messages for that connection. + You can change the controlling process of a connection through this function. + + This function also takes care of "transferring" all the connection messages + that are in the mailbox of the current controlling process to the new + controlling process. + + Remember that the connection is a data structure, so if you + change the controlling process it doesn't mean you "transferred" the + connection data structure itself to the other process, which you have + to do manually (for example by sending the connection data structure to the + new controlling process). If you do that, be careful of race conditions + and be sure to retrieve the connection in the new controlling process + before accepting connection messages in the new controlling process. + In fact, this function is guaranteed to return the connection unchanged, + so you are free to ignore the connection entry returned in `{:ok, conn}`. + + ## Examples + + send(new_pid, {:conn, conn}) + {:ok, conn} = Mint.HTTP.controlling_process(conn, new_pid) + + # In the "new_pid" process + receive do + {:conn, conn} -> + # Will receive connection messages. + end + + """ + @impl true + @spec controlling_process(t(), pid()) :: {:ok, t()} | {:error, Types.error()} + def controlling_process(conn, new_pid), do: conn_module(conn).controlling_process(conn, new_pid) + + @doc """ + Assigns a new private key and value in the connection. + + This storage is meant to be used to associate metadata with the connection and + it can be useful when handling multiple connections. + + The given `key` must be an atom, while the given `value` can be an arbitrary + term. The return value of this function is an updated connection. + + See also `get_private/3` and `delete_private/2`. + + ## Examples + + Let's see an example of putting a value and then getting it: + + conn = Mint.HTTP.put_private(conn, :client_name, "Mint") + Mint.HTTP.get_private(conn, :client_name) + #=> "Mint" + + """ + @impl true + @spec put_private(t(), atom(), term()) :: t() + def put_private(conn, key, value), do: conn_module(conn).put_private(conn, key, value) + + @doc """ + Gets a private value from the connection. + + Retrieves a private value previously set with `put_private/3` from the connection. + `key` is the key under which the value to retrieve is stored. `default` is a default + value returned in case there's no value under the given key. + + See also `put_private/3` and `delete_private/2`. + + ## Examples + + conn = Mint.HTTP.put_private(conn, :client_name, "Mint") + + Mint.HTTP.get_private(conn, :client_name) + #=> "Mint" + + Mint.HTTP.get_private(conn, :non_existent) + #=> nil + + """ + @impl true + @spec get_private(t(), atom(), term()) :: term() + def get_private(conn, key, default \\ nil), + do: conn_module(conn).get_private(conn, key, default) + + @doc """ + Deletes a value in the private store. + + Deletes the private value stored under `key` in the connection. Returns the + updated connection. + + See also `put_private/3` and `get_private/3`. + + ## Examples + + conn = Mint.HTTP.put_private(conn, :client_name, "Mint") + + Mint.HTTP.get_private(conn, :client_name) + #=> "Mint" + + conn = Mint.HTTP.delete_private(conn, :client_name) + Mint.HTTP.get_private(conn, :client_name) + #=> nil + + """ + @impl true + @spec delete_private(t(), atom()) :: t() + def delete_private(conn, key), do: conn_module(conn).delete_private(conn, key) + + @doc """ + Gets the socket associated with the connection. + + Do not use the returned socket to change its internal state. Only read information from the socket. + For instance, use `:ssl.connection_information/2` to retrieve TLS-specific information from the + socket. + """ + @impl true + @spec get_socket(t()) :: Mint.Types.socket() + def get_socket(conn), do: conn_module(conn).get_socket(conn) + + @doc """ + Gets the proxy headers associated with the connection in the `CONNECT` method. + + When using tunnel proxy and HTTPs, the only way to exchange data with + the proxy is through headers in the `CONNECT` method. + """ + if Version.compare(System.version(), "1.7.0") in [:eq, :gt] do + @doc since: "1.4.0" + end + + @impl true + @spec get_proxy_headers(t()) :: Mint.Types.headers() + def get_proxy_headers(conn), do: conn_module(conn).get_proxy_headers(conn) + + ## Helpers + + defp conn_module(%UnsafeProxy{}), do: UnsafeProxy + defp conn_module(%Mint.HTTP1{}), do: Mint.HTTP1 + defp conn_module(%Mint.HTTP2{}), do: Mint.HTTP2 +end diff --git a/.deps/mint/lib/mint/http1.ex b/.deps/mint/lib/mint/http1.ex @@ -0,0 +1,1063 @@ +defmodule Mint.HTTP1 do + @moduledoc """ + Processless HTTP client with support for HTTP/1 and HTTP/1.1. + + This module provides a data structure that represents an HTTP/1 or HTTP/1.1 connection to + a given server. The connection is represented as an opaque struct `%Mint.HTTP1{}`. + The connection is a data structure and is not backed by a process, and all the + connection handling happens in the process that creates the struct. + + This module and data structure work exactly like the ones described in the `Mint` + module, with the exception that `Mint.HTTP1` specifically deals with HTTP/1 and HTTP/1.1 while + `Mint` deals seamlessly with HTTP/1, HTTP/1.1, and HTTP/2. For more information on + how to use the data structure and client architecture, see `Mint`. + """ + + import Mint.Core.Util + + alias Mint.Core.Util + alias Mint.HTTP1.{Parse, Request, Response} + alias Mint.{HTTPError, TransportError, Types} + + require Logger + + @behaviour Mint.Core.Conn + + @opaque t() :: %__MODULE__{} + + @user_agent "mint/" <> Mix.Project.config()[:version] + + @typedoc """ + An HTTP/1-specific error reason. + + The values can be: + + * `:closed` - when you try to make a request or stream a body chunk but the connection + is closed. + + * `:request_body_is_streaming` - when you call `request/5` to send a new + request but another request is already streaming. + + * `{:unexpected_data, data}` - when unexpected data is received from the server. + + * `:invalid_status_line` - when the HTTP/1 status line is invalid. + + * `{:invalid_request_target, target}` - when the request target is invalid. + + * `:invalid_header` - when headers can't be parsed correctly. + + * `{:invalid_header_name, name}` - when a header name is invalid. + + * `{:invalid_header_value, name, value}` - when a header value is invalid. `name` + is the name of the header and `value` is the invalid value. + + * `:invalid_chunk_size` - when the chunk size is invalid. + + * `:missing_crlf_after_chunk` - when the CRLF after a chunk is missing. + + * `:invalid_trailer_header` - when trailer headers can't be parsed. + + * `:more_than_one_content_length_header` - when more than one `content-length` + headers are in the response. + + * `:transfer_encoding_and_content_length` - when both the `content-length` as well + as the `transfer-encoding` headers are in the response. + + * `{:invalid_content_length_header, value}` - when the value of the `content-length` + header is invalid, that is, is not an non-negative integer. + + * `:empty_token_list` - when a header that is supposed to contain a list of tokens + (such as the `connection` header) doesn't contain any. + + * `{:invalid_token_list, string}` - when a header that is supposed to contain a list + of tokens (such as the `connection` header) contains a malformed list of tokens. + + * `:trailing_headers_but_not_chunked_encoding` - when you try to send trailing + headers through `stream_request_body/3` but the transfer encoding of the request + was not `chunked`. + + """ + @type error_reason() :: term() + + defstruct [ + :host, + :port, + :request, + :streaming_request, + :socket, + :transport, + :mode, + :scheme_as_string, + requests: :queue.new(), + state: :closed, + buffer: "", + proxy_headers: [], + private: %{} + ] + + @doc """ + Same as `Mint.HTTP.connect/4`, but forces an HTTP/1 or HTTP/1.1 connection. + + This function doesn't support proxying. + """ + @spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) :: + {:ok, t()} | {:error, Types.error()} + def connect(scheme, address, port, opts \\ []) do + # TODO: Also ALPN negotiate HTTP1? + + hostname = Mint.Core.Util.hostname(opts, address) + transport = scheme_to_transport(scheme) + + transport_opts = + Keyword.get(opts, :transport_opts, []) + |> Keyword.put(:hostname, hostname) + + with {:ok, socket} <- transport.connect(address, port, transport_opts) do + initiate(scheme, socket, hostname, port, opts) + end + end + + @doc false + @spec upgrade( + Types.scheme(), + Types.socket(), + Types.scheme(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def upgrade(old_scheme, socket, new_scheme, hostname, port, opts) do + # TODO: Also ALPN negotiate HTTP1? + + transport = scheme_to_transport(new_scheme) + + transport_opts = + Keyword.get(opts, :transport_opts, []) + |> Keyword.put(:hostname, hostname) + + with {:ok, socket} <- transport.upgrade(socket, old_scheme, hostname, port, transport_opts) do + initiate(new_scheme, socket, hostname, port, opts) + end + end + + @doc false + @impl true + @spec initiate( + Types.scheme(), + Types.socket(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def initiate(scheme, socket, hostname, port, opts) do + transport = scheme_to_transport(scheme) + mode = Keyword.get(opts, :mode, :active) + + unless mode in [:active, :passive] do + raise ArgumentError, + "the :mode option must be either :active or :passive, got: #{inspect(mode)}" + end + + with :ok <- inet_opts(transport, socket), + :ok <- if(mode == :active, do: transport.setopts(socket, active: :once), else: :ok) do + conn = %__MODULE__{ + transport: transport, + socket: socket, + mode: mode, + host: hostname, + port: port, + scheme_as_string: Atom.to_string(scheme), + state: :open + } + + {:ok, conn} + else + {:error, reason} -> + :ok = transport.close(socket) + {:error, reason} + end + end + + @doc """ + See `Mint.HTTP.close/1`. + """ + @impl true + @spec close(t()) :: {:ok, t()} + def close(conn) + + def close(%__MODULE__{state: :open} = conn) do + conn = internal_close(conn) + {:ok, conn} + end + + def close(%__MODULE__{state: :closed} = conn) do + {:ok, conn} + end + + @doc """ + See `Mint.HTTP.open?/1`. + """ + @impl true + @spec open?(t(), :read | :write | :read_write) :: boolean() + def open?(conn, type \\ :read_write) + + def open?(%__MODULE__{state: state}, type) when type in [:read, :write, :read_write] do + state == :open + end + + @doc """ + See `Mint.HTTP.request/5`. + + In HTTP/1 and HTTP/1.1, you can't open a new request if you're streaming the body of + another request. If you try, an error will be returned. + """ + @impl true + @spec request( + t(), + method :: String.t(), + path :: String.t(), + Types.headers(), + body :: iodata() | nil | :stream + ) :: + {:ok, t(), Types.request_ref()} + | {:error, t(), Types.error()} + def request(conn, method, path, headers, body) + + def request(%__MODULE__{state: :closed} = conn, _method, _path, _headers, _body) do + {:error, conn, wrap_error(:closed)} + end + + def request(%__MODULE__{streaming_request: %{}} = conn, _method, _path, _headers, _body) do + {:error, conn, wrap_error(:request_body_is_streaming)} + end + + def request(%__MODULE__{} = conn, method, path, headers, body) do + %__MODULE__{transport: transport, socket: socket} = conn + + headers = + headers + |> lower_header_keys() + |> add_default_headers(conn) + + with {:ok, headers, encoding} <- add_content_length_or_transfer_encoding(headers, body), + {:ok, iodata} <- Request.encode(method, path, headers, body), + :ok <- transport.send(socket, iodata) do + request_ref = make_ref() + request = new_request(request_ref, method, body, encoding) + + case request.state do + {:stream_request, _} -> + conn = %__MODULE__{conn | streaming_request: request} + {:ok, conn, request_ref} + + _ -> + conn = enqueue_request(conn, request) + {:ok, conn, request_ref} + end + else + {:error, %TransportError{reason: :closed} = error} -> + {:error, %{conn | state: :closed}, error} + + {:error, %error_module{} = error} when error_module in [HTTPError, TransportError] -> + {:error, conn, error} + + {:error, reason} -> + {:error, conn, wrap_error(reason)} + end + end + + @doc """ + See `Mint.HTTP.stream_request_body/3`. + + In HTTP/1, sending an empty chunk is a no-op. + + ## Transfer encoding and content length + + When streaming the request body, Mint cannot send a precalculated `content-length` + request header because it doesn't know the body that you'll stream. However, Mint + will transparently handle the presence of a `content-length` header using this logic: + + * if you specifically set a `content-length` header, then transfer encoding and + making sure the content length is correct for what you'll stream is up to you. + + * if you specifically set the transfer encoding (`transfer-encoding` header) + to `chunked`, then it's up to you to + [properly encode chunks](https://en.wikipedia.org/wiki/Chunked_transfer_encoding). + + * if you don't set the transfer encoding to `chunked` and don't provide a + `content-length` header, Mint will do implicit `chunked` transfer encoding + (setting the `transfer-encoding` header appropriately) and will take care + of properly encoding the chunks. + + """ + @impl true + @spec stream_request_body( + t(), + Types.request_ref(), + iodata() | :eof | {:eof, trailing_headers :: Types.headers()} + ) :: + {:ok, t()} | {:error, t(), Types.error()} + def stream_request_body( + %__MODULE__{streaming_request: %{state: {:stream_request, :identity}, ref: ref}} = conn, + ref, + :eof + ) do + request = %{conn.streaming_request | state: :status} + conn = enqueue_request(%__MODULE__{conn | streaming_request: nil}, request) + {:ok, conn} + end + + def stream_request_body( + %__MODULE__{streaming_request: %{state: {:stream_request, :identity}, ref: ref}} = conn, + ref, + {:eof, _trailing_headers} + ) do + {:error, conn, wrap_error(:trailing_headers_but_not_chunked_encoding)} + end + + def stream_request_body( + %__MODULE__{streaming_request: %{state: {:stream_request, :identity}, ref: ref}} = conn, + ref, + body + ) do + case conn.transport.send(conn.socket, body) do + :ok -> + {:ok, conn} + + {:error, %TransportError{reason: :closed} = error} -> + {:error, %{conn | state: :closed}, error} + + {:error, error} -> + {:error, conn, error} + end + end + + def stream_request_body( + %__MODULE__{streaming_request: %{state: {:stream_request, :chunked}, ref: ref}} = conn, + ref, + chunk + ) do + with {:ok, chunk} <- validate_chunk(chunk), + :ok <- conn.transport.send(conn.socket, Request.encode_chunk(chunk)) do + case chunk do + :eof -> + request = %{conn.streaming_request | state: :status} + conn = enqueue_request(%__MODULE__{conn | streaming_request: nil}, request) + {:ok, conn} + + {:eof, _trailing_headers} -> + request = %{conn.streaming_request | state: :status} + conn = enqueue_request(%__MODULE__{conn | streaming_request: nil}, request) + {:ok, conn} + + _other -> + {:ok, conn} + end + else + :empty_chunk -> + {:ok, conn} + + {:error, %TransportError{reason: :closed} = error} -> + {:error, %{conn | state: :closed}, error} + + {:error, error} -> + {:error, conn, error} + end + end + + defp validate_chunk({:eof, trailing_headers}) do + headers = lower_header_keys(trailing_headers) + + if unallowed_header = find_unallowed_trailing_header(headers) do + {:error, wrap_error({:unallowed_trailing_header, unallowed_header})} + else + {:ok, {:eof, headers}} + end + end + + defp validate_chunk(:eof) do + {:ok, :eof} + end + + defp validate_chunk(chunk) do + if IO.iodata_length(chunk) == 0 do + :empty_chunk + else + {:ok, chunk} + end + end + + @doc """ + See `Mint.HTTP.stream/2`. + """ + @impl true + @spec stream(t(), term()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + | :unknown + def stream(conn, message) + + def stream(%__MODULE__{transport: transport, socket: socket} = conn, {tag, socket, data}) + when tag in [:tcp, :ssl] do + case handle_data(conn, data) do + {:ok, %{mode: mode, state: state} = conn, responses} + when mode == :active and state != :closed -> + case transport.setopts(socket, active: :once) do + :ok -> {:ok, conn, responses} + {:error, reason} -> {:error, put_in(conn.state, :closed), reason, responses} + end + + other -> + other + end + end + + def stream(%__MODULE__{socket: socket} = conn, {tag, socket}) + when tag in [:tcp_closed, :ssl_closed] do + handle_close(conn) + end + + def stream(%__MODULE__{socket: socket} = conn, {tag, socket, reason}) + when tag in [:tcp_error, :ssl_error] do + handle_error(conn, conn.transport.wrap_error(reason)) + end + + def stream(%__MODULE__{}, _message) do + :unknown + end + + defp handle_data(%__MODULE__{request: nil} = conn, data) do + conn = internal_close(conn) + {:error, conn, wrap_error({:unexpected_data, data}), []} + end + + defp handle_data(%__MODULE__{request: request} = conn, data) do + data = maybe_concat(conn.buffer, data) + + case decode(request.state, conn, data, []) do + {:ok, conn, responses} -> + {:ok, conn, Enum.reverse(responses)} + + {:error, conn, reason, responses} -> + conn = put_in(conn.state, :closed) + {:error, conn, reason, responses} + end + end + + defp handle_close(%__MODULE__{request: request} = conn) do + conn = put_in(conn.state, :closed) + conn = request_done(conn) + + if request && request.body == :until_closed do + conn = put_in(conn.state, :closed) + {:ok, conn, [{:done, request.ref}]} + else + {:error, conn, conn.transport.wrap_error(:closed), []} + end + end + + defp handle_error(conn, error) do + conn = put_in(conn.state, :closed) + {:error, conn, error, []} + end + + @doc """ + See `Mint.HTTP.recv/3`. + """ + @impl true + @spec recv(t(), non_neg_integer(), timeout()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + def recv(conn, byte_count, timeout) + + def recv(%__MODULE__{mode: :passive} = conn, byte_count, timeout) do + case conn.transport.recv(conn.socket, byte_count, timeout) do + {:ok, data} -> handle_data(conn, data) + {:error, %Mint.TransportError{reason: :closed}} -> handle_close(conn) + {:error, error} -> handle_error(conn, error) + end + end + + def recv(_conn, _byte_count, _timeout) do + raise ArgumentError, + "can't use recv/3 to synchronously receive data when the mode is :active. " <> + "Use Mint.HTTP.set_mode/2 to set the connection to passive mode" + end + + @doc """ + See `Mint.HTTP.set_mode/2`. + """ + @impl true + @spec set_mode(t(), :active | :passive) :: {:ok, t()} | {:error, Types.error()} + def set_mode(%__MODULE__{} = conn, mode) when mode in [:active, :passive] do + active = + case mode do + :active -> :once + :passive -> false + end + + with :ok <- conn.transport.setopts(conn.socket, active: active) do + {:ok, put_in(conn.mode, mode)} + end + end + + @doc """ + See `Mint.HTTP.controlling_process/2`. + """ + @impl true + @spec controlling_process(t(), pid()) :: {:ok, t()} | {:error, Types.error()} + def controlling_process(%__MODULE__{} = conn, new_pid) when is_pid(new_pid) do + with :ok <- conn.transport.controlling_process(conn.socket, new_pid) do + {:ok, conn} + end + end + + @doc """ + See `Mint.HTTP.open_request_count/1`. + + In HTTP/1, the number of open requests is the number of pipelined requests. + """ + @impl true + @spec open_request_count(t()) :: non_neg_integer() + def open_request_count(%__MODULE__{} = conn) do + case conn do + %{request: nil, streaming_request: nil} -> 0 + %{request: nil} -> 1 + %{streaming_request: nil} -> 1 + :queue.len(conn.requests) + _ -> 2 + :queue.len(conn.requests) + end + end + + @doc """ + See `Mint.HTTP.put_private/3`. + """ + @impl true + @spec put_private(t(), atom(), term()) :: t() + def put_private(%__MODULE__{private: private} = conn, key, value) when is_atom(key) do + %{conn | private: Map.put(private, key, value)} + end + + @doc """ + See `Mint.HTTP.get_private/3`. + """ + @impl true + @spec get_private(t(), atom(), term()) :: term() + def get_private(%__MODULE__{private: private} = _conn, key, default \\ nil) when is_atom(key) do + Map.get(private, key, default) + end + + @doc """ + See `Mint.HTTP.delete_private/2`. + """ + @impl true + @spec delete_private(t(), atom()) :: t() + def delete_private(%__MODULE__{private: private} = conn, key) when is_atom(key) do + %{conn | private: Map.delete(private, key)} + end + + @doc """ + See `Mint.HTTP.get_socket/1`. + """ + @impl true + @spec get_socket(t()) :: Mint.Types.socket() + def get_socket(%__MODULE__{socket: socket} = _conn) do + socket + end + + @doc """ + See `Mint.HTTP.get_proxy_headers/1`. + """ + if Version.compare(System.version(), "1.7.0") in [:eq, :gt] do + @doc since: "1.4.0" + end + + @impl true + @spec get_proxy_headers(t()) :: Mint.Types.headers() + def get_proxy_headers(%__MODULE__{proxy_headers: proxy_headers}), do: proxy_headers + + ## Helpers + + defp decode(:status, %{request: request} = conn, data, responses) do + case Response.decode_status_line(data) do + {:ok, {version, status, _reason}, rest} -> + request = %{request | version: version, status: status, state: :headers} + conn = %{conn | request: request} + responses = [{:status, request.ref, status} | responses] + decode(:headers, conn, rest, responses) + + :more -> + conn = put_in(conn.buffer, data) + {:ok, conn, responses} + + :error -> + {:error, conn, wrap_error(:invalid_status_line), responses} + end + end + + defp decode(:headers, %{request: request} = conn, data, responses) do + decode_headers(conn, request, data, responses, request.headers_buffer) + end + + defp decode(:body, conn, data, responses) do + case message_body(conn.request) do + {:ok, body} -> + conn = put_in(conn.request.body, body) + decode_body(body, conn, data, conn.request.ref, responses) + + {:error, reason} -> + {:error, conn, wrap_error(reason), responses} + end + end + + defp decode_headers(conn, request, data, responses, headers) do + case Response.decode_header(data) do + {:ok, {name, value}, rest} -> + headers = [{name, value} | headers] + + case store_header(request, name, value) do + {:ok, request} -> decode_headers(conn, request, rest, responses, headers) + {:error, reason} -> {:error, conn, wrap_error(reason), responses} + end + + {:ok, :eof, rest} -> + responses = [{:headers, request.ref, Enum.reverse(headers)} | responses] + request = %{request | state: :body, headers_buffer: []} + conn = %{conn | buffer: "", request: request} + decode(:body, conn, rest, responses) + + :more -> + request = %{request | headers_buffer: headers} + conn = %{conn | buffer: data, request: request} + {:ok, conn, responses} + + :error -> + {:error, conn, wrap_error(:invalid_header), responses} + end + end + + defp decode_body(:none, conn, data, request_ref, responses) do + conn = put_in(conn.buffer, data) + conn = request_done(conn) + responses = [{:done, request_ref} | responses] + {:ok, conn, responses} + end + + defp decode_body(:single, conn, data, request_ref, responses) do + {conn, responses} = add_body(conn, data, responses) + conn = request_done(conn) + responses = [{:done, request_ref} | responses] + {:ok, conn, responses} + end + + defp decode_body(:until_closed, conn, data, _request_ref, responses) do + {conn, responses} = add_body(conn, data, responses) + {:ok, conn, responses} + end + + defp decode_body({:content_length, length}, conn, data, request_ref, responses) do + cond do + length > byte_size(data) -> + conn = put_in(conn.request.body, {:content_length, length - byte_size(data)}) + {conn, responses} = add_body(conn, data, responses) + {:ok, conn, responses} + + length <= byte_size(data) -> + <<body::binary-size(length), rest::binary>> = data + {conn, responses} = add_body(conn, body, responses) + conn = request_done(conn) + responses = [{:done, request_ref} | responses] + next_request(conn, rest, responses) + end + end + + defp decode_body({:chunked, nil}, conn, "", _request_ref, responses) do + conn = put_in(conn.buffer, "") + conn = put_in(conn.request.body, {:chunked, nil}) + {:ok, conn, responses} + end + + defp decode_body({:chunked, nil}, conn, data, request_ref, responses) do + case Integer.parse(data, 16) do + {_size, ""} -> + conn = put_in(conn.buffer, data) + conn = put_in(conn.request.body, {:chunked, nil}) + {:ok, conn, responses} + + {0, rest} -> + # Manually collapse the body buffer since we're done with the body + {conn, responses} = collapse_body_buffer(conn, responses) + decode_body({:chunked, :metadata, :trailer}, conn, rest, request_ref, responses) + + {size, rest} when size > 0 -> + decode_body({:chunked, :metadata, size}, conn, rest, request_ref, responses) + + _other -> + {:error, conn, wrap_error(:invalid_chunk_size), responses} + end + end + + defp decode_body({:chunked, :metadata, size}, conn, data, request_ref, responses) do + case Parse.ignore_until_crlf(data) do + {:ok, rest} -> + decode_body({:chunked, size}, conn, rest, request_ref, responses) + + :more -> + conn = put_in(conn.buffer, data) + conn = put_in(conn.request.body, {:chunked, :metadata, size}) + {:ok, conn, responses} + end + end + + defp decode_body({:chunked, :trailer}, conn, data, _request_ref, responses) do + decode_trailer_headers(conn, data, responses, conn.request.headers_buffer) + end + + defp decode_body({:chunked, :crlf}, conn, data, request_ref, responses) do + case data do + <<"\r\n", rest::binary>> -> + conn = put_in(conn.request.body, {:chunked, nil}) + decode_body({:chunked, nil}, conn, rest, request_ref, responses) + + _other when byte_size(data) < 2 -> + conn = put_in(conn.buffer, data) + {:ok, conn, responses} + + _other -> + {:error, conn, wrap_error(:missing_crlf_after_chunk), responses} + end + end + + defp decode_body({:chunked, length}, conn, data, request_ref, responses) do + cond do + length > byte_size(data) -> + conn = put_in(conn.buffer, "") + conn = put_in(conn.request.body, {:chunked, length - byte_size(data)}) + conn = add_body_to_buffer(conn, data) + {:ok, conn, responses} + + length <= byte_size(data) -> + <<body::binary-size(length), rest::binary>> = data + {conn, responses} = add_body(conn, body, responses) + conn = put_in(conn.request.body, {:chunked, :crlf}) + decode_body({:chunked, :crlf}, conn, rest, request_ref, responses) + end + end + + defp decode_trailer_headers(conn, data, responses, headers) do + case Response.decode_header(data) do + {:ok, {name, value}, rest} -> + headers = [{name, value} | headers] + decode_trailer_headers(conn, rest, responses, headers) + + {:ok, :eof, rest} -> + headers = Util.remove_unallowed_trailing_headers(headers) + + responses = [ + {:done, conn.request.ref} + | add_trailing_headers(headers, conn.request.ref, responses) + ] + + conn = request_done(conn) + next_request(conn, rest, responses) + + :more -> + request = %{conn.request | body: {:chunked, :trailer}, headers_buffer: headers} + conn = %{conn | buffer: data, request: request} + {:ok, conn, responses} + + :error -> + {:error, conn, wrap_error(:invalid_trailer_header), responses} + end + end + + defp next_request(%{request: nil} = conn, data, responses) do + # TODO: Figure out if we should keep buffering even though there are no + # requests in flight + {:ok, %{conn | buffer: data}, responses} + end + + defp next_request(conn, data, responses) do + decode(:status, %{conn | state: :status}, data, responses) + end + + defp add_trailing_headers([], _request_ref, responses), do: responses + + defp add_trailing_headers(headers, request_ref, responses), + do: [{:headers, request_ref, Enum.reverse(headers)} | responses] + + defp add_body(conn, data, responses) do + conn = add_body_to_buffer(conn, data) + collapse_body_buffer(conn, responses) + end + + defp add_body_to_buffer(conn, data) do + update_in(conn.request.data_buffer, &[&1 | data]) + end + + defp collapse_body_buffer(conn, responses) do + case IO.iodata_to_binary(conn.request.data_buffer) do + "" -> + {conn, responses} + + data -> + conn = put_in(conn.request.data_buffer, []) + {conn, [{:data, conn.request.ref, data} | responses]} + end + end + + defp store_header(%{content_length: nil} = request, "content-length", value) do + with {:ok, content_length} <- Parse.content_length_header(value), + do: {:ok, %{request | content_length: content_length}} + end + + defp store_header(%{connection: connection} = request, "connection", value) do + with {:ok, connection_header} <- Parse.connection_header(value), + do: {:ok, %{request | connection: connection ++ connection_header}} + end + + defp store_header(%{transfer_encoding: transfer_encoding} = request, "transfer-encoding", value) do + with {:ok, transfer_encoding_header} <- Parse.transfer_encoding_header(value), + do: {:ok, %{request | transfer_encoding: transfer_encoding ++ transfer_encoding_header}} + end + + defp store_header(_request, "content-length", _value) do + {:error, :more_than_one_content_length_header} + end + + defp store_header(request, _name, _value) do + {:ok, request} + end + + defp request_done(%{request: request} = conn) do + conn = pop_request(conn) + + cond do + !request -> conn + "close" in request.connection -> internal_close(conn) + request.version >= {1, 1} -> conn + "keep-alive" in request.connection -> conn + true -> internal_close(conn) + end + end + + defp pop_request(conn) do + case :queue.out(conn.requests) do + {{:value, request}, requests} -> + %{conn | request: request, requests: requests} + + {:empty, requests} -> + %{conn | request: nil, requests: requests} + end + end + + defp enqueue_request(%{request: nil} = conn, request) do + %__MODULE__{conn | request: request} + end + + defp enqueue_request(conn, request) do + requests = :queue.in(request, conn.requests) + %__MODULE__{conn | requests: requests} + end + + defp internal_close(conn) do + if conn.buffer != "" do + _ = Logger.debug(["Connection closed with data left in the buffer: ", inspect(conn.buffer)]) + end + + _ = conn.transport.close(conn.socket) + %{conn | state: :closed} + end + + # RFC7230 3.3.3: + # > If a message is received with both a Transfer-Encoding and a + # > Content-Length header field, the Transfer-Encoding overrides the + # > Content-Length. Such a message might indicate an attempt to + # > perform request smuggling (Section 9.5) or response splitting + # > (Section 9.4) and ought to be handled as an error. A sender MUST + # > remove the received Content-Length field prior to forwarding such + # > a message downstream. + defp message_body(%{body: nil, method: method, status: status} = request) do + cond do + status == 101 -> + {:ok, :single} + + method == "HEAD" or status in 100..199 or status in [204, 304] -> + {:ok, :none} + + # method == "CONNECT" and status in 200..299 -> nil + + request.transfer_encoding != [] && request.content_length -> + {:error, :transfer_encoding_and_content_length} + + "chunked" == List.first(request.transfer_encoding) -> + {:ok, {:chunked, nil}} + + request.content_length -> + {:ok, {:content_length, request.content_length}} + + true -> + {:ok, :until_closed} + end + end + + defp message_body(%{body: body}) do + {:ok, body} + end + + defp new_request(ref, method, body, encoding) do + state = + if body == :stream do + {:stream_request, encoding} + else + :status + end + + %{ + ref: ref, + state: state, + method: method, + version: nil, + status: nil, + headers_buffer: [], + data_buffer: [], + content_length: nil, + connection: [], + transfer_encoding: [], + body: nil + } + end + + defp lower_header_keys(headers) do + for {name, value} <- headers, do: {Util.downcase_ascii(name), value} + end + + defp add_default_headers(headers, conn) do + headers + |> Util.put_new_header("user-agent", @user_agent) + |> Util.put_new_header("host", default_host_header(conn)) + end + + # If the port is the default for the scheme, don't add it to the host header + defp default_host_header(%__MODULE__{scheme_as_string: scheme, host: host, port: port}) do + if URI.default_port(scheme) == port do + host + else + "#{host}:#{port}" + end + end + + defp add_content_length_or_transfer_encoding(headers, :stream) do + cond do + List.keymember?(headers, "content-length", 0) -> + {:ok, headers, :identity} + + found = List.keyfind(headers, "transfer-encoding", 0) -> + {"transfer-encoding", value} = found + + with {:ok, tokens} <- Parse.transfer_encoding_header(value) do + if "chunked" in tokens or "identity" in tokens do + {:ok, headers, :identity} + else + new_transfer_encoding = {"transfer-encoding", value <> ",chunked"} + headers = List.keyreplace(headers, "transfer-encoding", 0, new_transfer_encoding) + {:ok, headers, :chunked} + end + end + + # If no content-length or transfer-encoding are present, assume + # chunked transfer-encoding and handle the encoding ourselves. + true -> + headers = Util.put_new_header(headers, "transfer-encoding", "chunked") + {:ok, headers, :chunked} + end + end + + defp add_content_length_or_transfer_encoding(headers, nil) do + {:ok, headers, :identity} + end + + defp add_content_length_or_transfer_encoding(headers, body) do + length_fun = fn -> body |> IO.iodata_length() |> Integer.to_string() end + {:ok, Util.put_new_header_lazy(headers, "content-length", length_fun), :identity} + end + + defp wrap_error(reason) do + %HTTPError{reason: reason, module: __MODULE__} + end + + @doc false + def format_error(reason) + + def format_error(:closed) do + "the connection is closed" + end + + def format_error(:request_body_is_streaming) do + "a request body is currently streaming, so no new requests can be issued" + end + + def format_error({:unexpected_data, data}) do + "received unexpected data: " <> inspect(data) + end + + def format_error(:invalid_status_line) do + "invalid status line" + end + + def format_error(:invalid_header) do + "invalid header" + end + + def format_error({:invalid_request_target, target}) do + "invalid request target: #{inspect(target)}" + end + + def format_error({:invalid_header_name, name}) do + "invalid header name: #{inspect(name)}" + end + + def format_error({:invalid_header_value, name, value}) do + "invalid value for header (only printable ASCII characters are allowed) " <> + "#{inspect(name)}: #{inspect(value)}" + end + + def format_error(:invalid_chunk_size) do + "invalid chunk size" + end + + def format_error(:missing_crlf_after_chunk) do + "missing CRLF after chunk" + end + + def format_error(:invalid_trailer_header) do + "invalid trailer header" + end + + def format_error(:more_than_one_content_length_header) do + "the response contains two or more Content-Length headers" + end + + def format_error(:transfer_encoding_and_content_length) do + "the response contained both a Transfer-Encoding header as well as a Content-Length header" + end + + def format_error({:invalid_content_length_header, value}) do + "invalid Content-Length header: #{inspect(value)}" + end + + def format_error(:empty_token_list) do + "header should contain a list of values, but it doesn't" + end + + def format_error({:invalid_token_list, string}) do + "header contains invalid tokens: #{inspect(string)}" + end + + def format_error(:trailing_headers_but_not_chunked_encoding) do + "trailing headers can only be sent when using chunked transfer-encoding" + end + + def format_error({:unallowed_trailing_header, {name, value}}) do + "header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailing header" + end +end diff --git a/.deps/mint/lib/mint/http1/parse.ex b/.deps/mint/lib/mint/http1/parse.ex @@ -0,0 +1,71 @@ +defmodule Mint.HTTP1.Parse do + @moduledoc false + + alias Mint.Core.Util + + defmacro is_digit(char), do: quote(do: unquote(char) in ?0..?9) + defmacro is_alpha(char), do: quote(do: unquote(char) in ?a..?z or unquote(char) in ?A..?Z) + defmacro is_whitespace(char), do: quote(do: unquote(char) in '\s\t') + defmacro is_comma(char), do: quote(do: unquote(char) == ?,) + defmacro is_vchar(char), do: quote(do: unquote(char) in 33..126) + + defmacro is_tchar(char) do + quote do + is_digit(unquote(char)) or is_alpha(unquote(char)) or unquote(char) in '!#$%&\'*+-.^_`|~' + end + end + + def ignore_until_crlf(<<>>), do: :more + def ignore_until_crlf(<<"\r\n", rest::binary>>), do: {:ok, rest} + def ignore_until_crlf(<<_char, rest::binary>>), do: ignore_until_crlf(rest) + + def content_length_header(string) do + case Integer.parse(String.trim_trailing(string)) do + {length, ""} when length >= 0 -> {:ok, length} + _other -> {:error, {:invalid_content_length_header, string}} + end + end + + def connection_header(string) do + split_into_downcase_tokens(string) + end + + def transfer_encoding_header(string) do + split_into_downcase_tokens(string) + end + + defp split_into_downcase_tokens(string) do + case token_list_downcase(string) do + {:ok, []} -> {:error, :empty_token_list} + {:ok, list} -> {:ok, list} + :error -> {:error, {:invalid_token_list, string}} + end + end + + # Made public for testing. + def token_list_downcase(string), do: token_list_downcase(string, []) + + defp token_list_downcase(<<>>, acc), do: {:ok, :lists.reverse(acc)} + + # Skip all whitespace and commas. + defp token_list_downcase(<<char, rest::binary>>, acc) + when is_whitespace(char) or is_comma(char), + do: token_list_downcase(rest, acc) + + defp token_list_downcase(rest, acc), do: token_downcase(rest, _token_acc = <<>>, acc) + + defp token_downcase(<<char, rest::binary>>, token_acc, acc) when is_tchar(char), + do: token_downcase(rest, <<token_acc::binary, Util.downcase_ascii_char(char)>>, acc) + + defp token_downcase(rest, token_acc, acc), do: token_list_sep_downcase(rest, [token_acc | acc]) + + defp token_list_sep_downcase(<<>>, acc), do: {:ok, :lists.reverse(acc)} + + defp token_list_sep_downcase(<<char, rest::binary>>, acc) when is_whitespace(char), + do: token_list_sep_downcase(rest, acc) + + defp token_list_sep_downcase(<<char, rest::binary>>, acc) when is_comma(char), + do: token_list_downcase(rest, acc) + + defp token_list_sep_downcase(_rest, _acc), do: :error +end diff --git a/.deps/mint/lib/mint/http1/request.ex b/.deps/mint/lib/mint/http1/request.ex @@ -0,0 +1,92 @@ +defmodule Mint.HTTP1.Request do + @moduledoc false + + import Mint.HTTP1.Parse + + def encode(method, target, headers, body) do + body = [ + encode_request_line(method, target), + encode_headers(headers), + "\r\n", + encode_body(body) + ] + + {:ok, body} + catch + {:mint, reason} -> {:error, reason} + end + + defp encode_request_line(method, target) do + validate_target!(target) + [method, ?\s, target, " HTTP/1.1\r\n"] + end + + defp encode_headers(headers) do + Enum.reduce(headers, "", fn {name, value}, acc -> + validate_header_name!(name) + validate_header_value!(name, value) + [acc, name, ": ", value, "\r\n"] + end) + end + + defp encode_body(nil), do: "" + defp encode_body(:stream), do: "" + defp encode_body(body), do: body + + def encode_chunk(:eof) do + "0\r\n\r\n" + end + + def encode_chunk({:eof, trailing_headers}) do + ["0\r\n", encode_headers(trailing_headers), "\r\n"] + end + + def encode_chunk(chunk) do + length = IO.iodata_length(chunk) + [Integer.to_string(length, 16), "\r\n", chunk, "\r\n"] + end + + # Percent-encoding is not case sensitive so we have to account for lowercase and uppercase. + @hex_characters '0123456789abcdefABCDEF' + + defp validate_target!(target), do: validate_target!(target, target) + + defp validate_target!(<<?%, char1, char2, rest::binary>>, original_target) + when char1 in @hex_characters and char2 in @hex_characters do + validate_target!(rest, original_target) + end + + defp validate_target!(<<char, rest::binary>>, original_target) do + if URI.char_unescaped?(char) do + validate_target!(rest, original_target) + else + throw({:mint, {:invalid_request_target, original_target}}) + end + end + + defp validate_target!(<<>>, _original_target) do + :ok + end + + defp validate_header_name!(name) do + _ = + for <<char <- name>> do + unless is_tchar(char) do + throw({:mint, {:invalid_header_name, name}}) + end + end + + :ok + end + + defp validate_header_value!(name, value) do + _ = + for <<char <- value>> do + unless is_vchar(char) or char in '\s\t' do + throw({:mint, {:invalid_header_value, name, value}}) + end + end + + :ok + end +end diff --git a/.deps/mint/lib/mint/http1/response.ex b/.deps/mint/lib/mint/http1/response.ex @@ -0,0 +1,43 @@ +defmodule Mint.HTTP1.Response do + @moduledoc false + + alias Mint.Core.Util + + def decode_status_line(binary) do + case :erlang.decode_packet(:http_bin, binary, []) do + {:ok, {:http_response, version, status, reason}, rest} -> + {:ok, {version, status, reason}, rest} + + {:ok, _other, _rest} -> + :error + + {:more, _length} -> + :more + + {:error, _reason} -> + :error + end + end + + def decode_header(binary) do + case :erlang.decode_packet(:httph_bin, binary, []) do + {:ok, {:http_header, _unused, name, _reserved, value}, rest} -> + {:ok, {header_name(name), value}, rest} + + {:ok, :http_eoh, rest} -> + {:ok, :eof, rest} + + {:ok, _other, _rest} -> + :error + + {:more, _length} -> + :more + + {:error, _reason} -> + :error + end + end + + defp header_name(atom) when is_atom(atom), do: atom |> Atom.to_string() |> Util.downcase_ascii() + defp header_name(binary) when is_binary(binary), do: Util.downcase_ascii(binary) +end diff --git a/.deps/mint/lib/mint/http2.ex b/.deps/mint/lib/mint/http2.ex @@ -0,0 +1,2197 @@ +defmodule Mint.HTTP2 do + @moduledoc """ + Processless HTTP client with support for HTTP/2. + + This module provides a data structure that represents an HTTP/2 connection to + a given server. The connection is represented as an opaque struct `%Mint.HTTP2{}`. + The connection is a data structure and is not backed by a process, and all the + connection handling happens in the process that creates the struct. + + This module and data structure work exactly like the ones described in the `Mint.HTTP` + module, with the exception that `Mint.HTTP2` specifically deals with HTTP/2 while + `Mint.HTTP` deals seamlessly with HTTP/1.1 and HTTP/2. For more information on + how to use the data structure and client architecture, see `Mint.HTTP`. + + ## HTTP/2 streams and requests + + HTTP/2 introduces the concept of **streams**. A stream is an isolated conversation + between the client and the server. Each stream is unique and identified by a unique + **stream ID**, which means that there's no order when data comes on different streams + since they can be identified uniquely. A stream closely corresponds to a request, so + in this documentation and client we will mostly refer to streams as "requests". + We mentioned data on streams can come in arbitrary order, and streams are requests, + so the practical effect of this is that performing request A and then request B + does not mean that the response to request A will come before the response to request B. + This is why we identify each request with a unique reference returned by `request/5`. + See `request/5` for more information. + + ## Closed connection + + In HTTP/2, the connection can either be open, closed, or only closed for writing. + When a connection is closed for writing, the client cannot send requests or stream + body chunks, but it can still read data that the server might be sending. When the + connection gets closed on the writing side, a `:server_closed_connection` error is + returned. `{:error, request_ref, error}` is returned for requests that haven't been + processed by the server, with the reason of `error` being `:unprocessed`. + These requests are safe to retry. + + ## HTTP/2 settings + + HTTP/2 supports settings negotiation between servers and clients. The server advertises + its settings to the client and the client advertises its settings to the server. A peer + (server or client) has to acknowledge the settings advertised by the other peer before + those settings come into action (that's why it's called a negotiation). + + A first settings negotiation happens right when the connection starts. + Servers and clients can renegotiate settings at any time during the life of the + connection. + + Mint users don't need to care about settings acknowledgements directly since they're + handled transparently by `stream/2`. + + To retrieve the server settings, you can use `get_server_setting/2`. Doing so is often + useful to be able to tune your requests based on the server settings. + + To communicate client settings to the server, use `put_settings/2` or pass them when + starting up a connection with `connect/4`. Note that the server needs to acknowledge + the settings sent through `put_setting/2` before those settings come into effect. The + server ack is processed transparently by `stream/2`, but this means that if you change + a setting through `put_settings/2` and try to retrieve the value of that setting right + after with `get_client_setting/2`, you'll likely get the old value of that setting. Once + the server acknowledges the new settings, the updated value will be returned by + `get_client_setting/2`. + + ## Server push + + HTTP/2 supports [server push](https://en.wikipedia.org/wiki/HTTP/2_Server_Push), which + is a way for a server to send a response to a client without the client needing to make + the corresponding request. The server sends a `:push_promise` response to a normal request: + this creates a new request reference. Then, the server sends normal responses for the newly + created request reference. + + Let's see an example. We will ask the server for `"/index.html"` and the server will + send us a push promise for `"/style.css"`. + + {:ok, conn} = Mint.HTTP2.connect(:https, "example.com", 443) + {:ok, conn, request_ref} = Mint.HTTP2.request(conn, "GET", "/index.html", _headers = [], _body = "") + + next_message = + receive do + msg -> msg + end + + {:ok, conn, responses} = Mint.HTTP2.stream(conn, next_message) + + [ + {:push_promise, ^request_ref, promised_request_ref, promised_headers}, + {:status, ^request_ref, 200}, + {:headers, ^request_ref, []}, + {:data, ^request_ref, "<html>..."}, + {:done, ^request_ref} + ] = responses + + promised_headers + #=> [{":method", "GET"}, {":path", "/style.css"}] + + As you can see in the example above, when the server sends a push promise then a + `:push_promise` response is returned as a response to a request. The `:push_promise` + response contains a `promised_request_ref` and some `promised_headers`. The + `promised_request_ref` is the new request ref that pushed responses will be tagged with. + `promised_headers` are headers that tell the client *what request* the promised response + will respond to. The idea is that the server tells the client a request the client will + want to make and then preemptively sends a response for that request. Promised headers + will always include `:method`, `:path`, and `:authority`. + + next_message = + receive do + msg -> msg + end + + {:ok, conn, responses} = Mint.HTTP2.stream(conn, next_message) + + [ + {:status, ^promised_request_ref, 200}, + {:headers, ^promised_request_ref, []}, + {:data, ^promised_request_ref, "body { ... }"}, + {:done, ^promised_request_ref} + ] + + The response to a promised request is like a response to any normal request. + + ### Disabling server pushes + + HTTP/2 exposes a boolean setting for enabling or disabling server pushes with `:enable_push`. + You can pass this option when connecting or in `put_settings/2`. By default server push + is enabled. + """ + + import Mint.Core.Util + import Mint.HTTP2.Frame, except: [encode: 1, decode_next: 1] + + alias Mint.{HTTPError, TransportError} + alias Mint.Types + alias Mint.Core.Util + alias Mint.HTTP2.Frame + + require Logger + require Integer + + @behaviour Mint.Core.Conn + + ## Constants + + @connection_preface "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + @transport_opts [alpn_advertised_protocols: ["h2"]] + + @default_window_size 65_535 + @max_window_size 2_147_483_647 + + @default_max_frame_size 16_384 + @valid_max_frame_size_range @default_max_frame_size..16_777_215 + + @user_agent "mint/" <> Mix.Project.config()[:version] + + # HTTP/2 connection struct. + defstruct [ + # Transport things. + :transport, + :socket, + :mode, + + # Host things. + :hostname, + :port, + :scheme, + + # Connection state (open, closed, and so on). + :state, + + # Fields of the connection. + buffer: "", + window_size: @default_window_size, + encode_table: HPAX.new(4096), + decode_table: HPAX.new(4096), + + # Queue for sent PING frames. + ping_queue: :queue.new(), + + # Queue for sent SETTINGS frames. + client_settings_queue: :queue.new(), + + # Stream-set-related things. + next_stream_id: 3, + streams: %{}, + open_client_stream_count: 0, + open_server_stream_count: 0, + ref_to_stream_id: %{}, + + # Settings that the server communicates to the client. + server_settings: %{ + enable_push: true, + max_concurrent_streams: 100, + initial_window_size: @default_window_size, + max_frame_size: @default_max_frame_size, + max_header_list_size: :infinity, + # Only supported by the server: https://www.rfc-editor.org/rfc/rfc8441.html#section-3 + enable_connect_protocol: false + }, + + # Settings that the client communicates to the server. + client_settings: %{ + max_concurrent_streams: 100, + max_frame_size: @default_max_frame_size, + enable_push: true + }, + + # Headers being processed (when headers are split into multiple frames with CONTINUATIONS, all + # the continuation frames must come one right after the other). + headers_being_processed: nil, + + # Stores the headers returned by the proxy in the `CONNECT` method + proxy_headers: [], + + # Private store. + private: %{} + ] + + ## Types + + @typedoc """ + HTTP/2 setting with its value. + + This type represents both server settings as well as client settings. To retrieve + server settings use `get_server_setting/2` and to retrieve client settings use + `get_client_setting/2`. To send client settings to the server, see `put_settings/2`. + + The supported settings are the following: + + * `:header_table_size` - (integer) corresponds to `SETTINGS_HEADER_TABLE_SIZE`. + + * `:enable_push` - (boolean) corresponds to `SETTINGS_ENABLE_PUSH`. Sets whether + push promises are supported. If you don't want to support push promises, + use `put_settings/2` to tell the server that your client doesn't want push promises. + + * `:max_concurrent_streams` - (integer) corresponds to `SETTINGS_MAX_CONCURRENT_STREAMS`. + Tells what is the maximum number of streams that the peer sending this (client or server) + supports. As mentioned in the module documentation, HTTP/2 streams are equivalent to + requests, so knowing the maximum number of streams that the server supports can be useful + to know how many concurrent requests can be open at any time. Use `get_server_setting/2` + to find out how many concurrent streams the server supports. + + * `:initial_window_size` - (integer smaller than `#{inspect(@max_window_size)}`) + corresponds to `SETTINGS_INITIAL_WINDOW_SIZE`. Tells what is the value of + the initial HTTP/2 window size for the peer that sends this setting. + + * `:max_frame_size` - (integer in the range `#{inspect(@valid_max_frame_size_range)}`) + corresponds to `SETTINGS_MAX_FRAME_SIZE`. Tells what is the maximum size of an HTTP/2 + frame for the peer that sends this setting. + + * `:max_header_list_size` - (integer) corresponds to `SETTINGS_MAX_HEADER_LIST_SIZE`. + + * `:enable_connect_protocol` - (boolean) corresponds to `SETTINGS_ENABLE_CONNECT_PROTOCOL`. + Sets whether the client may invoke the extended connect protocol which is used to + bootstrap WebSocket connections. + + """ + @type setting() :: + {:enable_push, boolean()} + | {:max_concurrent_streams, pos_integer()} + | {:initial_window_size, 1..2_147_483_647} + | {:max_frame_size, 16_384..16_777_215} + | {:max_header_list_size, :infinity | pos_integer()} + | {:enable_connect_protocol, boolean()} + + @typedoc """ + HTTP/2 settings. + + See `t:setting/0`. + """ + @type settings() :: [setting()] + + @typedoc """ + An HTTP/2-specific error reason. + + The values can be: + + * `:closed` - when you try to make a request or stream a body chunk but the connection + is closed. + + * `:closed_for_writing` - when you try to make a request or stream a body chunk but + the connection is closed for writing. This means you cannot issue any more requests. + See the "Closed connection" section in the module documentation for more information. + + * `:too_many_concurrent_requests` - when the maximum number of concurrent requests + allowed by the server is reached. To find out what this limit is, use `get_setting/2` + with the `:max_concurrent_streams` setting name. + + * `{:max_header_list_size_exceeded, size, max_size}` - when the maximum size of + the header list is reached. `size` is the actual value of the header list size, + `max_size` is the maximum value allowed. See `get_setting/2` to retrieve the + value of the max size. + + * `{:exceeds_window_size, what, window_size}` - when the data you're trying to send + exceeds the window size of the connection (if `what` is `:connection`) or of a request + (if `what` is `:request`). `window_size` is the allowed window size. See + `get_window_size/2`. + + * `{:stream_not_found, stream_id}` - when the given request is not found. + + * `:unknown_request_to_stream` - when you're trying to stream data on an unknown + request. + + * `:request_is_not_streaming` - when you try to send data (with `stream_request_body/3`) + on a request that is not open for streaming. + + * `:unprocessed` - when a request was closed because it was not processed by the server. + When this error is returned, it means that the server hasn't processed the request at all, + so it's safe to retry the given request on a different or new connection. + + * `{:server_closed_request, error_code}` - when the server closes the request. + `error_code` is the reason why the request was closed. + + * `{:server_closed_connection, reason, debug_data}` - when the server closes the connection + gracefully or because of an error. In HTTP/2, this corresponds to a `GOAWAY` frame. + `error` is the reason why the connection was closed. `debug_data` is additional debug data. + + * `{:frame_size_error, frame}` - when there's an error with the size of a frame. + `frame` is the frame type, such as `:settings` or `:window_update`. + + * `{:protocol_error, debug_data}` - when there's a protocol error. + `debug_data` is a string that explains the nature of the error. + + * `{:compression_error, debug_data}` - when there's a header compression error. + `debug_data` is a string that explains the nature of the error. + + * `{:flow_control_error, debug_data}` - when there's a flow control error. + `debug_data` is a string that explains the nature of the error. + + """ + @type error_reason() :: term() + + @opaque t() :: %Mint.HTTP2{} + + ## Public interface + + @doc """ + Same as `Mint.HTTP.connect/4`, but forces a HTTP/2 connection. + """ + @spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) :: + {:ok, t()} | {:error, Types.error()} + def connect(scheme, address, port, opts \\ []) do + hostname = Mint.Core.Util.hostname(opts, address) + + transport_opts = + opts + |> Keyword.get(:transport_opts, []) + |> Keyword.merge(@transport_opts) + |> Keyword.put(:hostname, hostname) + + case negotiate(address, port, scheme, transport_opts) do + {:ok, socket} -> + initiate(scheme, socket, hostname, port, opts) + + {:error, reason} -> + {:error, reason} + end + end + + @doc false + @spec upgrade( + Types.scheme(), + Mint.Types.socket(), + Types.scheme(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def upgrade(old_scheme, socket, new_scheme, hostname, port, opts) do + transport = scheme_to_transport(new_scheme) + + transport_opts = + opts + |> Keyword.get(:transport_opts, []) + |> Keyword.merge(@transport_opts) + + with {:ok, socket} <- transport.upgrade(socket, old_scheme, hostname, port, transport_opts) do + initiate(new_scheme, socket, hostname, port, opts) + end + end + + @doc """ + See `Mint.HTTP.close/1`. + """ + @impl true + @spec close(t()) :: {:ok, t()} + def close(conn) + + def close(%__MODULE__{state: :open} = conn) do + send_connection_error!(conn, :no_error, "connection peacefully closed by client") + catch + {:mint, conn, %HTTPError{reason: {:no_error, _}}} -> + {:ok, conn} + + # We could have an error sending the GOAWAY frame, but we want to ignore that since + # we're closing the connection anyways. + {:mint, conn, %TransportError{}} -> + conn = put_in(conn.state, :closed) + {:ok, conn} + end + + def close(%__MODULE__{state: {:goaway, _error_code, _debug_data}} = conn) do + _ = conn.transport.close(conn.socket) + {:ok, put_in(conn.state, :closed)} + end + + def close(%__MODULE__{state: :closed} = conn) do + {:ok, conn} + end + + @doc """ + See `Mint.HTTP.open?/1`. + """ + @impl true + @spec open?(t(), :read | :write | :read_write) :: boolean() + def open?(%Mint.HTTP2{state: state} = _conn, type \\ :read_write) + when type in [:read, :write, :read_write] do + case state do + :open -> true + {:goaway, _error_code, _debug_data} -> type == :read + :closed -> false + end + end + + @doc """ + See `Mint.HTTP.request/5`. + + In HTTP/2, opening a request means opening a new HTTP/2 stream (see the + module documentation). This means that a request could fail because the + maximum number of concurrent streams allowed by the server has been reached. + In that case, the error reason `:too_many_concurrent_requests` is returned. + If you want to avoid incurring in this error, you can retrieve the value of + the maximum number of concurrent streams supported by the server through + `get_server_setting/2` (passing in the `:max_concurrent_streams` setting name). + + ## Header list size + + In HTTP/2, the server can optionally specify a maximum header list size that + the client needs to respect when sending headers. The header list size is calculated + by summing the length (in bytes) of each header name plus value, plus 32 bytes for + each header. Note that pseudo-headers (like `:path` or `:method`) count towards + this size. If the size is exceeded, an error is returned. To check what the size + is, use `get_server_setting/2`. + + ## Request body size + + If the request body size will exceed the window size of the HTTP/2 stream created by the + request or the window size of the connection Mint will return a `:exceeds_window_size` + error. + + To ensure you do not exceed the window size it is recommended to stream the request + body by initially passing `:stream` as the body and sending the body in chunks using + `stream_request_body/3` and using `get_window_size/2` to get the window size of the + request and connection. + """ + @impl true + @spec request( + t(), + method :: String.t(), + path :: String.t(), + Types.headers(), + body :: iodata() | nil | :stream + ) :: + {:ok, t(), Types.request_ref()} + | {:error, t(), Types.error()} + def request(conn, method, path, headers, body) + + def request(%Mint.HTTP2{state: :closed} = conn, _method, _path, _headers, _body) do + {:error, conn, wrap_error(:closed)} + end + + def request( + %Mint.HTTP2{state: {:goaway, _error_code, _debug_data}} = conn, + _method, + _path, + _headers, + _body + ) do + {:error, conn, wrap_error(:closed_for_writing)} + end + + def request(%Mint.HTTP2{} = conn, method, path, headers, body) + when is_binary(method) and is_binary(path) and is_list(headers) do + headers = + headers + |> downcase_header_names() + |> add_pseudo_headers(conn, method, path) + |> add_default_headers(body) + |> sort_pseudo_headers_to_front() + + {conn, stream_id, ref} = open_stream(conn) + {conn, payload} = encode_request_payload(conn, stream_id, headers, body) + conn = send!(conn, payload) + {:ok, conn, ref} + catch + :throw, {:mint, _conn, reason} -> + # The stream is invalid and "_conn" may be tracking it, so we return the original connection instead. + {:error, conn, reason} + end + + @doc """ + See `Mint.HTTP.stream_request_body/3`. + """ + @impl true + @spec stream_request_body( + t(), + Types.request_ref(), + iodata() | :eof | {:eof, trailing_headers :: Types.headers()} + ) :: {:ok, t()} | {:error, t(), Types.error()} + def stream_request_body(conn, request_ref, chunk) + + def stream_request_body(%Mint.HTTP2{state: :closed} = conn, _request_ref, _chunk) do + {:error, conn, wrap_error(:closed)} + end + + def stream_request_body( + %Mint.HTTP2{state: {:goaway, _error_code, _debug_data}} = conn, + _request_ref, + _chunk + ) do + {:error, conn, wrap_error(:closed_for_writing)} + end + + def stream_request_body(%Mint.HTTP2{} = conn, request_ref, chunk) + when is_reference(request_ref) do + case Map.fetch(conn.ref_to_stream_id, request_ref) do + {:ok, stream_id} -> + {conn, payload} = encode_stream_body_request_payload(conn, stream_id, chunk) + conn = send!(conn, payload) + {:ok, conn} + + :error -> + {:error, conn, wrap_error(:unknown_request_to_stream)} + end + catch + :throw, {:mint, _conn, reason} -> + # The stream is invalid and "_conn" may be tracking it, so we return the original connection instead. + {:error, conn, reason} + end + + @doc """ + Pings the server. + + This function is specific to HTTP/2 connections. It sends a **ping** request to + the server `conn` is connected to. A `{:ok, conn, request_ref}` tuple is returned, + where `conn` is the updated connection and `request_ref` is a unique reference that + identifies this ping request. The response to a ping request is returned by `stream/2` + as a `{:pong, request_ref}` tuple. If there's an error, this function returns + `{:error, conn, reason}` where `conn` is the updated connection and `reason` is the + error reason. + + `payload` must be an 8-byte binary with arbitrary content. When the server responds to + a ping request, it will use that same payload. By default, the payload is an 8-byte + binary with all bits set to `0`. + + Pinging can be used to measure the latency with the server and to ensure the connection + is alive and well. + + ## Examples + + {:ok, conn, ref} = Mint.HTTP2.ping(conn) + + """ + @spec ping(t(), <<_::8>>) :: {:ok, t(), Types.request_ref()} | {:error, t(), Types.error()} + def ping(%Mint.HTTP2{} = conn, payload \\ :binary.copy(<<0>>, 8)) + when byte_size(payload) == 8 do + {conn, ref} = send_ping(conn, payload) + {:ok, conn, ref} + catch + :throw, {:mint, conn, error} -> {:error, conn, error} + end + + @doc """ + Communicates the given client settings to the server. + + This function is HTTP/2-specific. + + This function takes a connection and a keyword list of HTTP/2 settings and sends + the values of those settings to the server. The settings won't be effective until + the server acknowledges them, which will be handled transparently by `stream/2`. + + This function returns `{:ok, conn}` when sending the settings to the server is + successful, with `conn` being the updated connection. If there's an error, this + function returns `{:error, conn, reason}` with `conn` being the updated connection + and `reason` being the reason of the error. + + ## Supported settings + + See `t:setting/0` for the supported settings. You can see the meaning + of these settings [in the corresponding section in the HTTP/2 + RFC](https://httpwg.org/specs/rfc7540.html#SettingValues). + + See the "HTTP/2 settings" section in the module documentation for more information. + + ## Examples + + {:ok, conn} = Mint.HTTP2.put_settings(conn, max_frame_size: 100) + + """ + @spec put_settings(t(), settings()) :: {:ok, t()} | {:error, t(), Types.error()} + def put_settings(%Mint.HTTP2{} = conn, settings) when is_list(settings) do + conn = send_settings(conn, settings) + {:ok, conn} + catch + :throw, {:mint, conn, error} -> {:error, conn, error} + end + + @doc """ + Gets the value of the given HTTP/2 server settings. + + This function returns the value of the given HTTP/2 setting that the server + advertised to the client. This function is HTTP/2 specific. + For more information on HTTP/2 settings, see [the related section in + the RFC](https://httpwg.org/specs/rfc7540.html#SettingValues). + + See the "HTTP/2 settings" section in the module documentation for more information. + + ## Supported settings + + The possible settings that can be retrieved are described in `t:setting/0`. + Any other atom passed as `name` will raise an error. + + ## Examples + + Mint.HTTP2.get_server_setting(conn, :max_concurrent_streams) + #=> 500 + + """ + @spec get_server_setting(t(), atom()) :: term() + def get_server_setting(%Mint.HTTP2{} = conn, name) when is_atom(name) do + get_setting(conn.server_settings, name) + end + + @doc """ + Gets the value of the given HTTP/2 client setting. + + This function returns the value of the given HTTP/2 setting that the client + advertised to the server. Client settings can be advertised through `put_settings/2` + or when starting up a connection. + + Client settings have to be acknowledged by the server before coming into effect. + + This function is HTTP/2 specific. For more information on HTTP/2 settings, see + [the related section in the RFC](https://httpwg.org/specs/rfc7540.html#SettingValues). + + See the "HTTP/2 settings" section in the module documentation for more information. + + ## Supported settings + + The possible settings that can be retrieved are described in `t:setting/0`. + Any other atom passed as `name` will raise an error. + + ## Examples + + Mint.HTTP2.get_client_setting(conn, :max_concurrent_streams) + #=> 500 + + """ + @spec get_client_setting(t(), atom()) :: term() + def get_client_setting(%Mint.HTTP2{} = conn, name) when is_atom(name) do + get_setting(conn.client_settings, name) + end + + defp get_setting(settings, name) do + case Map.fetch(settings, name) do + {:ok, value} -> value + :error -> raise ArgumentError, "unknown HTTP/2 setting: #{inspect(name)}" + end + end + + @doc """ + Cancels an in-flight request. + + This function is HTTP/2 specific. It cancels an in-flight request. The server could have + already sent responses for the request you want to cancel: those responses will be parsed + by the connection but not returned to the user. No more responses + to a request will be returned after you call `cancel_request/2` on that request. + + If there's no error in canceling the request, `{:ok, conn}` is returned where `conn` is + the updated connection. If there's an error, `{:error, conn, reason}` is returned where + `conn` is the updated connection and `reason` is the error reason. + + ## Examples + + {:ok, conn, ref} = Mint.HTTP2.request(conn, "GET", "/", _headers = []) + {:ok, conn} = Mint.HTTP2.cancel_request(conn, ref) + + """ + @spec cancel_request(t(), Types.request_ref()) :: {:ok, t()} | {:error, t(), Types.error()} + def cancel_request(%Mint.HTTP2{} = conn, request_ref) when is_reference(request_ref) do + case Map.fetch(conn.ref_to_stream_id, request_ref) do + {:ok, stream_id} -> + conn = close_stream!(conn, stream_id, _error_code = :cancel) + {:ok, conn} + + :error -> + {:ok, conn} + end + catch + :throw, {:mint, conn, error} -> {:error, conn, error} + end + + @doc """ + Returns the window size of the connection or of a single request. + + This function is HTTP/2 specific. It returns the window size of + either the connection if `connection_or_request` is `:connection` or of a single + request if `connection_or_request` is `{:request, request_ref}`. + + Use this function to check the window size of the connection before sending a + full request. Also use this function to check the window size of both the + connection and of a request if you want to stream body chunks on that request. + + For more information on flow control and window sizes in HTTP/2, see the section + below. + + ## HTTP/2 flow control + + In HTTP/2, flow control is implemented through a + window size. When the client sends data to the server, the window size is decreased + and the server needs to "refill" it on the client side. You don't need to take care of + the refilling of the client window as it happens behind the scenes in `stream/2`. + + A window size is kept for the entire connection and all requests affect this window + size. A window size is also kept per request. + + The only thing that affects the window size is the body of a request, regardless of + if it's a full request sent with `request/5` or body chunks sent through + `stream_request_body/3`. That means that if we make a request with a body that is + five bytes long, like `"hello"`, the window size of the connection and the window size + of that particular request will decrease by five bytes. + + If we use all the window size before the server refills it, functions like + `request/5` will return an error. + + ## Examples + + On the connection: + + HTTP2.get_window_size(conn, :connection) + #=> 65_536 + + On a single streamed request: + + {:ok, conn, request_ref} = HTTP2.request(conn, "GET", "/", [], :stream) + HTTP2.get_window_size(conn, {:request, request_ref}) + #=> 65_536 + + {:ok, conn} = HTTP2.stream_request_body(conn, request_ref, "hello") + HTTP2.get_window_size(conn, {:request, request_ref}) + #=> 65_531 + + """ + @spec get_window_size(t(), :connection | {:request, Types.request_ref()}) :: non_neg_integer() + def get_window_size(conn, connection_or_request) + + def get_window_size(%Mint.HTTP2{} = conn, :connection) do + conn.window_size + end + + def get_window_size(%Mint.HTTP2{} = conn, {:request, request_ref}) do + case Map.fetch(conn.ref_to_stream_id, request_ref) do + {:ok, stream_id} -> + conn.streams[stream_id].window_size + + :error -> + raise ArgumentError, + "request with request reference #{inspect(request_ref)} was not found" + end + end + + @doc """ + See `Mint.HTTP.stream/2`. + """ + @impl true + @spec stream(t(), term()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + | :unknown + def stream(conn, message) + + def stream(%Mint.HTTP2{socket: socket} = conn, {tag, socket, reason}) + when tag in [:tcp_error, :ssl_error] do + error = conn.transport.wrap_error(reason) + {:error, %{conn | state: :closed}, error, _responses = []} + end + + def stream(%Mint.HTTP2{socket: socket} = conn, {tag, socket}) + when tag in [:tcp_closed, :ssl_closed] do + handle_closed(conn) + end + + def stream(%Mint.HTTP2{transport: transport, socket: socket} = conn, {tag, socket, data}) + when tag in [:tcp, :ssl] do + case maybe_concat_and_handle_new_data(conn, data) do + {:ok, %{mode: mode, state: state} = conn, responses} + when mode == :active and state != :closed -> + case transport.setopts(socket, active: :once) do + :ok -> {:ok, conn, responses} + {:error, reason} -> {:error, put_in(conn.state, :closed), reason, responses} + end + + other -> + other + end + catch + :throw, {:mint, conn, error, responses} -> {:error, conn, error, responses} + end + + def stream(%Mint.HTTP2{}, _message) do + :unknown + end + + @doc """ + See `Mint.HTTP.open_request_count/1`. + + In HTTP/2, the number of open requests is the number of requests **opened by the client** + that have not yet received a `:done` response. It's important to note that only + requests opened by the client (with `request/5`) count towards the number of open + requests, as requests opened from the server with server pushes (see the "Server push" + section in the module documentation) are not considered open requests. We do this because + clients might need to know how many open requests there are because the server limits + the number of concurrent requests the client can open. To know how many requests the client + can open, see `get_server_setting/2` with the `:max_concurrent_streams` setting. + """ + @impl true + @spec open_request_count(t()) :: non_neg_integer() + def open_request_count(%Mint.HTTP2{} = conn) do + conn.open_client_stream_count + end + + @doc """ + See `Mint.HTTP.recv/3`. + """ + @impl true + @spec recv(t(), non_neg_integer(), timeout()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + def recv(conn, byte_count, timeout) + + def recv(%__MODULE__{mode: :passive} = conn, byte_count, timeout) do + case conn.transport.recv(conn.socket, byte_count, timeout) do + {:ok, data} -> + maybe_concat_and_handle_new_data(conn, data) + + {:error, %TransportError{reason: :closed}} -> + handle_closed(conn) + + {:error, error} -> + {:error, %{conn | state: :closed}, error, _responses = []} + end + catch + :throw, {:mint, conn, error, responses} -> {:error, conn, error, responses} + end + + def recv(_conn, _byte_count, _timeout) do + raise ArgumentError, + "can't use recv/3 to synchronously receive data when the mode is :active. " <> + "Use Mint.HTTP.set_mode/2 to set the connection to passive mode" + end + + @doc """ + See `Mint.HTTP.set_mode/2`. + """ + @impl true + @spec set_mode(t(), :active | :passive) :: {:ok, t()} | {:error, Types.error()} + def set_mode(%__MODULE__{} = conn, mode) when mode in [:active, :passive] do + active = + case mode do + :active -> :once + :passive -> false + end + + with :ok <- conn.transport.setopts(conn.socket, active: active) do + {:ok, put_in(conn.mode, mode)} + end + end + + @doc """ + See `Mint.HTTP.controlling_process/2`. + """ + @impl true + @spec controlling_process(t(), pid()) :: {:ok, t()} | {:error, Types.error()} + def controlling_process(%__MODULE__{} = conn, new_pid) when is_pid(new_pid) do + with :ok <- conn.transport.controlling_process(conn.socket, new_pid) do + {:ok, conn} + end + end + + @doc """ + See `Mint.HTTP.put_private/3`. + """ + @impl true + @spec put_private(t(), atom(), term()) :: t() + def put_private(%Mint.HTTP2{private: private} = conn, key, value) when is_atom(key) do + %{conn | private: Map.put(private, key, value)} + end + + @doc """ + See `Mint.HTTP.get_private/3`. + """ + @impl true + @spec get_private(t(), atom(), term()) :: term() + def get_private(%Mint.HTTP2{private: private} = _conn, key, default \\ nil) when is_atom(key) do + Map.get(private, key, default) + end + + @doc """ + See `Mint.HTTP.delete_private/2`. + """ + @impl true + @spec delete_private(t(), atom()) :: t() + def delete_private(%Mint.HTTP2{private: private} = conn, key) when is_atom(key) do + %{conn | private: Map.delete(private, key)} + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + # SETTINGS parameters are not negotiated. We keep client settings and server settings separate. + @doc false + @impl true + @spec initiate( + Types.scheme(), + Types.socket(), + String.t(), + :inet.port_number(), + keyword() + ) :: {:ok, t()} | {:error, Types.error()} + def initiate(scheme, socket, hostname, port, opts) do + transport = scheme_to_transport(scheme) + mode = Keyword.get(opts, :mode, :active) + client_settings_params = Keyword.get(opts, :client_settings, []) + validate_settings!(client_settings_params) + + unless mode in [:active, :passive] do + raise ArgumentError, + "the :mode option must be either :active or :passive, got: #{inspect(mode)}" + end + + conn = %Mint.HTTP2{ + hostname: hostname, + port: port, + transport: scheme_to_transport(scheme), + socket: socket, + mode: mode, + scheme: Atom.to_string(scheme), + state: :open + } + + with :ok <- inet_opts(transport, socket), + client_settings = settings(stream_id: 0, params: client_settings_params), + preface = [@connection_preface, Frame.encode(client_settings)], + :ok <- transport.send(socket, preface), + conn = update_in(conn.client_settings_queue, &:queue.in(client_settings_params, &1)), + {:ok, server_settings, buffer, socket} <- receive_server_settings(transport, socket), + server_settings_ack = + settings(stream_id: 0, params: [], flags: set_flags(:settings, [:ack])), + :ok <- transport.send(socket, Frame.encode(server_settings_ack)), + conn = put_in(conn.buffer, buffer), + conn = put_in(conn.socket, socket), + conn = apply_server_settings(conn, settings(server_settings, :params)), + :ok <- if(mode == :active, do: transport.setopts(socket, active: :once), else: :ok) do + {:ok, conn} + else + error -> + transport.close(socket) + error + end + catch + {:mint, conn, error} -> + {:ok, _conn} = close(conn) + {:error, error} + end + + @doc """ + See `Mint.HTTP.get_socket/1`. + """ + @impl true + @spec get_socket(t()) :: Mint.Types.socket() + def get_socket(%Mint.HTTP2{socket: socket} = _conn) do + socket + end + + @doc """ + See `Mint.HTTP.get_proxy_headers/1`. + """ + if Version.compare(System.version(), "1.7.0") in [:eq, :gt] do + @doc since: "1.4.0" + end + + @impl true + @spec get_proxy_headers(t()) :: Mint.Types.headers() + def get_proxy_headers(%__MODULE__{proxy_headers: proxy_headers}), do: proxy_headers + + ## Helpers + + defp handle_closed(conn) do + conn = put_in(conn.state, :closed) + + if conn.open_client_stream_count > 0 or conn.open_server_stream_count > 0 do + error = conn.transport.wrap_error(:closed) + {:error, conn, error, _responses = []} + else + {:ok, conn, _responses = []} + end + end + + defp negotiate(address, port, :http, transport_opts) do + # We don't support protocol negotiation for TCP connections + # so currently we just assume the HTTP/2 protocol + transport = scheme_to_transport(:http) + transport.connect(address, port, transport_opts) + end + + defp negotiate(address, port, :https, transport_opts) do + transport = scheme_to_transport(:https) + + with {:ok, socket} <- transport.connect(address, port, transport_opts), + {:ok, protocol} <- transport.negotiated_protocol(socket) do + if protocol == "h2" do + {:ok, socket} + else + {:error, transport.wrap_error({:bad_alpn_protocol, protocol})} + end + end + end + + defp receive_server_settings(transport, socket) do + case recv_next_frame(transport, socket, _buffer = "") do + {:ok, settings(), _buffer, _socket} = result -> + result + + {:ok, goaway(error_code: error_code, debug_data: debug_data), _buffer, _socket} -> + error = wrap_error({:server_closed_connection, error_code, debug_data}) + {:error, error} + + {:ok, frame, _buffer, _socket} -> + debug_data = "received invalid frame #{elem(frame, 0)} during handshake" + {:error, wrap_error({:protocol_error, debug_data})} + + {:error, error} -> + {:error, error} + end + end + + defp recv_next_frame(transport, socket, buffer) do + case Frame.decode_next(buffer, @default_max_frame_size) do + {:ok, frame, rest} -> + {:ok, frame, rest, socket} + + :more -> + with {:ok, data} <- transport.recv(socket, 0, _timeout = 10_000) do + data = maybe_concat(buffer, data) + recv_next_frame(transport, socket, data) + end + + {:error, {kind, _info} = reason} when kind in [:frame_size_error, :protocol_error] -> + {:error, wrap_error(reason)} + end + end + + defp open_stream(conn) do + max_concurrent_streams = conn.server_settings.max_concurrent_streams + + if conn.open_client_stream_count >= max_concurrent_streams do + throw({:mint, conn, wrap_error(:too_many_concurrent_requests)}) + end + + stream = %{ + id: conn.next_stream_id, + ref: make_ref(), + state: :idle, + window_size: conn.server_settings.initial_window_size, + received_first_headers?: false + } + + conn = put_in(conn.streams[stream.id], stream) + conn = put_in(conn.ref_to_stream_id[stream.ref], stream.id) + conn = update_in(conn.next_stream_id, &(&1 + 2)) + {conn, stream.id, stream.ref} + end + + defp encode_stream_body_request_payload(conn, stream_id, :eof) do + encode_data(conn, stream_id, "", [:end_stream]) + end + + defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailing_headers}) do + lowered_headers = downcase_header_names(trailing_headers) + + if unallowed_trailing_header = Util.find_unallowed_trailing_header(lowered_headers) do + error = wrap_error({:unallowed_trailing_header, unallowed_trailing_header}) + throw({:mint, conn, error}) + end + + encode_headers(conn, stream_id, trailing_headers, [:end_headers, :end_stream]) + end + + defp encode_stream_body_request_payload(conn, stream_id, iodata) do + encode_data(conn, stream_id, iodata, []) + end + + defp encode_request_payload(conn, stream_id, headers, :stream) do + encode_headers(conn, stream_id, headers, [:end_headers]) + end + + defp encode_request_payload(conn, stream_id, headers, nil) do + encode_headers(conn, stream_id, headers, [:end_stream, :end_headers]) + end + + defp encode_request_payload(conn, stream_id, headers, iodata) do + {conn, headers_payload} = encode_headers(conn, stream_id, headers, [:end_headers]) + {conn, data_payload} = encode_data(conn, stream_id, iodata, [:end_stream]) + {conn, [headers_payload, data_payload]} + end + + defp encode_headers(conn, stream_id, headers, enabled_flags) do + assert_headers_smaller_than_max_header_list_size(conn, headers) + + headers = Enum.map(headers, fn {name, value} -> {:store_name, name, value} end) + {hbf, conn} = get_and_update_in(conn.encode_table, &HPAX.encode(headers, &1)) + + payload = headers_to_encoded_frames(conn, stream_id, hbf, enabled_flags) + + stream_state = if :end_stream in enabled_flags, do: :half_closed_local, else: :open + + conn = put_in(conn.streams[stream_id].state, stream_state) + conn = update_in(conn.open_client_stream_count, &(&1 + 1)) + + {conn, payload} + end + + defp assert_headers_smaller_than_max_header_list_size( + %{server_settings: %{max_header_list_size: :infinity}}, + _headers + ) do + :ok + end + + defp assert_headers_smaller_than_max_header_list_size(conn, headers) do + # The value is based on the uncompressed size of header fields, including the length + # of the name and value in octets plus an overhead of 32 octets for each header field. + total_size = + Enum.reduce(headers, 0, fn {name, value}, acc -> + acc + byte_size(name) + byte_size(value) + 32 + end) + + max_header_list_size = conn.server_settings.max_header_list_size + + if total_size <= max_header_list_size do + :ok + else + error = wrap_error({:max_header_list_size_exceeded, total_size, max_header_list_size}) + throw({:mint, conn, error}) + end + end + + defp headers_to_encoded_frames(conn, stream_id, hbf, enabled_flags) do + if IO.iodata_length(hbf) > conn.server_settings.max_frame_size do + hbf + |> IO.iodata_to_binary() + |> split_payload_in_chunks(conn.server_settings.max_frame_size) + |> split_hbf_to_encoded_frames(stream_id, enabled_flags) + else + Frame.encode( + headers(stream_id: stream_id, hbf: hbf, flags: set_flags(:headers, enabled_flags)) + ) + end + end + + defp split_hbf_to_encoded_frames({[first_chunk | chunks], last_chunk}, stream_id, enabled_flags) do + flags = set_flags(:headers, enabled_flags -- [:end_headers]) + first_frame = Frame.encode(headers(stream_id: stream_id, hbf: first_chunk, flags: flags)) + + middle_frames = + Enum.map(chunks, fn chunk -> + Frame.encode(continuation(stream_id: stream_id, hbf: chunk)) + end) + + flags = + if :end_headers in enabled_flags do + set_flags(:continuation, [:end_headers]) + else + set_flags(:continuation, []) + end + + last_frame = Frame.encode(continuation(stream_id: stream_id, hbf: last_chunk, flags: flags)) + + [first_frame, middle_frames, last_frame] + end + + defp encode_data(conn, stream_id, data, enabled_flags) do + stream = fetch_stream!(conn, stream_id) + + if stream.state != :open do + error = wrap_error(:request_is_not_streaming) + throw({:mint, conn, error}) + end + + data_size = IO.iodata_length(data) + + cond do + data_size > stream.window_size -> + throw({:mint, conn, wrap_error({:exceeds_window_size, :request, stream.window_size})}) + + data_size > conn.window_size -> + throw({:mint, conn, wrap_error({:exceeds_window_size, :connection, conn.window_size})}) + + # If the data size is greater than the max frame size, we chunk automatically based + # on the max frame size. + data_size > conn.server_settings.max_frame_size -> + {chunks, last_chunk} = + data + |> IO.iodata_to_binary() + |> split_payload_in_chunks(conn.server_settings.max_frame_size) + + {encoded_chunks, conn} = + Enum.map_reduce(chunks, conn, fn chunk, acc -> + {acc, encoded} = encode_data_chunk(acc, stream_id, chunk, []) + {encoded, acc} + end) + + {conn, encoded_last_chunk} = encode_data_chunk(conn, stream_id, last_chunk, enabled_flags) + {conn, [encoded_chunks, encoded_last_chunk]} + + true -> + encode_data_chunk(conn, stream_id, data, enabled_flags) + end + end + + defp encode_data_chunk(%__MODULE__{} = conn, stream_id, chunk, enabled_flags) + when is_integer(stream_id) and is_list(enabled_flags) do + chunk_size = IO.iodata_length(chunk) + frame = data(stream_id: stream_id, flags: set_flags(:data, enabled_flags), data: chunk) + conn = update_in(conn.streams[stream_id].window_size, &(&1 - chunk_size)) + conn = update_in(conn.window_size, &(&1 - chunk_size)) + + conn = + if :end_stream in enabled_flags do + put_in(conn.streams[stream_id].state, :half_closed_local) + else + conn + end + + {conn, Frame.encode(frame)} + end + + defp split_payload_in_chunks(binary, chunk_size), + do: split_payload_in_chunks(binary, chunk_size, []) + + defp split_payload_in_chunks(chunk, chunk_size, acc) when byte_size(chunk) <= chunk_size do + {Enum.reverse(acc), chunk} + end + + defp split_payload_in_chunks(binary, chunk_size, acc) do + <<chunk::size(chunk_size)-binary, rest::binary>> = binary + split_payload_in_chunks(rest, chunk_size, [chunk | acc]) + end + + defp send_ping(conn, payload) do + frame = Frame.ping(stream_id: 0, opaque_data: payload) + conn = send!(conn, Frame.encode(frame)) + ref = make_ref() + conn = update_in(conn.ping_queue, &:queue.in({ref, payload}, &1)) + {conn, ref} + end + + defp send_settings(conn, settings) do + validate_settings!(settings) + frame = settings(stream_id: 0, params: settings) + conn = send!(conn, Frame.encode(frame)) + conn = update_in(conn.client_settings_queue, &:queue.in(settings, &1)) + conn + end + + defp validate_settings!(settings) do + unless Keyword.keyword?(settings) do + raise ArgumentError, "settings must be a keyword list" + end + + Enum.each(settings, fn + {:header_table_size, value} -> + unless is_integer(value) do + raise ArgumentError, ":header_table_size must be an integer, got: #{inspect(value)}" + end + + {:enable_push, value} -> + unless is_boolean(value) do + raise ArgumentError, ":enable_push must be a boolean, got: #{inspect(value)}" + end + + {:max_concurrent_streams, value} -> + unless is_integer(value) do + raise ArgumentError, + ":max_concurrent_streams must be an integer, got: #{inspect(value)}" + end + + {:initial_window_size, value} -> + unless is_integer(value) and value <= @max_window_size do + raise ArgumentError, + ":initial_window_size must be an integer < #{@max_window_size}, " <> + "got: #{inspect(value)}" + end + + {:max_frame_size, value} -> + unless is_integer(value) and value in @valid_max_frame_size_range do + raise ArgumentError, + ":max_frame_size must be an integer in #{inspect(@valid_max_frame_size_range)}, " <> + "got: #{inspect(value)}" + end + + {:max_header_list_size, value} -> + unless is_integer(value) do + raise ArgumentError, ":max_header_list_size must be an integer, got: #{inspect(value)}" + end + + {:enable_connect_protocol, value} -> + unless is_boolean(value) do + raise ArgumentError, + ":enable_connect_protocol must be a boolean, got: #{inspect(value)}" + end + + {name, _value} -> + raise ArgumentError, "unknown setting parameter #{inspect(name)}" + end) + end + + defp downcase_header_names(headers) do + for {name, value} <- headers, do: {Util.downcase_ascii(name), value} + end + + defp add_default_headers(headers, body) do + headers + |> Util.put_new_header("user-agent", @user_agent) + |> add_default_content_length_header(body) + end + + defp add_default_content_length_header(headers, body) when body in [nil, :stream] do + headers + end + + defp add_default_content_length_header(headers, body) do + Util.put_new_header_lazy(headers, "content-length", fn -> + body |> IO.iodata_length() |> Integer.to_string() + end) + end + + defp add_pseudo_headers(headers, conn, method, path) do + if String.upcase(method) == "CONNECT" do + [ + {":method", method}, + {":authority", authority_pseudo_header(conn.scheme, conn.port, conn.hostname)} + | headers + ] + else + [ + {":method", method}, + {":path", path}, + {":scheme", conn.scheme}, + {":authority", authority_pseudo_header(conn.scheme, conn.port, conn.hostname)} + | headers + ] + end + end + + defp sort_pseudo_headers_to_front(headers) do + Enum.sort_by(headers, fn {key, _value} -> + not String.starts_with?(key, ":") + end) + end + + ## Frame handling + + defp maybe_concat_and_handle_new_data(conn, data) do + data = maybe_concat(conn.buffer, data) + {conn, responses} = handle_new_data(conn, data, []) + {:ok, conn, Enum.reverse(responses)} + end + + defp handle_new_data(%Mint.HTTP2{} = conn, data, responses) do + case Frame.decode_next(data, conn.client_settings.max_frame_size) do + {:ok, frame, rest} -> + assert_valid_frame(conn, frame) + {conn, responses} = handle_frame(conn, frame, responses) + handle_new_data(conn, rest, responses) + + :more -> + conn = put_in(conn.buffer, data) + handle_consumed_all_frames(conn, responses) + + {:error, :payload_too_big} -> + # TODO: sometimes, this could be handled with RST_STREAM instead of a GOAWAY frame (for + # example, if the payload of a DATA frame is too big). + # http://httpwg.org/specs/rfc7540.html#rfc.section.4.2 + debug_data = "frame payload exceeds connection's max frame size" + send_connection_error!(conn, :frame_size_error, debug_data) + + {:error, {:frame_size_error, frame}} -> + debug_data = "error with size of frame: #{inspect(frame)}" + send_connection_error!(conn, :frame_size_error, debug_data) + + {:error, {:protocol_error, info}} -> + debug_data = "error when decoding frame: #{inspect(info)}" + send_connection_error!(conn, :protocol_error, debug_data) + end + catch + :throw, {:mint, conn, error} -> throw({:mint, conn, error, responses}) + :throw, {:mint, _conn, _error, _responses} = thrown -> throw(thrown) + end + + defp handle_consumed_all_frames(%{state: state} = conn, responses) do + case state do + # TODO: should we do something with the debug data here, like logging it? + {:goaway, :no_error, _debug_data} -> + {conn, responses} + + {:goaway, error_code, debug_data} -> + error = wrap_error({:server_closed_connection, error_code, debug_data}) + throw({:mint, conn, error, responses}) + + _ -> + {conn, responses} + end + end + + defp assert_valid_frame(_conn, unknown()) do + # Unknown frames MUST be ignored: + # https://datatracker.ietf.org/doc/html/rfc7540#section-4.1 + :ok + end + + defp assert_valid_frame(conn, frame) do + stream_id = elem(frame, 1) + + assert_frame_on_right_level(conn, elem(frame, 0), stream_id) + assert_stream_id_is_allowed(conn, stream_id) + assert_frame_doesnt_interrupt_header_streaming(conn, frame) + end + + # http://httpwg.org/specs/rfc7540.html#HttpSequence + defp assert_frame_doesnt_interrupt_header_streaming(conn, frame) do + case {conn.headers_being_processed, frame} do + {nil, continuation()} -> + debug_data = "CONTINUATION received outside of headers streaming" + send_connection_error!(conn, :protocol_error, debug_data) + + {nil, _frame} -> + :ok + + {{stream_id, _, _}, continuation(stream_id: stream_id)} -> + :ok + + _other -> + debug_data = + "headers are streaming but got a #{inspect(elem(frame, 0))} frame instead " <> + "of a CONTINUATION frame" + + send_connection_error!(conn, :protocol_error, debug_data) + end + end + + stream_level_frames = [:data, :headers, :priority, :rst_stream, :push_promise, :continuation] + connection_level_frames = [:settings, :ping, :goaway] + + defp assert_frame_on_right_level(conn, frame, _stream_id = 0) + when frame in unquote(stream_level_frames) do + debug_data = "frame #{inspect(frame)} not allowed at the connection level (stream_id = 0)" + send_connection_error!(conn, :protocol_error, debug_data) + end + + defp assert_frame_on_right_level(conn, frame, stream_id) + when frame in unquote(connection_level_frames) and stream_id != 0 do + debug_data = "frame #{inspect(frame)} only allowed at the connection level" + send_connection_error!(conn, :protocol_error, debug_data) + end + + defp assert_frame_on_right_level(_conn, _frame, _stream_id) do + :ok + end + + defp assert_stream_id_is_allowed(conn, stream_id) do + if Integer.is_odd(stream_id) and stream_id >= conn.next_stream_id do + debug_data = "frame with stream ID #{inspect(stream_id)} has not been opened yet" + send_connection_error!(conn, :protocol_error, debug_data) + else + :ok + end + end + + for frame_name <- stream_level_frames ++ connection_level_frames ++ [:window_update, :unknown] do + function_name = :"handle_#{frame_name}" + + defp handle_frame(conn, Frame.unquote(frame_name)() = frame, responses) do + unquote(function_name)(conn, frame, responses) + end + end + + defp handle_unknown(conn, _frame, responses) do + # Implementations MUST ignore and discard any frame that has a type that is unknown. + # see: https://datatracker.ietf.org/doc/html/rfc7540#section-4.1 + + {conn, responses} + end + + # DATA + + defp handle_data(conn, frame, responses) do + data(stream_id: stream_id, flags: flags, data: data, padding: padding) = frame + + # Regardless of whether we have the stream or not, we need to abide by flow + # control rules so we still refill the client window for the stream_id we got. + window_size_increment = byte_size(data) + byte_size(padding || "") + + conn = + if window_size_increment > 0 do + refill_client_windows(conn, stream_id, window_size_increment) + else + conn + end + + case Map.fetch(conn.streams, stream_id) do + {:ok, stream} -> + assert_stream_in_state(conn, stream, [:open, :half_closed_local]) + responses = [{:data, stream.ref, data} | responses] + + if flag_set?(flags, :data, :end_stream) do + conn = close_stream!(conn, stream.id, :no_error) + {conn, [{:done, stream.ref} | responses]} + else + {conn, responses} + end + + :error -> + _ = Logger.debug(fn -> "Received DATA frame on closed stream ID #{stream_id}" end) + {conn, responses} + end + end + + defp refill_client_windows(conn, stream_id, data_size) do + connection_frame = window_update(stream_id: 0, window_size_increment: data_size) + stream_frame = window_update(stream_id: stream_id, window_size_increment: data_size) + + if open?(conn) do + send!(conn, [Frame.encode(connection_frame), Frame.encode(stream_frame)]) + else + conn + end + end + + # HEADERS + + defp handle_headers(conn, frame, responses) do + headers(stream_id: stream_id, flags: flags, hbf: hbf) = frame + + stream = Map.get(conn.streams, stream_id) + end_stream? = flag_set?(flags, :headers, :end_stream) + + if stream do + assert_stream_in_state(conn, stream, [:open, :half_closed_local, :reserved_remote]) + end + + if flag_set?(flags, :headers, :end_headers) do + decode_hbf_and_add_responses(conn, responses, hbf, stream, end_stream?) + else + callback = &decode_hbf_and_add_responses(&1, &2, &3, &4, end_stream?) + conn = put_in(conn.headers_being_processed, {stream_id, hbf, callback}) + {conn, responses} + end + end + + # Here, "stream" can be nil in case the stream was closed. In that case, we + # still need to process the hbf so that the HPACK table is updated, but then + # we don't add any responses. + defp decode_hbf_and_add_responses(conn, responses, hbf, stream, end_stream?) do + {conn, headers} = decode_hbf(conn, hbf) + + if stream do + handle_decoded_headers_for_stream(conn, responses, stream, headers, end_stream?) + else + _ = Logger.debug(fn -> "Received HEADERS frame on closed stream ID" end) + {conn, responses} + end + end + + defp handle_decoded_headers_for_stream(conn, responses, stream, headers, end_stream?) do + %{ref: ref, received_first_headers?: received_first_headers?} = stream + + case headers do + # Interim response (1xx), which is made of only one HEADERS plus zero or more CONTINUATIONs. + # There can be zero or more interim responses before a "proper" response. + # https://httpwg.org/specs/rfc9113.html#HttpFraming + [{":status", <<?1, _, _>> = status} | headers] -> + cond do + received_first_headers? -> + conn = close_stream!(conn, stream.id, :protocol_error) + + debug_data = + "informational response (1xx) must appear before final response, got a #{status} status" + + error = wrap_error({:protocol_error, debug_data}) + responses = [{:error, stream.ref, error} | responses] + {conn, responses} + + end_stream? -> + conn = close_stream!(conn, stream.id, :protocol_error) + debug_data = "informational response (1xx) must not have the END_STREAM flag set" + error = wrap_error({:protocol_error, debug_data}) + responses = [{:error, stream.ref, error} | responses] + {conn, responses} + + true -> + assert_stream_in_state(conn, stream, [:open, :half_closed_local]) + status = String.to_integer(status) + headers = join_cookie_headers(headers) + new_responses = [{:headers, ref, headers}, {:status, ref, status} | responses] + {conn, new_responses} + end + + [{":status", status} | headers] when not received_first_headers? -> + conn = put_in(conn.streams[stream.id].received_first_headers?, true) + status = String.to_integer(status) + headers = join_cookie_headers(headers) + new_responses = [{:headers, ref, headers}, {:status, ref, status} | responses] + + cond do + # :reserved_remote means that this was a promised stream. As soon as headers come, + # the stream goes in the :half_closed_local state (unless it's not allowed because + # of the client's max concurrent streams limit, or END_STREAM is set). + stream.state == :reserved_remote -> + cond do + conn.open_server_stream_count >= conn.client_settings.max_concurrent_streams -> + conn = close_stream!(conn, stream.id, :refused_stream) + {conn, responses} + + end_stream? -> + conn = close_stream!(conn, stream.id, :no_error) + {conn, [{:done, ref} | new_responses]} + + true -> + conn = update_in(conn.open_server_stream_count, &(&1 + 1)) + conn = put_in(conn.streams[stream.id].state, :half_closed_local) + {conn, new_responses} + end + + end_stream? -> + conn = close_stream!(conn, stream.id, :no_error) + {conn, [{:done, ref} | new_responses]} + + true -> + {conn, new_responses} + end + + # Trailing headers. We don't care about the :status header here. + headers when received_first_headers? -> + if end_stream? do + conn = close_stream!(conn, stream.id, :no_error) + headers = headers |> Util.remove_unallowed_trailing_headers() |> join_cookie_headers() + {conn, [{:done, ref}, {:headers, ref, headers} | responses]} + else + # Trailing headers must set the END_STREAM flag because they're + # the last thing allowed on the stream (other than RST_STREAM and + # the usual frames). + conn = close_stream!(conn, stream.id, :protocol_error) + debug_data = "trailing headers didn't set the END_STREAM flag" + error = wrap_error({:protocol_error, debug_data}) + responses = [{:error, stream.ref, error} | responses] + {conn, responses} + end + + # Non-trailing headers need to have a :status header, otherwise + # it's a protocol error. + _headers -> + conn = close_stream!(conn, stream.id, :protocol_error) + error = wrap_error(:missing_status_header) + responses = [{:error, stream.ref, error} | responses] + {conn, responses} + end + end + + defp decode_hbf(conn, hbf) do + case HPAX.decode(hbf, conn.decode_table) do + {:ok, headers, decode_table} -> + conn = put_in(conn.decode_table, decode_table) + {conn, headers} + + {:error, reason} -> + debug_data = "unable to decode headers: #{inspect(reason)}" + send_connection_error!(conn, :compression_error, debug_data) + end + end + + # If the port is the default for the scheme, don't add it to the :authority pseudo-header + defp authority_pseudo_header(scheme, port, hostname) do + if URI.default_port(scheme) == port do + hostname + else + "#{hostname}:#{port}" + end + end + + defp join_cookie_headers(headers) do + # If we have 0 or 1 Cookie headers, we just use the old list of headers. + case Enum.split_with(headers, fn {name, _value} -> Util.downcase_ascii(name) == "cookie" end) do + {[], _headers} -> + headers + + {[_], _headers} -> + headers + + {cookies, headers} -> + cookie = Enum.map_join(cookies, "; ", fn {_name, value} -> value end) + [{"cookie", cookie} | headers] + end + end + + # PRIORITY + + # For now we ignore all PRIORITY frames. This shouldn't cause practical trouble. + defp handle_priority(conn, frame, responses) do + _ = Logger.warn(fn -> "Ignoring PRIORITY frame: #{inspect(frame)}" end) + {conn, responses} + end + + # RST_STREAM + + defp handle_rst_stream(conn, frame, responses) do + rst_stream(stream_id: stream_id, error_code: error_code) = frame + + # If we receive RST_STREAM on a closed stream, we ignore it. + case Map.fetch(conn.streams, stream_id) do + {:ok, stream} -> + # If we receive RST_STREAM then the stream is definitely closed. + # We won't send anything else on the stream so we can simply delete + # it, so that if we get things like DATA on that stream we error out. + conn = delete_stream(conn, stream) + + if error_code == :no_error do + {conn, [{:done, stream.ref} | responses]} + else + error = wrap_error({:server_closed_request, error_code}) + {conn, [{:error, stream.ref, error} | responses]} + end + + :error -> + {conn, responses} + end + end + + # SETTINGS + + defp handle_settings(conn, frame, responses) do + settings(flags: flags, params: params) = frame + + if flag_set?(flags, :settings, :ack) do + {{:value, params}, conn} = get_and_update_in(conn.client_settings_queue, &:queue.out/1) + conn = apply_client_settings(conn, params) + {conn, responses} + else + conn = apply_server_settings(conn, params) + frame = settings(flags: set_flags(:settings, [:ack]), params: []) + conn = send!(conn, Frame.encode(frame)) + {conn, responses} + end + end + + defp apply_server_settings(conn, server_settings) do + Enum.reduce(server_settings, conn, fn + {:header_table_size, header_table_size}, conn -> + update_in(conn.encode_table, &HPAX.resize(&1, header_table_size)) + + {:enable_push, enable_push?}, conn -> + put_in(conn.server_settings.enable_push, enable_push?) + + {:max_concurrent_streams, max_concurrent_streams}, conn -> + put_in(conn.server_settings.max_concurrent_streams, max_concurrent_streams) + + {:initial_window_size, initial_window_size}, conn -> + if initial_window_size > @max_window_size do + debug_data = "INITIAL_WINDOW_SIZE setting of #{initial_window_size} is too big" + send_connection_error!(conn, :flow_control_error, debug_data) + end + + update_server_initial_window_size(conn, initial_window_size) + + {:max_frame_size, max_frame_size}, conn -> + if max_frame_size not in @valid_max_frame_size_range do + debug_data = "MAX_FRAME_SIZE setting parameter outside of allowed range" + send_connection_error!(conn, :protocol_error, debug_data) + end + + put_in(conn.server_settings.max_frame_size, max_frame_size) + + {:max_header_list_size, max_header_list_size}, conn -> + put_in(conn.server_settings.max_header_list_size, max_header_list_size) + + {:enable_connect_protocol, enable_connect_protocol?}, conn -> + put_in(conn.server_settings.enable_connect_protocol, enable_connect_protocol?) + end) + end + + defp apply_client_settings(conn, client_settings) do + Enum.reduce(client_settings, conn, fn + {:max_frame_size, value}, conn -> + put_in(conn.client_settings.max_frame_size, value) + + {:max_concurrent_streams, value}, conn -> + put_in(conn.client_settings.max_concurrent_streams, value) + + {:enable_push, value}, conn -> + put_in(conn.client_settings.enable_push, value) + end) + end + + defp update_server_initial_window_size(conn, new_iws) do + diff = new_iws - conn.server_settings.initial_window_size + + conn = + update_in(conn.streams, fn streams -> + for {stream_id, stream} <- streams, + stream.state in [:open, :half_closed_remote], + into: streams do + window_size = stream.window_size + diff + + if window_size > @max_window_size do + debug_data = + "INITIAL_WINDOW_SIZE parameter of #{window_size} makes some window sizes too big" + + send_connection_error!(conn, :flow_control_error, debug_data) + end + + {stream_id, %{stream | window_size: window_size}} + end + end) + + put_in(conn.server_settings.initial_window_size, new_iws) + end + + # PUSH_PROMISE + + defp handle_push_promise( + %Mint.HTTP2{client_settings: %{enable_push: false}} = conn, + push_promise(), + _responses + ) do + debug_data = "received PUSH_PROMISE frame when SETTINGS_ENABLE_PUSH was false" + send_connection_error!(conn, :protocol_error, debug_data) + end + + defp handle_push_promise(conn, push_promise() = frame, responses) do + push_promise( + stream_id: stream_id, + flags: flags, + promised_stream_id: promised_stream_id, + hbf: hbf + ) = frame + + assert_valid_promised_stream_id(conn, promised_stream_id) + + stream = fetch_stream!(conn, stream_id) + assert_stream_in_state(conn, stream, [:open, :half_closed_local]) + + if flag_set?(flags, :push_promise, :end_headers) do + decode_push_promise_headers_and_add_response( + conn, + responses, + hbf, + stream, + promised_stream_id + ) + else + callback = &decode_push_promise_headers_and_add_response(&1, &2, &3, &4, promised_stream_id) + conn = put_in(conn.headers_being_processed, {stream_id, hbf, callback}) + {conn, responses} + end + end + + defp decode_push_promise_headers_and_add_response( + conn, + responses, + hbf, + stream, + promised_stream_id + ) do + {conn, headers} = decode_hbf(conn, hbf) + + promised_stream = %{ + id: promised_stream_id, + ref: make_ref(), + state: :reserved_remote, + window_size: conn.server_settings.initial_window_size, + received_first_headers?: false + } + + conn = put_in(conn.streams[promised_stream.id], promised_stream) + new_response = {:push_promise, stream.ref, promised_stream.ref, headers} + {conn, [new_response | responses]} + end + + defp assert_valid_promised_stream_id(conn, promised_stream_id) do + cond do + not is_integer(promised_stream_id) or Integer.is_odd(promised_stream_id) -> + debug_data = "invalid promised stream ID: #{inspect(promised_stream_id)}" + send_connection_error!(conn, :protocol_error, debug_data) + + Map.has_key?(conn.streams, promised_stream_id) -> + debug_data = + "stream with ID #{inspect(promised_stream_id)} already exists and can't be " <> + "reserved by the server" + + send_connection_error!(conn, :protocol_error, debug_data) + + true -> + :ok + end + end + + # PING + + defp handle_ping(conn, Frame.ping() = frame, responses) do + Frame.ping(flags: flags, opaque_data: opaque_data) = frame + + if flag_set?(flags, :ping, :ack) do + handle_ping_ack(conn, opaque_data, responses) + else + ack = Frame.ping(stream_id: 0, flags: set_flags(:ping, [:ack]), opaque_data: opaque_data) + conn = send!(conn, Frame.encode(ack)) + {conn, responses} + end + end + + defp handle_ping_ack(conn, opaque_data, responses) do + case :queue.peek(conn.ping_queue) do + {:value, {ref, ^opaque_data}} -> + conn = update_in(conn.ping_queue, &:queue.drop/1) + {conn, [{:pong, ref} | responses]} + + {:value, _} -> + _ = Logger.warn("Received PING ack that doesn't match next PING request in the queue") + {conn, responses} + + :empty -> + _ = Logger.warn("Received PING ack but no PING requests are pending") + {conn, responses} + end + end + + # GOAWAY + + defp handle_goaway(conn, frame, responses) do + goaway( + last_stream_id: last_stream_id, + error_code: error_code, + debug_data: debug_data + ) = frame + + # We gather all the unprocessed requests and form {:error, _, _} tuples for each one. + # At the same time, we delete all the unprocessed requests from the stream set. + {unprocessed_request_responses, conn} = + Enum.flat_map_reduce(conn.streams, conn, fn + {stream_id, _stream}, conn_acc when stream_id <= last_stream_id -> + {[], conn_acc} + + {_stream_id, stream}, conn_acc -> + conn_acc = delete_stream(conn_acc, stream) + {[{:error, stream.ref, wrap_error(:unprocessed)}], conn_acc} + end) + + conn = put_in(conn.state, {:goaway, error_code, debug_data}) + {conn, unprocessed_request_responses ++ responses} + end + + # WINDOW_UPDATE + + defp handle_window_update( + conn, + window_update(stream_id: 0, window_size_increment: wsi), + responses + ) do + new_window_size = conn.window_size + wsi + + if new_window_size > @max_window_size do + send_connection_error!(conn, :flow_control_error, "window size too big") + else + conn = put_in(conn.window_size, new_window_size) + {conn, responses} + end + end + + defp handle_window_update( + conn, + window_update(stream_id: stream_id, window_size_increment: wsi), + responses + ) do + stream = fetch_stream!(conn, stream_id) + new_window_size = conn.streams[stream_id].window_size + wsi + + if new_window_size > @max_window_size do + conn = close_stream!(conn, stream_id, :flow_control_error) + error = wrap_error({:flow_control_error, "window size too big"}) + {conn, [{:error, stream.ref, error} | responses]} + else + conn = put_in(conn.streams[stream_id].window_size, new_window_size) + {conn, responses} + end + end + + # CONTINUATION + + defp handle_continuation(conn, frame, responses) do + continuation(stream_id: stream_id, flags: flags, hbf: hbf_chunk) = frame + stream = Map.get(conn.streams, stream_id) + + if stream do + assert_stream_in_state(conn, stream, [:open, :half_closed_local, :reserved_remote]) + end + + {^stream_id, hbf_acc, callback} = conn.headers_being_processed + + if flag_set?(flags, :continuation, :end_headers) do + hbf = IO.iodata_to_binary([hbf_acc, hbf_chunk]) + conn = put_in(conn.headers_being_processed, nil) + callback.(conn, responses, hbf, stream) + else + conn = put_in(conn.headers_being_processed, {stream_id, [hbf_acc, hbf_chunk], callback}) + {conn, responses} + end + end + + ## General helpers + + defp send_connection_error!(conn, error_code, debug_data) do + frame = + goaway(stream_id: 0, last_stream_id: 2, error_code: error_code, debug_data: debug_data) + + conn = send!(conn, Frame.encode(frame)) + _ = conn.transport.close(conn.socket) + conn = put_in(conn.state, :closed) + throw({:mint, conn, wrap_error({error_code, debug_data})}) + end + + defp close_stream!(conn, stream_id, error_code) do + stream = Map.fetch!(conn.streams, stream_id) + + # First of all we send a RST_STREAM with the given error code so that we + # move the stream to the :closed state (that is, we remove it). + rst_stream_frame = rst_stream(stream_id: stream_id, error_code: error_code) + + conn = + if open?(conn) do + send!(conn, Frame.encode(rst_stream_frame)) + else + conn + end + + delete_stream(conn, stream) + end + + defp delete_stream(conn, stream) do + conn = update_in(conn.streams, &Map.delete(&1, stream.id)) + conn = update_in(conn.ref_to_stream_id, &Map.delete(&1, stream.ref)) + + stream_open? = stream.state in [:open, :half_closed_local, :half_closed_remote] + + conn = + cond do + # Stream initiated by the client. + stream_open? and Integer.is_odd(stream.id) -> + update_in(conn.open_client_stream_count, &(&1 - 1)) + + # Stream initiated by the server. + stream_open? and Integer.is_even(stream.id) -> + update_in(conn.open_server_stream_count, &(&1 - 1)) + + true -> + conn + end + + conn + end + + defp fetch_stream!(conn, stream_id) do + case Map.fetch(conn.streams, stream_id) do + {:ok, stream} -> stream + :error -> throw({:mint, conn, wrap_error({:stream_not_found, stream_id})}) + end + end + + defp assert_stream_in_state(conn, %{state: state}, expected_states) do + if state not in expected_states do + debug_data = + "stream was in state #{inspect(state)} and not in one of the expected states: " <> + Enum.map_join(expected_states, ", ", &inspect/1) + + send_connection_error!(conn, :protocol_error, debug_data) + end + end + + defp send!(%Mint.HTTP2{transport: transport, socket: socket} = conn, bytes) do + case transport.send(socket, bytes) do + :ok -> + conn + + {:error, %TransportError{reason: :closed} = error} -> + throw({:mint, %{conn | state: :closed}, error}) + + {:error, reason} -> + throw({:mint, conn, reason}) + end + end + + defp wrap_error(reason) do + %HTTPError{reason: reason, module: __MODULE__} + end + + @doc false + def format_error(reason) + + def format_error(:closed) do + "the connection is closed" + end + + def format_error(:closed_for_writing) do + "the connection is closed for writing, which means that you cannot issue any more " <> + "requests on the connection but you can expect responses to still be delivered for " <> + "part of the requests that are in flight. If a connection is closed for writing, " <> + "it usually means that you got a :server_closed_request error already." + end + + def format_error(:too_many_concurrent_requests) do + "the number of max concurrent HTTP/2 requests supported by the server has been reached. " <> + "Use Mint.HTTP2.get_server_setting/2 with the :max_concurrent_streams setting name " <> + "to find out the maximum number of concurrent requests supported by the server." + end + + def format_error({:max_header_list_size_exceeded, size, max_size}) do + "the given header list (of size #{size}) goes over the max header list size of " <> + "#{max_size} supported by the server. In HTTP/2, the header list size is calculated " <> + "by summing up the size in bytes of each header name, value, plus 32 for each header." + end + + def format_error({:exceeds_window_size, what, window_size}) do + what = + case what do + :request -> "request" + :connection -> "connection" + end + + "the given data exceeds the #{what} window size, which is #{window_size}. " <> + "The server will refill the window size of the #{what} when ready. This will be " <> + "handled transparently by stream/2." + end + + def format_error({:stream_not_found, stream_id}) do + "request not found (with stream_id #{inspect(stream_id)})" + end + + def format_error(:unknown_request_to_stream) do + "can't stream chunk of data because the request is unknown" + end + + def format_error(:request_is_not_streaming) do + "can't send more data on this request since it's not streaming" + end + + def format_error({:unallowed_trailing_header, {name, value}}) do + "header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailing header" + end + + def format_error(:missing_status_header) do + "the :status pseudo-header (which is required in HTTP/2) is missing from the response" + end + + def format_error({:server_closed_request, error_code}) do + "server closed request with error code #{inspect(error_code)}" + end + + def format_error({:server_closed_connection, error, debug_data}) do + "server closed connection with error code #{inspect(error)} and debug data: " <> debug_data + end + + def format_error(:unprocessed) do + "request was not processed by the server, which means that it's safe to retry on a " <> + "different or new connection" + end + + def format_error({:frame_size_error, frame}) do + "frame size error for #{inspect(frame)} frame" + end + + def format_error({:protocol_error, debug_data}) do + "protocol error: " <> debug_data + end + + def format_error({:compression_error, debug_data}) do + "compression error: " <> debug_data + end + + def format_error({:flow_control_error, debug_data}) do + "flow control error: " <> debug_data + end +end diff --git a/.deps/mint/lib/mint/http2/frame.ex b/.deps/mint/lib/mint/http2/frame.ex @@ -0,0 +1,465 @@ +defmodule Mint.HTTP2.Frame do + @moduledoc false + + import Bitwise, only: [band: 2, bor: 2] + import Record + + shared_stream = [:stream_id, {:flags, 0x00}] + shared_conn = [stream_id: 0, flags: 0x00] + + defrecord :data, shared_stream ++ [:data, :padding] + defrecord :headers, shared_stream ++ [:exclusive?, :stream_dependency, :weight, :hbf, :padding] + defrecord :priority, shared_stream ++ [:exclusive?, :stream_dependency, :weight] + defrecord :rst_stream, shared_stream ++ [:error_code] + defrecord :settings, shared_conn ++ [:params] + defrecord :push_promise, shared_stream ++ [:promised_stream_id, :hbf, :padding] + defrecord :ping, shared_conn ++ [:opaque_data] + defrecord :goaway, shared_conn ++ [:last_stream_id, :error_code, :debug_data] + defrecord :window_update, shared_stream ++ [:window_size_increment] + defrecord :continuation, shared_stream ++ [:hbf] + defrecord :unknown, [] + + @types %{ + data: 0x00, + headers: 0x01, + priority: 0x02, + rst_stream: 0x03, + settings: 0x04, + push_promise: 0x05, + ping: 0x06, + goaway: 0x07, + window_update: 0x08, + continuation: 0x09 + } + + ## Flag handling + + @flags %{ + data: [end_stream: 0x01, padded: 0x08], + headers: [end_stream: 0x01, end_headers: 0x04, padded: 0x08, priority: 0x20], + settings: [ack: 0x01], + push_promise: [end_headers: 0x04, padded: 0x08], + ping: [ack: 0x01], + continuation: [end_headers: 0x04] + } + + @spec set_flags(byte(), atom(), [flag_name :: atom()]) :: byte() + def set_flags(initial_flags \\ 0x00, frame_name, flags_to_set) + when is_integer(initial_flags) and is_list(flags_to_set) do + Enum.reduce(flags_to_set, initial_flags, &set_flag(&2, frame_name, &1)) + end + + @spec flag_set?(byte(), atom(), atom()) :: boolean() + def flag_set?(flags, frame, flag_name) + + for {frame, flags} <- @flags, + {flag_name, flag_value} <- flags do + defp set_flag(flags, unquote(frame), unquote(flag_name)), do: bor(flags, unquote(flag_value)) + defp set_flag(unquote(frame), unquote(flag_name)), do: unquote(flag_value) + + def flag_set?(flags, unquote(frame), unquote(flag_name)), + do: band(flags, unquote(flag_value)) == unquote(flag_value) + end + + defmacrop is_flag_set(flags, flag) do + quote do + band(unquote(flags), unquote(flag)) == unquote(flag) + end + end + + ## Parsing + + @doc """ + Decodes the next frame of the given binary. + + Returns `{:ok, frame, rest}` if successful, `{:error, reason}` if not. + """ + @spec decode_next(binary()) :: {:ok, tuple(), binary()} | :more | {:error, reason} + when reason: + {:frame_size_error, atom()} + | {:protocol_error, binary()} + | :payload_too_big + def decode_next(bin, max_frame_size \\ 16_384) when is_binary(bin) do + case decode_next_raw(bin) do + {:ok, {_type, _flags, _stream_id, payload}, _rest} + when byte_size(payload) > max_frame_size -> + {:error, :payload_too_big} + + {:ok, {type, flags, stream_id, payload}, rest} -> + {:ok, decode_contents(type, flags, stream_id, payload), rest} + + :more -> + :more + end + catch + :throw, {:mint, reason} -> {:error, reason} + end + + defp decode_next_raw(<< + length::24, + type, + flags, + _reserved::1, + stream_id::31, + payload::size(length)-binary, + rest::binary + >>) do + {:ok, {type, flags, stream_id, payload}, rest} + end + + defp decode_next_raw(_other) do + :more + end + + for {frame, type} <- @types do + function = :"decode_#{frame}" + + defp decode_contents(unquote(type), flags, stream_id, payload) do + unquote(function)(flags, stream_id, payload) + end + end + + defp decode_contents(_type, _flags, _stream_id, _payload) do + unknown() + end + + # Parsing of specific frames + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.1 + defp decode_data(flags, stream_id, payload) do + {data, padding} = decode_padding(:data, flags, payload) + data(stream_id: stream_id, flags: flags, data: data, padding: padding) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.2 + defp decode_headers(flags, stream_id, payload) do + {data, padding} = decode_padding(:headers, flags, payload) + + {exclusive?, stream_dependency, weight, data} = + if flag_set?(flags, :headers, :priority) do + <<exclusive::1, stream_dependency::31, weight::8, rest::binary>> = data + {exclusive == 1, stream_dependency, weight + 1, rest} + else + {nil, nil, nil, data} + end + + headers( + stream_id: stream_id, + flags: flags, + padding: padding, + exclusive?: exclusive?, + stream_dependency: stream_dependency, + weight: weight, + hbf: data + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.3 + defp decode_priority(_flags, _stream_id, payload) when byte_size(payload) != 5 do + throw({:mint, {:frame_size_error, :priority}}) + end + + defp decode_priority(flags, stream_id, payload) do + <<exclusive::1, stream_dependency::31, weight::8>> = payload + + priority( + stream_id: stream_id, + flags: flags, + exclusive?: exclusive == 1, + stream_dependency: stream_dependency, + weight: weight + 1 + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.4 + defp decode_rst_stream(_flags, _stream_id, payload) when byte_size(payload) != 4 do + throw({:mint, {:frame_size_error, :rst_stream}}) + end + + defp decode_rst_stream(flags, stream_id, <<error_code::32>>) do + rst_stream( + stream_id: stream_id, + flags: flags, + error_code: humanize_error_code(error_code) + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + defp decode_settings(_flags, _stream_id, payload) when rem(byte_size(payload), 6) != 0 do + throw({:mint, {:frame_size_error, :settings}}) + end + + defp decode_settings(flags, stream_id, payload) do + settings(stream_id: stream_id, flags: flags, params: decode_settings_params(payload)) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.6 + defp decode_push_promise(flags, stream_id, payload) do + {data, padding} = decode_padding(:push_promise, flags, payload) + <<_reserved::1, promised_stream_id::31, header_block_fragment::binary>> = data + + push_promise( + stream_id: stream_id, + flags: flags, + promised_stream_id: promised_stream_id, + hbf: header_block_fragment, + padding: padding + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.7 + defp decode_ping(_flags, _stream_id, payload) when byte_size(payload) != 8 do + throw({:mint, {:frame_size_error, :ping}}) + end + + defp decode_ping(flags, stream_id, payload) do + ping(stream_id: stream_id, flags: flags, opaque_data: payload) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.8 + defp decode_goaway(flags, stream_id, payload) do + <<_reserved::1, last_stream_id::31, error_code::32, debug_data::binary>> = payload + + goaway( + stream_id: stream_id, + flags: flags, + last_stream_id: last_stream_id, + error_code: humanize_error_code(error_code), + debug_data: debug_data + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.9 + defp decode_window_update(_flags, _stream_id, payload) when byte_size(payload) != 4 do + throw({:mint, {:frame_size_error, :window_update}}) + end + + defp decode_window_update(_flags, _stream_id, <<_reserved::1, 0::31>>) do + throw({:mint, {:protocol_error, "bad WINDOW_SIZE increment"}}) + end + + defp decode_window_update(flags, stream_id, <<_reserved::1, window_size_increment::31>>) do + window_update( + stream_id: stream_id, + flags: flags, + window_size_increment: window_size_increment + ) + end + + # http://httpwg.org/specs/rfc7540.html#rfc.section.6.10 + defp decode_continuation(flags, stream_id, payload) do + continuation(stream_id: stream_id, flags: flags, hbf: payload) + end + + defp decode_padding(frame, flags, <<pad_length, rest::binary>> = payload) + when is_flag_set(flags, unquote(@flags[:data][:padded])) do + if pad_length >= byte_size(payload) do + debug_data = + "the padding length of a #{inspect(frame)} frame is bigger than the payload length" + + throw({:mint, {:protocol_error, debug_data}}) + else + # 1 byte is for the space taken by pad_length + data_length = byte_size(payload) - pad_length - 1 + <<data::size(data_length)-binary, padding::size(pad_length)-binary>> = rest + {data, padding} + end + end + + defp decode_padding(_frame, _flags, payload) do + {payload, nil} + end + + defp decode_settings_params(payload) do + decode_settings_params(payload, _acc = []) + end + + defp decode_settings_params(<<>>, acc) do + Enum.reverse(acc) + end + + defp decode_settings_params(<<identifier::16, value::32, rest::binary>>, acc) do + # From http://httpwg.org/specs/rfc7540.html#SettingValues: + # An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST + # ignore that setting. + acc = + case identifier do + 0x01 -> [{:header_table_size, value} | acc] + 0x02 -> [{:enable_push, value == 1} | acc] + 0x03 -> [{:max_concurrent_streams, value} | acc] + 0x04 -> [{:initial_window_size, value} | acc] + 0x05 -> [{:max_frame_size, value} | acc] + 0x06 -> [{:max_header_list_size, value} | acc] + 0x08 -> [{:enable_connect_protocol, value == 1} | acc] + _other -> acc + end + + decode_settings_params(rest, acc) + end + + ## Encoding + + @doc """ + Encodes the given `frame`. + """ + @spec encode(tuple()) :: iodata() + def encode(frame) + + def encode(data(stream_id: stream_id, flags: flags, data: data, padding: nil)) do + encode_raw(@types[:data], flags, stream_id, data) + end + + def encode(data(stream_id: stream_id, flags: flags, data: data, padding: padding)) do + flags = set_flags(flags, :data, [:padded]) + payload = [byte_size(padding), data, padding] + encode_raw(@types[:data], flags, stream_id, payload) + end + + def encode(headers() = frame) do + headers( + flags: flags, + stream_id: stream_id, + exclusive?: exclusive?, + stream_dependency: stream_dependency, + weight: weight, + hbf: hbf, + padding: padding + ) = frame + + payload = hbf + + {payload, flags} = + if stream_dependency && weight && is_boolean(exclusive?) do + { + [<<if(exclusive?, do: 1, else: 0)::1, stream_dependency::31>>, weight - 1, payload], + set_flags(flags, :headers, [:priority]) + } + else + {payload, flags} + end + + {payload, flags} = + if padding do + {[byte_size(padding), payload, padding], set_flags(flags, :headers, [:padded])} + else + {payload, flags} + end + + encode_raw(@types[:headers], flags, stream_id, payload) + end + + def encode(priority() = frame) do + priority( + stream_id: stream_id, + flags: flags, + exclusive?: exclusive?, + stream_dependency: stream_dependency, + weight: weight + ) = frame + + payload = [ + <<if(exclusive?, do: 1, else: 0)::1, stream_dependency::31>>, + weight - 1 + ] + + encode_raw(@types[:priority], flags, stream_id, payload) + end + + def encode(rst_stream(stream_id: stream_id, flags: flags, error_code: error_code)) do + payload = <<dehumanize_error_code(error_code)::32>> + encode_raw(@types[:rst_stream], flags, stream_id, payload) + end + + def encode(settings(stream_id: stream_id, flags: flags, params: params)) do + payload = + Enum.map(params, fn + {:header_table_size, value} -> <<0x01::16, value::32>> + {:enable_push, value} -> <<0x02::16, if(value, do: 1, else: 0)::32>> + {:max_concurrent_streams, value} -> <<0x03::16, value::32>> + {:initial_window_size, value} -> <<0x04::16, value::32>> + {:max_frame_size, value} -> <<0x05::16, value::32>> + {:max_header_list_size, value} -> <<0x06::16, value::32>> + {:enable_connect_protocol, value} -> <<0x08::16, if(value, do: 1, else: 0)::32>> + end) + + encode_raw(@types[:settings], flags, stream_id, payload) + end + + def encode(push_promise() = frame) do + push_promise( + stream_id: stream_id, + flags: flags, + promised_stream_id: promised_stream_id, + hbf: hbf, + padding: padding + ) = frame + + payload = [<<0::1, promised_stream_id::31>>, hbf] + + {payload, flags} = + if padding do + { + [byte_size(padding), payload, padding], + set_flags(flags, :push_promise, [:padded]) + } + else + {payload, flags} + end + + encode_raw(@types[:push_promise], flags, stream_id, payload) + end + + def encode(ping(stream_id: 0, flags: flags, opaque_data: opaque_data)) do + encode_raw(@types[:ping], flags, 0, opaque_data) + end + + def encode(goaway() = frame) do + goaway( + stream_id: 0, + flags: flags, + last_stream_id: last_stream_id, + error_code: error_code, + debug_data: debug_data + ) = frame + + payload = [<<0::1, last_stream_id::31, dehumanize_error_code(error_code)::32>>, debug_data] + encode_raw(@types[:goaway], flags, 0, payload) + end + + def encode(window_update(stream_id: stream_id, flags: flags, window_size_increment: wsi)) do + payload = <<0::1, wsi::31>> + encode_raw(@types[:window_update], flags, stream_id, payload) + end + + def encode(continuation(stream_id: stream_id, flags: flags, hbf: hbf)) do + encode_raw(@types[:continuation], flags, stream_id, _payload = hbf) + end + + def encode_raw(type, flags, stream_id, payload) do + [<<IO.iodata_length(payload)::24>>, type, flags, <<0::1, stream_id::31>>, payload] + end + + ## Helpers + + error_codes = %{ + 0x00 => :no_error, + 0x01 => :protocol_error, + 0x02 => :internal_error, + 0x03 => :flow_control_error, + 0x04 => :settings_timeout, + 0x05 => :stream_closed, + 0x06 => :frame_size_error, + 0x07 => :refused_stream, + 0x08 => :cancel, + 0x09 => :compression_error, + 0x0A => :connect_error, + 0x0B => :enhance_your_calm, + 0x0C => :inadequate_security, + 0x0D => :http_1_1_required + } + + for {code, human_code} <- error_codes do + defp humanize_error_code(unquote(code)), do: unquote(human_code) + defp dehumanize_error_code(unquote(human_code)), do: unquote(code) + end +end diff --git a/.deps/mint/lib/mint/http_error.ex b/.deps/mint/lib/mint/http_error.ex @@ -0,0 +1,65 @@ +defmodule Mint.HTTPError do + @moduledoc """ + An HTTP error. + + This exception struct is used to represent HTTP errors of all sorts and for + both HTTP/1 and HTTP/2. + + A `Mint.HTTPError` struct is an exception, so it can be raised as any + other exception. + + ## Struct + + The `Mint.HTTPError` struct is opaque, that is, not all of its fields are public. + The list of public fields is: + + * `:reason` - the error reason. Can be one of: + + * a term of type `t:Mint.HTTP1.error_reason/0`. See its documentation for + more information. + + * a term of type `t:Mint.HTTP2.error_reason/0`. See its documentation for + more information. + + * `{:proxy, reason}`, which is used when an HTTP error happens when connecting + to a tunnel proxy. `reason` can be: + + * `:tunnel_timeout` - when the tunnel times out. + + * `{:unexpected_status, status}` - when the proxy returns an unexpected + status `status`. + + * `{:unexpected_trailing_responses, responses}` - when the proxy returns + unexpected responses (`responses`). + + ## Message representation + + If you want to convert an error reason to a human-friendly message (for example + for using in logs), you can use `Exception.message/1`: + + iex> {:error, %Mint.HTTPError{} = error} = Mint.HTTP.connect(:http, "badresponse.com", 80) + iex> Exception.message(error) + "the response contains two or more Content-Length headers" + + """ + + alias Mint.{HTTP1, HTTP2} + + @type proxy_reason() :: + {:proxy, + HTTP1.error_reason() + | HTTP2.error_reason() + | :tunnel_timeout + | {:unexpected_status, non_neg_integer()} + | {:unexpected_trailing_responses, list()}} + + @type t() :: %__MODULE__{ + reason: HTTP1.error_reason() | HTTP2.error_reason() | proxy_reason() | term() + } + + defexception [:reason, :module] + + def message(%__MODULE__{reason: reason, module: module}) do + module.format_error(reason) + end +end diff --git a/.deps/mint/lib/mint/negotiate.ex b/.deps/mint/lib/mint/negotiate.ex @@ -0,0 +1,133 @@ +defmodule Mint.Negotiate do + @moduledoc false + + import Mint.Core.Util + + alias Mint.{ + HTTP1, + HTTP2, + TransportError + } + + @default_protocols [:http1, :http2] + @transport_opts [alpn_advertised_protocols: ["http/1.1", "h2"]] + + def connect(scheme, address, port, opts \\ []) do + {protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols) + + case Enum.sort(protocols) do + [:http1] -> + HTTP1.connect(scheme, address, port, opts) + + [:http2] -> + HTTP2.connect(scheme, address, port, opts) + + [:http1, :http2] -> + transport_connect(scheme, address, port, opts) + end + end + + def upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) do + {protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols) + + case Enum.sort(protocols) do + [:http1] -> + HTTP1.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) + + [:http2] -> + HTTP2.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) + + [:http1, :http2] -> + transport_upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) + end + end + + def initiate(transport, transport_state, hostname, port, opts), + do: alpn_negotiate(transport, transport_state, hostname, port, opts) + + defp transport_connect(:http, address, port, opts) do + # HTTP1 upgrade is not supported + HTTP1.connect(:http, address, port, opts) + end + + defp transport_connect(:https, address, port, opts) do + connect_negotiate(:https, address, port, opts) + end + + defp connect_negotiate(scheme, address, port, opts) do + transport = scheme_to_transport(scheme) + hostname = Mint.Core.Util.hostname(opts, address) + + transport_opts = + opts + |> Keyword.get(:transport_opts, []) + |> Keyword.merge(@transport_opts) + |> Keyword.put(:hostname, hostname) + + with {:ok, transport_state} <- transport.connect(address, port, transport_opts) do + alpn_negotiate(scheme, transport_state, hostname, port, opts) + end + end + + defp transport_upgrade( + proxy_scheme, + transport_state, + :http, + hostname, + port, + opts + ) do + # HTTP1 upgrade is not supported + HTTP1.upgrade(proxy_scheme, transport_state, :http, hostname, port, opts) + end + + defp transport_upgrade( + proxy_scheme, + transport_state, + :https, + hostname, + port, + opts + ) do + connect_upgrade(proxy_scheme, transport_state, :https, hostname, port, opts) + end + + defp connect_upgrade(proxy_scheme, transport_state, new_scheme, hostname, port, opts) do + transport = scheme_to_transport(new_scheme) + + transport_opts = + opts + |> Keyword.get(:transport_opts, []) + |> Keyword.merge(@transport_opts) + + case transport.upgrade(transport_state, proxy_scheme, hostname, port, transport_opts) do + {:ok, transport_state} -> + alpn_negotiate(new_scheme, transport_state, hostname, port, opts) + + {:error, reason} -> + {:error, %TransportError{reason: reason}} + end + end + + defp alpn_negotiate(scheme, socket, hostname, port, opts) do + transport = scheme_to_transport(scheme) + + case transport.negotiated_protocol(socket) do + {:ok, "http/1.1"} -> + HTTP1.initiate(scheme, socket, hostname, port, opts) + + {:ok, "h2"} -> + HTTP2.initiate(scheme, socket, hostname, port, opts) + + {:error, %TransportError{reason: :protocol_not_negotiated}} -> + # Assume HTTP1 if ALPN is not supported + HTTP1.initiate(scheme, socket, hostname, port, opts) + + {:ok, protocol} -> + {:error, %TransportError{reason: {:bad_alpn_protocol, protocol}}} + + {:error, %TransportError{} = error} -> + {:error, error} + end + end +end diff --git a/.deps/mint/lib/mint/transport_error.ex b/.deps/mint/lib/mint/transport_error.ex @@ -0,0 +1,92 @@ +defmodule Mint.TransportError do + @moduledoc """ + Represents an error with the transport used by an HTTP connection. + + A `Mint.TransportError` struct is an exception, so it can be raised as any + other exception. + + ## Struct fields + + This exception represents an error with the transport (TCP or SSL) used + by an HTTP connection. The exception struct itself is opaque, that is, + not all fields are public. The following are the public fields: + + * `:reason` - a term representing the error reason. The value of this field + can be: + + * `:timeout` - if there's a timeout in interacting with the socket. + + * `:closed` - if the connection has been closed. + + * `:protocol_not_negotiated` - if the ALPN protocol negotiation failed. + + * `{:bad_alpn_protocol, protocol}` - when the ALPN protocol is not + one of the supported protocols, which are `http/1.1` and `h2`. + + * `t::inet.posix/0` - if there's any other error with the socket, + such as `:econnrefused` or `:nxdomain`. + + * `t::ssl.error_alert/0` - if there's an SSL error. + + ## Message representation + + If you want to convert an error reason to a human-friendly message (for example + for using in logs), you can use `Exception.message/1`: + + iex> {:error, %Mint.TransportError{} = error} = Mint.HTTP.connect(:http, "nonexistent", 80) + iex> Exception.message(error) + "non-existing domain" + + """ + + reason_type = + quote do + :timeout + | :closed + | :protocol_not_negotiated + | {:bad_alpn_protocol, String.t()} + | :inet.posix() + end + + reason_type = + if System.otp_release() >= "21" do + quote do: unquote(reason_type) | :ssl.error_alert() + else + reason_type + end + + @type t() :: %__MODULE__{reason: unquote(reason_type) | term()} + + defexception [:reason] + + def message(%__MODULE__{reason: reason}) do + format_reason(reason) + end + + ## Our reasons. + + defp format_reason(:protocol_not_negotiated) do + "ALPN protocol not negotiated" + end + + defp format_reason({:bad_alpn_protocol, protocol}) do + "bad ALPN protocol #{inspect(protocol)}, supported protocols are \"http/1.1\" and \"h2\"" + end + + defp format_reason(:closed) do + "socket closed" + end + + defp format_reason(:timeout) do + "timeout" + end + + # :ssl.format_error/1 falls back to :inet.format_error/1 when the error is not an SSL-specific + # error (at least since OTP 19+), so we can just use that. + defp format_reason(reason) do + case :ssl.format_error(reason) do + 'Unexpected error:' ++ _ -> inspect(reason) + message -> List.to_string(message) + end + end +end diff --git a/.deps/mint/lib/mint/tunnel_proxy.ex b/.deps/mint/lib/mint/tunnel_proxy.ex @@ -0,0 +1,145 @@ +defmodule Mint.TunnelProxy do + @moduledoc false + + alias Mint.{HTTP1, HTTPError, Negotiate, TransportError} + + @tunnel_timeout 30_000 + + def connect(proxy, host) do + with {:ok, conn} <- establish_proxy(proxy, host) do + upgrade_connection(conn, proxy, host) + end + end + + defp establish_proxy(proxy, host) do + {proxy_scheme, proxy_address, proxy_port, proxy_opts} = proxy + {_scheme, address, port, opts} = host + hostname = Mint.Core.Util.hostname(opts, address) + + path = "#{hostname}:#{port}" + + with {:ok, conn} <- HTTP1.connect(proxy_scheme, proxy_address, proxy_port, proxy_opts), + timeout_deadline = timeout_deadline(proxy_opts), + headers = Keyword.get(opts, :proxy_headers, []), + {:ok, conn, ref} <- HTTP1.request(conn, "CONNECT", path, headers, nil), + {:ok, proxy_headers} <- receive_response(conn, ref, timeout_deadline) do + {:ok, struct!(conn, proxy_headers: proxy_headers)} + else + {:error, reason} -> + {:error, wrap_in_proxy_error(reason)} + + {:error, conn, reason} -> + {:ok, _conn} = HTTP1.close(conn) + {:error, wrap_in_proxy_error(reason)} + end + end + + defp upgrade_connection( + %{proxy_headers: proxy_headers} = conn, + {proxy_scheme, _proxy_address, _proxy_port, _proxy_opts} = _proxy, + {scheme, hostname, port, opts} = _host + ) do + socket = HTTP1.get_socket(conn) + + # Note that we may leak messages if the server sent data after the CONNECT response + case Negotiate.upgrade(proxy_scheme, socket, scheme, hostname, port, opts) do + {:ok, conn} -> {:ok, struct!(conn, proxy_headers: proxy_headers)} + {:error, reason} -> wrap_in_proxy_error(reason) + end + end + + defp receive_response(conn, ref, timeout_deadline) do + timeout = timeout_deadline - System.monotonic_time(:millisecond) + socket = HTTP1.get_socket(conn) + + receive do + {tag, ^socket, _data} = msg when tag in [:tcp, :ssl] -> + stream(conn, ref, timeout_deadline, msg) + + {tag, ^socket} = msg when tag in [:tcp_closed, :ssl_closed] -> + stream(conn, ref, timeout_deadline, msg) + + {tag, ^socket, _reason} = msg when tag in [:tcp_error, :ssl_error] -> + stream(conn, ref, timeout_deadline, msg) + after + timeout -> + {:error, conn, wrap_error({:proxy, :tunnel_timeout})} + end + end + + defp stream(conn, ref, timeout_deadline, msg) do + case HTTP1.stream(conn, msg) do + {:ok, conn, responses} -> + case handle_responses(ref, timeout_deadline, responses) do + {:done, proxy_headers} -> {:ok, proxy_headers} + :more -> receive_response(conn, ref, timeout_deadline) + {:error, reason} -> {:error, conn, reason} + end + + {:error, conn, reason, _responses} -> + {:error, conn, wrap_in_proxy_error(reason)} + end + end + + defp handle_responses(ref, timeout_deadline, [response | responses]) do + case response do + {:status, ^ref, status} when status in 200..299 -> + handle_responses(ref, timeout_deadline, responses) + + {:status, ^ref, status} -> + {:error, wrap_error({:proxy, {:unexpected_status, status}})} + + {:headers, ^ref, headers} when responses == [] -> + {:done, headers} + + {:headers, ^ref, _headers} -> + {:error, wrap_error({:proxy, {:unexpected_trailing_responses, responses}})} + + {:error, ^ref, reason} -> + {:error, wrap_in_proxy_error(reason)} + end + end + + defp handle_responses(_ref, _timeout_deadline, []) do + :more + end + + defp timeout_deadline(opts) do + timeout = Keyword.get(opts, :tunnel_timeout, @tunnel_timeout) + System.monotonic_time(:millisecond) + timeout + end + + defp wrap_error(reason) do + %HTTPError{module: __MODULE__, reason: reason} + end + + defp wrap_in_proxy_error(%HTTPError{reason: {:proxy, _}} = error) do + error + end + + defp wrap_in_proxy_error(%HTTPError{reason: reason}) do + %HTTPError{module: __MODULE__, reason: {:proxy, reason}} + end + + defp wrap_in_proxy_error(%TransportError{} = error) do + error + end + + @doc false + def format_error({:proxy, reason}) do + case reason do + :tunnel_timeout -> + "proxy tunnel timeout" + + {:unexpected_status, status} -> + "expected tunnel proxy to return a status between 200 and 299, got: #{inspect(status)}" + + {:unexpected_trailing_responses, responses} -> + "tunnel proxy returned unexpected trailing responses: #{inspect(responses)}" + + http_reason -> + "error when establishing the tunnel proxy connection: " <> + HTTP1.format_error(http_reason) + end + end +end diff --git a/.deps/mint/lib/mint/types.ex b/.deps/mint/lib/mint/types.ex @@ -0,0 +1,74 @@ +defmodule Mint.Types do + @moduledoc """ + HTTP-related types. + """ + + @typedoc """ + A hostname, IP address, Unix domain socket path, `:loopback`, or any + other term representing an internet address. + """ + @type address() :: :inet.socket_address() | String.t() + + @typedoc """ + A request reference that uniquely identifies a request. + + Responses for a request are always tagged with a request reference so that you + can connect each response to the right request. Also see `Mint.HTTP.request/5`. + """ + @type request_ref() :: reference() + + @typedoc """ + An HTTP/2-specific response to a request. + + This type of response is only returned on HTTP/2 connections. See `t:response/0` for + more response types. + """ + @type http2_response() :: + {:pong, request_ref()} + | {:push_promise, request_ref(), promised_request_ref :: request_ref(), headers()} + + @typedoc """ + A response to a request. + + Terms of this type are returned as responses to requests. See `Mint.HTTP.stream/2` + for more information. + """ + @type response() :: + {:status, request_ref(), status()} + | {:headers, request_ref(), headers()} + | {:data, request_ref(), body_chunk :: binary()} + | {:done, request_ref()} + | {:error, request_ref(), reason :: term()} + | http2_response() + + @typedoc """ + An HTTP status code. + + The type for an HTTP is a generic non-negative integer since we don't formally check that + the response code is in the "common" range (`200..599`). + """ + @type status() :: non_neg_integer() + + @typedoc """ + HTTP headers. + + Headers are sent and received as lists of two-element tuples containing two strings, + the header name and header value. + """ + @type headers() :: [{header_name :: String.t(), header_value :: String.t()}] + + @typedoc """ + The scheme to use when connecting to an HTTP server. + """ + @type scheme() :: :http | :https + + @typedoc """ + An error reason. + """ + @type error() :: Mint.TransportError.t() | Mint.HTTPError.t() + + @typedoc """ + The connection socket. + """ + @type socket() :: term() +end diff --git a/.deps/mint/lib/mint/unsafe_proxy.ex b/.deps/mint/lib/mint/unsafe_proxy.ex @@ -0,0 +1,189 @@ +defmodule Mint.UnsafeProxy do + @moduledoc false + + alias Mint.{Types, UnsafeProxy} + + @behaviour Mint.Core.Conn + + defstruct [ + :hostname, + :port, + :scheme, + :module, + :proxy_headers, + :state + ] + + @opaque t() :: %UnsafeProxy{} + + @type host_triple() :: {Types.scheme(), address :: Types.address(), :inet.port_number()} + + @spec connect(host_triple(), host_triple(), opts :: keyword()) :: + {:ok, t()} | {:error, Types.error()} + def connect(proxy, host, opts \\ []) do + {proxy_scheme, proxy_address, proxy_port} = proxy + {scheme, address, port} = host + hostname = Mint.Core.Util.hostname(opts, address) + + with {:ok, state} <- Mint.HTTP1.connect(proxy_scheme, proxy_address, proxy_port, opts) do + conn = %UnsafeProxy{ + scheme: scheme, + hostname: hostname, + port: port, + module: Mint.HTTP1, + proxy_headers: Keyword.get(opts, :proxy_headers, []), + state: state + } + + {:ok, conn} + end + end + + @impl true + @spec initiate( + module(), + Mint.Types.socket(), + String.t(), + :inet.port_number(), + keyword() + ) :: no_return() + def initiate(_transport, _transport_state, _hostname, _port, _opts) do + raise "initiate/5 does not apply for #{inspect(__MODULE__)}" + end + + @impl true + @spec close(t()) :: {:ok, t()} + def close(%UnsafeProxy{module: module, state: state} = _conn) do + module.close(state) + end + + @impl true + @spec open?(t(), :read | :write | :read_write) :: boolean() + def open?(%UnsafeProxy{module: module, state: state}, type \\ :read_write) do + module.open?(state, type) + end + + @impl true + @spec request( + t(), + method :: String.t(), + path :: String.t(), + Types.headers(), + body :: iodata() | nil | :stream + ) :: + {:ok, t(), Types.request_ref()} + | {:error, t(), Types.error()} + def request( + %UnsafeProxy{module: module, state: state} = conn, + method, + path, + headers, + body \\ nil + ) do + path = request_line(conn, path) + headers = headers ++ conn.proxy_headers + + case module.request(state, method, path, headers, body) do + {:ok, state, request} -> {:ok, %{conn | state: state}, request} + {:error, state, reason} -> {:error, %{conn | state: state}, reason} + end + end + + @impl true + @spec stream_request_body( + t(), + Types.request_ref(), + iodata() | :eof | {:eof, trailing_headers :: Types.headers()} + ) :: + {:ok, t()} | {:error, t(), Types.error()} + def stream_request_body(%UnsafeProxy{module: module, state: state} = conn, ref, body) do + case module.stream_request_body(state, ref, body) do + {:ok, state} -> {:ok, %{conn | state: state}} + {:error, state, reason} -> {:error, %{conn | state: state}, reason} + end + end + + @impl true + @spec stream(t(), term()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + | :unknown + def stream(%UnsafeProxy{module: module, state: state} = conn, message) do + case module.stream(state, message) do + {:ok, state, responses} -> {:ok, %{conn | state: state}, responses} + {:error, state, reason, responses} -> {:error, %{conn | state: state}, reason, responses} + :unknown -> :unknown + end + end + + @impl true + @spec open_request_count(t()) :: non_neg_integer() + def open_request_count(%UnsafeProxy{module: module, state: state} = _conn) do + module.open_request_count(state) + end + + @impl true + @spec recv(t(), non_neg_integer(), timeout()) :: + {:ok, t(), [Types.response()]} + | {:error, t(), Types.error(), [Types.response()]} + def recv(%UnsafeProxy{module: module, state: state} = conn, byte_count, timeout) do + case module.recv(state, byte_count, timeout) do + {:ok, state, responses} -> {:ok, %{conn | state: state}, responses} + {:error, state, reason, responses} -> {:error, %{conn | state: state}, reason, responses} + end + end + + @impl true + @spec set_mode(t(), :active | :passive) :: {:ok, t()} | {:error, Types.error()} + def set_mode(%UnsafeProxy{module: module, state: state} = conn, mode) do + with {:ok, state} <- module.set_mode(state, mode) do + {:ok, %{conn | state: state}} + end + end + + @impl true + @spec controlling_process(t(), pid()) :: {:ok, t()} | {:error, Types.error()} + def controlling_process(%UnsafeProxy{module: module, state: state} = conn, new_pid) do + with {:ok, _} <- module.controlling_process(state, new_pid) do + {:ok, conn} + end + end + + @impl true + @spec put_private(t(), atom(), term()) :: t() + def put_private(%UnsafeProxy{module: module, state: state} = conn, key, value) do + state = module.put_private(state, key, value) + %{conn | state: state} + end + + @impl true + @spec get_private(t(), atom(), term()) :: term() + def get_private(%UnsafeProxy{module: module, state: state}, key, default \\ nil) do + module.get_private(state, key, default) + end + + @impl true + @spec delete_private(t(), atom()) :: t() + def delete_private(%UnsafeProxy{module: module, state: state} = conn, key) do + state = module.delete_private(state, key) + %{conn | state: state} + end + + defp request_line(%UnsafeProxy{scheme: scheme, hostname: hostname, port: port}, path) do + %URI{scheme: Atom.to_string(scheme), host: hostname, port: port, path: path} + |> URI.to_string() + end + + @impl true + @spec get_socket(t()) :: Mint.Types.socket() + def get_socket(%UnsafeProxy{module: module, state: state}) do + module.get_socket(state) + end + + # The `%__MODULE__{proxy_headers: value}` here is the request headers, + # not the proxy response ones. Unsafe proxy mixes its headers (if any) + # with the regular response headers, so you can get them there. + @impl true + @spec get_proxy_headers(t()) :: Mint.Types.headers() + def get_proxy_headers(%__MODULE__{}), do: [] +end diff --git a/.deps/mint/mix.exs b/.deps/mint/mix.exs @@ -0,0 +1,82 @@ +defmodule Mint.MixProject do + use Mix.Project + + @version "1.4.2" + @repo_url "https://github.com/elixir-mint/mint" + + def project do + [ + app: :mint, + version: @version, + elixir: "~> 1.5", + start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(Mix.env()), + deps: deps(), + + # Xref + xref: [ + exclude: [ + :persistent_term, + {:ssl, :cipher_suites, 1}, + CAStore + ] + ], + + # Dialyxir + dialyzer: [ + plt_add_apps: [:castore] + ], + + # Code coverage + test_coverage: [tool: ExCoveralls], + preferred_cli_env: ["coveralls.html": :test, coveralls: :test], + + # Hex + package: package(), + description: "Small and composable HTTP client.", + + # Docs + name: "Mint", + docs: [ + source_ref: "v#{@version}", + source_url: @repo_url, + extras: [ + "pages/Architecture.md", + "pages/Decompression.md" + ] + ] + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger, :ssl], + mod: {Mint.Application, []} + ] + end + + defp package do + [ + licenses: ["Apache-2.0"], + links: %{"GitHub" => @repo_url} + ] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_env), do: ["lib"] + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:castore, "~> 0.1.0", optional: true}, + {:hpax, "~> 0.1.1"}, + + # Dev/test dependencies + {:dialyxir, "~> 1.0.0-rc.6", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.20", only: :dev}, + {:excoveralls, "~> 0.14.5", only: :test}, + {:stream_data, "~> 0.5.0", only: [:dev, :test]} + ] + end +end diff --git a/.deps/mint/src/mint_shims.erl b/.deps/mint/src/mint_shims.erl @@ -0,0 +1,236 @@ +%% Shims for functions introduced in recent Erlang/OTP releases, +%% to enable use of Mint on older releases. The code in this module +%% was taken directly from the Erlang/OTP project. +%% +%% File: lib/public_key/src/public_key.erl +%% Tag: OTP-20.3.4 +%% Commit: f2c1d537dc28ffbde5d42aedec70bf4c6574c3ea +%% Changes from original file: +%% - extracted pkix_verify_hostname/2 and /3, and any private +%% functions they depend upon +%% - replaced local calls to other public functions in the +%% 'public_key' module with fully qualified equivalents +%% - replaced local type references with fully qualified equivalents +%% +%% The original license follows: + +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(mint_shims). + +-include_lib("public_key/include/public_key.hrl"). + +-export([pkix_verify_hostname/2, pkix_verify_hostname/3]). + +%-------------------------------------------------------------------- +-spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(), + ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | public_key:oid(), string()}]) -> boolean(). + +-spec pkix_verify_hostname(Cert :: #'OTPCertificate'{} | binary(), + ReferenceIDs :: [{uri_id | dns_id | ip | srv_id | public_key:oid(), string()}], + Options :: proplists:proplist()) -> boolean(). + +%% Description: Validates a hostname to RFC 6125 +%%-------------------------------------------------------------------- +pkix_verify_hostname(Cert, ReferenceIDs) -> + pkix_verify_hostname(Cert, ReferenceIDs, []). + +pkix_verify_hostname(BinCert, ReferenceIDs, Options) when is_binary(BinCert) -> + pkix_verify_hostname(public_key:pkix_decode_cert(BinCert,otp), ReferenceIDs, Options); + +pkix_verify_hostname(Cert = #'OTPCertificate'{tbsCertificate = TbsCert}, ReferenceIDs0, Opts) -> + MatchFun = proplists:get_value(match_fun, Opts, undefined), + FailCB = proplists:get_value(fail_callback, Opts, fun(_Cert) -> false end), + FqdnFun = proplists:get_value(fqdn_fun, Opts, fun verify_hostname_extract_fqdn_default/1), + + ReferenceIDs = [{T,to_string(V)} || {T,V} <- ReferenceIDs0], + PresentedIDs = + try lists:keyfind(?'id-ce-subjectAltName', + #'Extension'.extnID, + TbsCert#'OTPTBSCertificate'.extensions) + of + #'Extension'{extnValue = ExtVals} -> + [{T,to_string(V)} || {T,V} <- ExtVals]; + false -> + [] + catch + _:_ -> [] + end, + %% PresentedIDs example: [{dNSName,"ewstest.ericsson.com"}, {dNSName,"www.ericsson.com"}]} + case PresentedIDs of + [] -> + %% Fallback to CN-ids [rfc6125, ch6] + case TbsCert#'OTPTBSCertificate'.subject of + {rdnSequence,RDNseq} -> + PresentedCNs = + [{cn, to_string(V)} + || ATVs <- RDNseq, % RDNseq is list-of-lists + #'AttributeTypeAndValue'{type = ?'id-at-commonName', + value = {_T,V}} <- ATVs + % _T = kind of string (teletexString etc) + ], + %% Example of PresentedCNs: [{cn,"www.ericsson.se"}] + %% match ReferenceIDs to PresentedCNs + verify_hostname_match_loop(verify_hostname_fqnds(ReferenceIDs, FqdnFun), + PresentedCNs, + MatchFun, FailCB, Cert); + + _ -> + false + end; + _ -> + %% match ReferenceIDs to PresentedIDs + case verify_hostname_match_loop(ReferenceIDs, PresentedIDs, + MatchFun, FailCB, Cert) of + false -> + %% Try to extract DNS-IDs from URIs etc + DNS_ReferenceIDs = + [{dns_id,X} || X <- verify_hostname_fqnds(ReferenceIDs, FqdnFun)], + verify_hostname_match_loop(DNS_ReferenceIDs, PresentedIDs, + MatchFun, FailCB, Cert); + true -> + true + end + end. + +%%%---------------------------------------------------------------- +%%% pkix_verify_hostname help functions +verify_hostname_extract_fqdn_default({dns_id,S}) -> + S; +verify_hostname_extract_fqdn_default({uri_id,URI}) -> + % Modified from original to remove dependency on http_uri:parse/1 from inets + #{scheme := <<"https">>, host := Host} = 'Elixir.URI':parse(list_to_binary(URI)), + binary_to_list(Host). + + +verify_hostname_fqnds(L, FqdnFun) -> + [E || E0 <- L, + E <- [try case FqdnFun(E0) of + default -> verify_hostname_extract_fqdn_default(E0); + undefined -> undefined; % will make the "is_list(E)" test fail + Other -> Other + end + catch _:_-> undefined % will make the "is_list(E)" test fail + end], + is_list(E), + E =/= "", + {error,einval} == inet:parse_address(E) + ]. + + +-define(srvName_OID, {1,3,6,1,4,1,434,2,2,1,37,0}). + +verify_hostname_match_default(Ref, Pres) -> + verify_hostname_match_default0(to_lower_ascii(Ref), to_lower_ascii(Pres)). + +verify_hostname_match_default0(FQDN=[_|_], {cn,FQDN}) -> + not lists:member($*, FQDN); +verify_hostname_match_default0(FQDN=[_|_], {cn,Name=[_|_]}) -> + [F1|Fs] = string:tokens(FQDN, "."), + [N1|Ns] = string:tokens(Name, "."), + match_wild(F1,N1) andalso Fs==Ns; +verify_hostname_match_default0({dns_id,R}, {dNSName,P}) -> + R==P; +verify_hostname_match_default0({uri_id,R}, {uniformResourceIdentifier,P}) -> + R==P; +verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 4 -> + %% IPv4 + try + list_to_tuple(P) + == if is_tuple(R), size(R)==4 -> R; + is_list(R) -> ok(inet:parse_ipv4strict_address(R)) + end + catch + _:_ -> + false + end; + +verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 16 -> + %% IPv6. The length 16 is due to the certificate specification. + try + l16_to_tup(P) + == if is_tuple(R), size(R)==8 -> R; + is_list(R) -> ok(inet:parse_ipv6strict_address(R)) + end + catch + _:_ -> + false + end; +verify_hostname_match_default0({srv_id,R}, {srvName,P}) -> + R==P; +verify_hostname_match_default0({srv_id,R}, {?srvName_OID,P}) -> + R==P; +verify_hostname_match_default0(_, _) -> + false. + +ok({ok,X}) -> X. + +l16_to_tup(L) -> list_to_tuple(l16_to_tup(L, [])). +%% +l16_to_tup([A,B|T], Acc) -> l16_to_tup(T, [(A bsl 8) bor B | Acc]); +l16_to_tup([], Acc) -> lists:reverse(Acc). + +match_wild(A, [$*|B]) -> match_wild_suffixes(A, B); +match_wild([C|A], [ C|B]) -> match_wild(A, B); +match_wild([], []) -> true; +match_wild(_, _) -> false. + +%% Match the parts after the only wildcard by comparing them from the end +match_wild_suffixes(A, B) -> match_wild_sfx(lists:reverse(A), lists:reverse(B)). + +match_wild_sfx([$*|_], _) -> false; % Bad name (no wildcards allowed) +match_wild_sfx(_, [$*|_]) -> false; % Bad pattern (no more wildcards allowed) +match_wild_sfx([A|Ar], [A|Br]) -> match_wild_sfx(Ar, Br); +match_wild_sfx(Ar, []) -> not lists:member($*, Ar); % Chk for bad name (= wildcards) +match_wild_sfx(_, _) -> false. + + +verify_hostname_match_loop(Refs0, Pres0, undefined, FailCB, Cert) -> + Pres = lists:map(fun to_lower_ascii/1, Pres0), + Refs = lists:map(fun to_lower_ascii/1, Refs0), + lists:any( + fun(R) -> + lists:any(fun(P) -> + verify_hostname_match_default(R,P) orelse FailCB(Cert) + end, Pres) + end, Refs); +verify_hostname_match_loop(Refs, Pres, MatchFun, FailCB, Cert) -> + lists:any( + fun(R) -> + lists:any(fun(P) -> + (case MatchFun(R,P) of + default -> verify_hostname_match_default(R,P); + Bool -> Bool + end) orelse FailCB(Cert) + end, + Pres) + end, + Refs). + + +to_lower_ascii({ip,_}=X) -> X; +to_lower_ascii({iPAddress,_}=X) -> X; +to_lower_ascii(S) when is_list(S) -> lists:map(fun to_lower_ascii/1, S); +to_lower_ascii({T,S}) -> {T, to_lower_ascii(S)}; +to_lower_ascii(C) when $A =< C,C =< $Z -> C + ($a-$A); +to_lower_ascii(C) -> C. + +to_string(S) when is_list(S) -> S; +to_string(B) when is_binary(B) -> binary_to_list(B); +to_string(X) -> X. diff --git a/conf/runtime.exs b/conf/runtime.exs @@ -69,8 +69,8 @@ config :zenflows, Zenflows.DB.Repo, db_conf # restroom # config :zenflows, Zenflows.Restroom, - room_host: fetch_env!("ROOM_HOST"), - room_port: fetch_env!("ROOM_PORT"), + room_host: get_env("ROOM_HOST", "localhost"), + room_port: get_env_int.("ROOM_PORT", 3000), room_salt: fetch_env!("ROOM_SALT") # diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md @@ -36,9 +36,9 @@ also see the [Required Options](#required-options). This option should be used if extended configuration is desired (using the options mention in the link above). -* `ROOM_HOST`: The hostname or IP address of the Restroom instance. +* `ROOM_HOST`: The hostname or IP address of the Restroom instance. Defaults to `localhost`. * `ROOM_PORT`: The port number of the Restroom instance. It must be an integer - between `0` and `65535`, inclusive. + between `0` and `65535`, inclusive. Defaults to `3000`. * `ROOM_SALT`: The base64-encoded salt to be used with Restroom's keypairoomServer call. @@ -59,8 +59,7 @@ also see the [Required Options](#required-options). Some of the options on how to connect to the database and the Restroom intance are required, along with `ADMIN_KEY` that is used authenticating admin calls. -For the Restroom instance, you need the `ROOM_HOST`, `ROOM_PORT`, and `ROOM_SALT` -options. +For the Restroom instance, you only need the `ROOM_SALT` option. About the database, there are only 2 things you need to setup: how to connect to the database host, and what credentials to use. diff --git a/mix.exs b/mix.exs @@ -63,6 +63,8 @@ defp deps() do # http {:plug_cowboy, "~> 2.5"}, + {:mint, "~> 1.4"}, + {:castore, "~> 0.1"}, # graphql {:absinthe, "~> 1.7"}, diff --git a/mix.lock b/mix.lock @@ -2,6 +2,7 @@ "absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"}, "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, @@ -17,11 +18,13 @@ "ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"}, "exsync": {:hex, :exsync, "0.2.4", "5cdc824553e0f4c4bf60018a9a6bbd5d3b51f93ef8401a0d8545f93127281d03", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f7622d8bb98abbe473aa066ae46f91afdf7a5346b8b89728404f7189d2e80896"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, + "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, diff --git a/src/zenflows/application.ex b/src/zenflows/application.ex @@ -23,9 +23,11 @@ use Application def start(_type, _args) do print_header() + children = [ Zenflows.DB.Repo, Zenflows.InstVars.Domain, + Zenflows.Restroom, {Plug.Cowboy, scheme: :http, plug: Zenflows.Web.Router, options: [port: 8000]}, ] diff --git a/src/zenflows/httpc.ex b/src/zenflows/httpc.ex @@ -0,0 +1,120 @@ +defmodule Zenflows.HTTPC do +@moduledoc """ +An HTTP client implemented for Zenswarm and Restroom. +""" +use GenServer + +require Logger + +alias Mint.HTTP + +defstruct [:conn, conn_info: {}, requests: %{}] + +def start_link(opts) do + scheme = Keyword.fetch!(opts, :scheme) + host = Keyword.fetch!(opts, :host) + port = Keyword.fetch!(opts, :port) + name = Keyword.fetch!(opts, :name) + GenServer.start_link(__MODULE__, {scheme, host, port}, name: name) +end + +def request(name, method, path, headers \\ [], body \\ nil, max \\ 5) do + headers = + case :lists.keyfind("user-agent", 1, headers) do + {"user-agent", _} -> headers + false -> [{"user-agent", "zenflows/#{Application.spec(:zenflows, :vsn)}"} | headers] + end + Enum.reduce_while(1..max, nil, fn x,_ -> + case GenServer.call(name, {:request, method, path, headers, body}) do + {:ok, result} -> + {:halt, {:ok, result}} + {:error_conn, reason} -> + if x != max, do: Process.sleep(5) + {:cont, {:error, reason}} + {:error_req, reason} -> + {:halt, {:error, reason}} + end + end) +end + +@impl true +def init({scheme, host, port}) do + {:ok, %__MODULE__{conn_info: {scheme, host, port}}} +end + +@impl true +def handle_call({:request, method, path, headers, body}, from, state) do + if state.conn && HTTP.open?(state.conn) do + {:ok, state} + else + {scheme, host, port} = state.conn_info + case HTTP.connect(scheme, host, port) do + {:ok, conn} -> + state = put_in(state.conn, conn) + {:ok, state} + {:error, reason} -> + {:error, reason} + end + end + |> case do + {:ok, state} -> + case HTTP.request(state.conn, method, path, headers, body) do + {:ok, conn, request_ref} -> + state = put_in(state.conn, conn) + state = put_in(state.requests[request_ref], %{from: from, response: %{}}) + {:noreply, state} + + {:error, conn, reason} -> + state = put_in(state.conn, conn) + {:reply, {:error_req, reason}, state} + end + {:error, reason} -> + {:reply, {:error_conn, reason}, state} + end +end + +@impl true +def handle_info(message, state) do + case HTTP.stream(state.conn, message) do + :unknown -> + _ = Logger.error(fn -> "Received unknown message: " <> inspect(message) end) + {:noreply, state} + + {:ok, conn, responses} -> + state = put_in(state.conn, conn) + state = Enum.reduce(responses, state, &process_response/2) + + {:noreply, state} + {:error, conn, _reason, responses} -> + state = put_in(state.conn, conn) + # Send a response to all the succesful request + state = Enum.reduce(responses, state, &process_response/2) + + {:noreply, state} + end +end + + +defp process_response({:status, request_ref, status}, state) do + put_in(state.requests[request_ref].response[:status], status) +end + +defp process_response({:headers, request_ref, headers}, state) do + put_in(state.requests[request_ref].response[:headers], headers) +end + +defp process_response({:data, request_ref, new_data}, state) do + update_in(state.requests[request_ref].response[:data], fn data -> [(data || ""), new_data] end) +end + +defp process_response({:error, request_ref, error}, state) do + update_in(state.requests[request_ref].response[:error], error) +end + +defp process_response({:done, request_ref}, state) do + state = update_in(state.requests[request_ref].response[:data], &IO.iodata_to_binary/1) + {%{response: response, from: from}, state} = pop_in(state.requests[request_ref]) + GenServer.reply(from, {:ok, response}) + state +end +end diff --git a/src/zenflows/restroom.ex b/src/zenflows/restroom.ex @@ -20,6 +20,11 @@ defmodule Zenflows.Restroom do A module to interact with Restroom instances over (for now) HTTP. """ +def child_spec(_init_arg) do + Supervisor.child_spec({Zenflows.HTTPC, name: __MODULE__, + scheme: :http, host: host(), port: port()}, id: __MODULE__) +end + @doc """ Returns `true` when `left` and `right` are equal, `false` otherwise. """ @@ -66,22 +71,17 @@ end # Execute a Zencode specified by `name` with JSON data `data`. @spec exec(String.t(), map()) :: {:ok, map()} | {:error, any()} -defp exec(name, data) do - url = to_charlist("http://#{host()}/api/#{name}") - hdrs = [{'user-agent', useragent()}] - http_opts = [ - {:timeout, 30_000}, # 30 seconds - {:connect_timeout, 5000}, # 5 seconds - {:autoredirect, false}, - ] - with {:ok, data} <- Jason.encode(%{data: data}), - {:ok, {{_, stat, _}, _, body_charlist}} when stat == 200 or stat == 500 <- - :httpc.request(:post, {url, hdrs, 'application/json', data}, http_opts, []), - {:ok, map} <- Jason.decode(body_charlist) do +defp exec(name, post_data) do + hdrs = [{"content-type", "application/json"}] + + with {:ok, post_body} <- Jason.encode(%{data: post_data}), + {:ok, %{status: stat, data: body}} when stat == 200 or stat == 500 <- + request("POST", "/api/#{name}", hdrs, post_body), + {:ok, data} <- Jason.decode(body) do if stat == 200 do - {:ok, map} + {:ok, data} else - {:error, map |> Map.fetch!("zenroom_errors") |> Map.fetch!("logs")} + {:error, data |> Map.fetch!("zenroom_errors") |> Map.fetch!("logs")} end else {:ok, {{_, stat, _}, _, body_charlist}} -> @@ -91,24 +91,26 @@ defp exec(name, data) do end end -# Return the useragent to be used by the HTTP client, this module. -@spec useragent() :: charlist() -defp useragent() do - 'zenflows/' ++ Application.spec(:zenflows, :vsn) +defp request(method, path, headers, body) do + Zenflows.HTTPC.request(__MODULE__, method, path, headers, body) end -# Return the host string (hostname:port) of the Restroom instance. +# Return the salt from the configs. +@spec salt() :: String.t() +defp salt() do + Keyword.fetch!(conf(), :room_salt) +end + +# Return the hostname of restroom from the configs. @spec host() :: String.t() defp host() do - conf = conf() - "#{conf[:room_host]}:#{conf[:room_port]}" + Keyword.fetch!(conf(), :room_host) end -# Return the salt from the configs. -@spec salt() :: String.t() -defp salt() do - conf = conf() - conf[:room_salt] +# Return the port of restroom from the configs. +@spec port() :: non_neg_integer() +defp port() do + Keyword.fetch!(conf(), :room_port) end # Return the application configurations of this module.