]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon: root trust anchors automatically bootstrapped from IANA
authorMarek Vavruša <marek.vavrusa@nic.cz>
Sat, 5 Dec 2015 17:09:23 +0000 (18:09 +0100)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Sat, 5 Dec 2015 17:09:23 +0000 (18:09 +0100)
if the root key file doesn’t exist, it will be populated from root DNSKEY query, which will be validated against root trust anchors retrieved over HTTPS with IANA cert verification against built-in current IANA cert CA. it requires luasocket and luasec for it to work. trust anchors XML file signature is not checked, as there’s no facility for PKCS7 checking yet.

Makefile
config.mk
daemon/README.rst
daemon/engine.c
daemon/lua/trust_anchors.lua
daemon/main.c
doc/build.rst
etc/etc.mk [new file with mode: 0644]
etc/icann-ca.pem [new file with mode: 0644]

index a1158cef48cc123e62e2ce6444c37f41557ee92c..298429debb7bf07f727a6f289a45762dd3586428 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ include platform.mk
 
 # Targets
 all: info lib daemon modules
-install: lib-install daemon-install modules-install
+install: lib-install daemon-install modules-install etc-install
 check: all tests
 clean: lib-clean daemon-clean modules-clean tests-clean doc-clean
 doc: doc-html
@@ -53,6 +53,7 @@ info:
        $(info PREFIX:     $(PREFIX))
        $(info BINDIR:     $(BINDIR))
        $(info LIBDIR:     $(LIBDIR))
+       $(info ETCDIR:     $(ETCDIR))
        $(info INCLUDEDIR: $(INCLUDEDIR))
        $(info MODULEDIR:  $(MODULEDIR))
        $(info )
@@ -73,10 +74,13 @@ info:
        $(info [$(HAS_socket_wrapper)] socket_wrapper (lib))
        $(info )
 
-# Moduledir
+# Installation directories
 $(PREFIX)/$(MODULEDIR):
-       $(INSTALL) -d $(PREFIX)/$(MODULEDIR)
+       $(INSTALL) -d $@
 moduledir: $(PREFIX)/$(MODULEDIR)
+$(PREFIX)/$(ETCDIR):
+       $(INSTALL) -m 0750 -d $@
+etcdir: $(PREFIX)/$(ETCDIR)
 
 # Sub-targets
 include lib/lib.mk
@@ -84,3 +88,4 @@ include daemon/daemon.mk
 include modules/modules.mk
 include tests/tests.mk
 include doc/doc.mk
+include etc/etc.mk
index db7d462ba1fb2ed37b15c5637c1dc17528283a93..c7c2e51d174149e704a9105671fa6b130b9645a5 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -9,12 +9,13 @@ BINDIR := /bin
 LIBDIR := /lib
 INCLUDEDIR := /include
 MODULEDIR := $(LIBDIR)/kdns_modules
+ETCDIR := /etc/kresd
 
 # Tools
 CC     ?= cc
 BUILD_LDFLAGS += $(LDFLAGS)
 BUILD_CFLAGS := $(CFLAGS) -std=c99 -D_GNU_SOURCE -fPIC -Wtype-limits -Wall -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
-BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
+BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" -DETCDIR="\"$(ETCDIR)\""
 RM     := rm -f
 LN      := ln -s
 XXD     := ./scripts/embed.sh
index 6e5a2bc59486c00f0b971f45bdac720cb6def32c..72d42cefdd3ac50606e19ac603483106dd8c4307 100644 (file)
@@ -14,9 +14,34 @@ Enabling DNSSEC
 ===============
 
 The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
-To enable it, you need to provide at least _one_ trust anchor. This step is not automatic, as you're supposed to obtain
-the trust anchor `using a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_.
-From there, the Knot DNS Resolver can perform automatic updates for you.
+To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
+
+.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
+
+.. code-block:: bash
+
+   $ kresd -k root.keys # File for root keys
+   [ ta ] bootstrapped root anchor "19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"
+   [ ta ] warning: you SHOULD check the key manually, see: https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs
+   [ ta ] key: 19036 state: Valid
+   [ ta ] next refresh: 86400000
+
+Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
+
+This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
+
+.. code-block:: bash
+
+   $ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
+   $ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
+   $ unbound -c unbound.conf
+
+.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
+
+Manually providing root anchors
+-------------------------------
+
+The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DNSKEY records.
 
 1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
 2. Fetch current keys (DNSKEY), verify digests
@@ -714,4 +739,6 @@ you can see the statistics or schedule new queries.
 .. _LuaRocks: https://rocks.moonscript.org/
 .. _libuv: https://github.com/libuv/libuv
 .. _Lua: http://www.lua.org/about.html
-.. _LuaJIT: http://luajit.org/luajit.html
\ No newline at end of file
+.. _LuaJIT: http://luajit.org/luajit.html
+.. _luasec: https://luarocks.org/modules/luarocks/luasec
+.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
\ No newline at end of file
index 8d92c13da28c239105fa50e8e560d7d4851cb550..78eb0a4661b4fce6d615cd62e4ecfed959bfd490 100644 (file)
@@ -396,6 +396,10 @@ static int init_state(struct engine *engine)
        lua_setglobal(engine->L, "user");
        lua_pushcfunction(engine->L, l_libpath);
        lua_setglobal(engine->L, "libpath");
+       lua_pushliteral(engine->L, PREFIX MODULEDIR);
+       lua_setglobal(engine->L, "moduledir");
+       lua_pushliteral(engine->L, PREFIX ETCDIR);
+       lua_setglobal(engine->L, "etcdir");
        lua_pushlightuserdata(engine->L, engine);
        lua_setglobal(engine->L, "__engine");
        return kr_ok();
index 7e04957307f6c9bc7d43ab1d54586d5273e865b8..d6e91340c59c15efd22135161579f85e1fa7b99e 100644 (file)
@@ -52,7 +52,7 @@ local function ta_present(keyset, rr, hold_down_time, force)
                        ta.state = key_state.Valid
                        ta.timer = nil
                end
-               print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
+               print('[ ta ] key: '..key_tag..' state: '..ta.state)
                return true
        elseif not key_revoked then -- First time seen (NewKey)
                rr.key_tag = key_tag
@@ -62,7 +62,7 @@ local function ta_present(keyset, rr, hold_down_time, force)
                        rr.state = key_state.AddPend
                        rr.timer = now + hold_down_time
                end
-               print('[trust_anchors] key: '..key_tag..' state: '..rr.state)
+               print('[ ta ] key: '..key_tag..' state: '..rr.state)
                table.insert(keyset, rr)
                return true
        end
@@ -79,24 +79,24 @@ local function ta_missing(keyset, ta, hold_down_time)
                ta.timer = os.time() + hold_down_time
        -- Purge pending key
        elseif ta.state == key_state.AddPend then
-               print('[trust_anchors] key: '..key_tag..' purging')
+               print('[ ta ] key: '..key_tag..' purging')
                keep_ta = false
        end
-       print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
+       print('[ ta ] key: '..key_tag..' state: '..ta.state)
        return keep_ta
 end
 
 -- Plan refresh event and re-schedule itself based on the result of the callback
-local function refresh_plan(trust_anchors, timeout, refresh_cb, priming)
+local function refresh_plan(trust_anchors, timeout, refresh_cb, priming, bootstrap)
        trust_anchors.refresh_ev = event.after(timeout, function (ev)
                resolve('.', kres.type.DNSKEY, kres.class.IN, kres.query.NO_CACHE,
                function (pkt)
                        -- Schedule itself with updated timeout
-                       local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt))
+                       local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt), bootstrap)
                        if trust_anchors.refresh_time ~= nil then
                                next_time = math.min(next_time, trust_anchors.refresh_time)
                        end
-                       print('[trust_anchors] next refresh: '..next_time)
+                       print('[ ta ] next refresh: '..next_time)
                        refresh_plan(trust_anchors, next_time, refresh_cb)
                        -- Priming query, prime root NS next
                        if priming ~= nil then
@@ -107,7 +107,7 @@ local function refresh_plan(trust_anchors, timeout, refresh_cb, priming)
 end
 
 -- Active refresh, return time of the next check
-local function active_refresh(trust_anchors, pkt)
+local function active_refresh(trust_anchors, pkt, bootstrap)
        local retry = true
        if pkt:rcode() == kres.rcode.NOERROR then
                local records = pkt:section(kres.section.ANSWER)
@@ -117,7 +117,7 @@ local function active_refresh(trust_anchors, pkt)
                                table.insert(keyset, rr)
                        end
                end
-               trust_anchors.update(keyset, false)
+               trust_anchors.update(keyset, bootstrap)
                retry = false
        end
        -- Calculate refresh/retry timer (RFC 5011, 2.3)
@@ -143,6 +143,22 @@ local function keyset_write(keyset, path)
        os.rename(path..'.lock', path)
 end
 
+-- Fetch over HTTPS with peert cert checked
+local function https_fetch(url, ca)
+       local https = require('ssl.https')
+       local ltn12 = require('ltn12')
+       local resp = {}
+       local r, c, h, s = https.request{
+              url = url,
+              cafile = ca,
+              verify = {'peer', 'fail_if_no_peer_cert' },
+              protocol = 'tlsv1_2',
+              sink = ltn12.sink.table(resp),
+       }
+       if r == nil then return r, c end
+       return resp[1]
+end
+
 -- TA store management
 local trust_anchors = {
        keyset = {},
@@ -187,21 +203,31 @@ local trust_anchors = {
                return true
        end,
        -- Load keys from a file (managed)
-       config = function (path, is_unmanaged)
-               if path == trust_anchors.file_current then return end
+       config = function (path, unmanaged, bootstrap)
+               bootstrap = true
+               -- Bootstrap if requested and keyfile doesn't exist
+               if bootstrap and not io.open(path, 'r') then
+                       if not trust_anchors.bootstrap() then
+                               error('you MUST obtain the root TA manually, see: '..
+                                     'http://knot-resolver.readthedocs.org/en/latest/daemon.html#enabling-dnssec')
+                       end
+               elseif path == trust_anchors.file_current then
+                       return
+               end
+               -- Parse new keys
                local new_keys = require('zonefile').parse_file(path)
                trust_anchors.file_current = path
-               if is_unmanaged then trust_anchors.file_current = nil end
+               if unmanaged then trust_anchors.file_current = nil end
                trust_anchors.keyset = {}
-               if trust_anchors.update(new_keys, true) then
+               if bootstrap or trust_anchors.update(new_keys, true) then
                        if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
-                       refresh_plan(trust_anchors, sec, active_refresh, true)
+                       refresh_plan(trust_anchors, sec, active_refresh, true, bootstrap)
                end
        end,
        -- Add DS/DNSKEY record(s) (unmanaged)
        add = function (keystr)
                local store = kres.context().trust_anchors
-               require('zonefile').parser(function (p)
+               return require('zonefile').parser(function (p)
                        local rr = p:current_rr()
                        C.kr_ta_add(store, rr.owner, rr.type, rr.ttl, rr.rdata, #rr.rdata)
                end):read(keystr..'\n')
@@ -216,6 +242,33 @@ local trust_anchors = {
                end
                trust_anchors.insecure = list
        end,
+       bootstrap = function (url, ca)
+               -- Fetch root anchors in XML over HTTPS
+               -- @todo ICANN certificate is verified against current CA
+               --       this is not ideal, as it should rather verify .xml signature which
+               --       is signed by ICANN long-lived cert, but luasec has no PKCS7
+               ca = ca or etcdir..'/icann-ca.pem'
+               url = url or 'https://data.iana.org/root-anchors/root-anchors.xml'
+               local xml, err = https_fetch(url, ca)
+               if not xml then
+                       print(string.format('[ ta ] fetch of "%s" failed: %s', url, err))
+                       return false
+               end
+               -- Parse root trust anchor
+               local fields = {}
+               string.gsub(xml, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end)
+               local rrdata = string.format('%s %s %s %s', fields.KeyDigest, fields.Algorithm, fields.DigestType, fields.Digest)
+               local rr = string.format('%s 0 IN DS %s', fields.TrustAnchor, rrdata)
+               -- Add to key set, create an empty keyset file to be filled
+               if trust_anchors.add(rr) ~= 0 then
+                       print(string.format('[ ta ] invalid format of the RR "%s"', rr))
+                       return false
+               end
+               print(string.format('[ ta ] bootstrapped root anchor "%s"', rrdata))
+               print('[ ta ] warning: you SHOULD check the key manually, see: '..
+                     'https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs')
+               return true
+       end,
 }
 
 return trust_anchors
\ No newline at end of file
index 519418a187c7db8d10c481f27cd910a74ac3ab42..37f9ae0f3ccd24f89018c621b35763e353ad31cf 100644 (file)
@@ -242,14 +242,29 @@ int main(int argc, char **argv)
 #endif
                        break;
                case 'k':
-                       keyfile_buf = malloc(PATH_MAX + 1);
+                       keyfile_buf = malloc(PATH_MAX);
                        assert(keyfile_buf);
-                       keyfile = realpath(optarg, keyfile_buf);
-                       if (keyfile)
-                               keyfile = strdup(keyfile);
+                       /* Check if the path is absolute */
+                       if (optarg[0] == '/') {
+                               keyfile = strdup(optarg);
+                       } else {
+                               /* Construct absolute path, the file may not exist */
+                               keyfile = realpath(".", keyfile_buf);
+                               if (keyfile) {
+                                       int len = strlen(keyfile);
+                                       int namelen = strlen(optarg);
+                                       if (len + namelen < PATH_MAX - 1) {
+                                               keyfile[len] = '/';
+                                               memcpy(keyfile + len + 1, optarg, namelen + 1);
+                                               keyfile = strdup(keyfile); /* Duplicate */
+                                       } else {
+                                               keyfile = NULL; /* Invalidate */
+                                       }
+                               }
+                       }
                        free(keyfile_buf);
-                       if (!keyfile || access(optarg, R_OK|W_OK) != 0) {
-                               log_error("[system] keyfile '%s': not readable/writeable\n", optarg);
+                       if (!keyfile) {
+                               log_error("[system] keyfile '%s': not writeable\n", optarg);
                                return EXIT_FAILURE;
                        }
                        break;
index 98407581f350a0c26c59bcd32e85b62b0754243b..1526ac233d15fee716bd73d94bcda768ff360787 100644 (file)
@@ -39,6 +39,8 @@ There are also *optional* packages that enable specific functionality in Knot DN
 .. csv-table::
    :header: "Optional", "Needed for", "Notes"
 
+   "luasocket_", "``trust anchors, modules/stats``", "Sockets for Lua."
+   "luasec_", "``trust anchors``", "TLS for Lua."
    "libmemcached_", "``modules/memcached``", "To build memcached backend module."
    "hiredis_", "``modules/redis``", "To build redis backend module."
    "Go_ 1.5+", "``modules``", "Build modules written in Go."
@@ -186,6 +188,8 @@ Read the `documentation <deckard_doc>`_ for more information about requirements,
 .. _libknot: https://gitlab.labs.nic.cz/labs/knot
 .. _cmocka: https://cmocka.org/
 .. _Python: https://www.python.org/
+.. _luasec: https://luarocks.org/modules/luarocks/luasec
+.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
 
 .. _boot2docker: http://boot2docker.io/
 
diff --git a/etc/etc.mk b/etc/etc.mk
new file mode 100644 (file)
index 0000000..4a5a428
--- /dev/null
@@ -0,0 +1,6 @@
+etc_SOURCES := icann-ca.pem
+
+etc-install: etcdir
+       $(INSTALL) -m 0640 $(addprefix etc/,$(etc_SOURCES)) $(PREFIX)/$(ETCDIR)
+
+.PHONY: etc-install
diff --git a/etc/icann-ca.pem b/etc/icann-ca.pem
new file mode 100644 (file)
index 0000000..0ae28d6
--- /dev/null
@@ -0,0 +1,82 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:3b:e0:56:90:42:46:b1:a1:75:6a:c9:59:91:c7:4a
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
+        Validity
+            Not Before: Nov 10 00:00:00 2006 GMT
+            Not After : Nov 10 00:00:00 2031 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:e2:3b:e1:11:72:de:a8:a4:d3:a3:57:aa:50:a2:
+                    8f:0b:77:90:c9:a2:a5:ee:12:ce:96:5b:01:09:20:
+                    cc:01:93:a7:4e:30:b7:53:f7:43:c4:69:00:57:9d:
+                    e2:8d:22:dd:87:06:40:00:81:09:ce:ce:1b:83:bf:
+                    df:cd:3b:71:46:e2:d6:66:c7:05:b3:76:27:16:8f:
+                    7b:9e:1e:95:7d:ee:b7:48:a3:08:da:d6:af:7a:0c:
+                    39:06:65:7f:4a:5d:1f:bc:17:f8:ab:be:ee:28:d7:
+                    74:7f:7a:78:99:59:85:68:6e:5c:23:32:4b:bf:4e:
+                    c0:e8:5a:6d:e3:70:bf:77:10:bf:fc:01:f6:85:d9:
+                    a8:44:10:58:32:a9:75:18:d5:d1:a2:be:47:e2:27:
+                    6a:f4:9a:33:f8:49:08:60:8b:d4:5f:b4:3a:84:bf:
+                    a1:aa:4a:4c:7d:3e:cf:4f:5f:6c:76:5e:a0:4b:37:
+                    91:9e:dc:22:e6:6d:ce:14:1a:8e:6a:cb:fe:cd:b3:
+                    14:64:17:c7:5b:29:9e:32:bf:f2:ee:fa:d3:0b:42:
+                    d4:ab:b7:41:32:da:0c:d4:ef:f8:81:d5:bb:8d:58:
+                    3f:b5:1b:e8:49:28:a2:70:da:31:04:dd:f7:b2:16:
+                    f2:4c:0a:4e:07:a8:ed:4a:3d:5e:b5:7f:a3:90:c3:
+                    af:27
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
+            X509v3 Authority Key Identifier: 
+                keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
+
+    Signature Algorithm: sha1WithRSAEncryption
+        cb:9c:37:aa:48:13:12:0a:fa:dd:44:9c:4f:52:b0:f4:df:ae:
+        04:f5:79:79:08:a3:24:18:fc:4b:2b:84:c0:2d:b9:d5:c7:fe:
+        f4:c1:1f:58:cb:b8:6d:9c:7a:74:e7:98:29:ab:11:b5:e3:70:
+        a0:a1:cd:4c:88:99:93:8c:91:70:e2:ab:0f:1c:be:93:a9:ff:
+        63:d5:e4:07:60:d3:a3:bf:9d:5b:09:f1:d5:8e:e3:53:f4:8e:
+        63:fa:3f:a7:db:b4:66:df:62:66:d6:d1:6e:41:8d:f2:2d:b5:
+        ea:77:4a:9f:9d:58:e2:2b:59:c0:40:23:ed:2d:28:82:45:3e:
+        79:54:92:26:98:e0:80:48:a8:37:ef:f0:d6:79:60:16:de:ac:
+        e8:0e:cd:6e:ac:44:17:38:2f:49:da:e1:45:3e:2a:b9:36:53:
+        cf:3a:50:06:f7:2e:e8:c4:57:49:6c:61:21:18:d5:04:ad:78:
+        3c:2c:3a:80:6b:a7:eb:af:15:14:e9:d8:89:c1:b9:38:6c:e2:
+        91:6c:8a:ff:64:b9:77:25:57:30:c0:1b:24:a3:e1:dc:e9:df:
+        47:7c:b5:b4:24:08:05:30:ec:2d:bd:0b:bf:45:bf:50:b9:a9:
+        f3:eb:98:01:12:ad:c8:88:c6:98:34:5f:8d:0a:3c:c6:e9:d5:
+        95:95:6d:de
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----