1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY

This commit is contained in:
Andy Green 2018-04-05 20:48:08 +08:00
parent c8af76c07c
commit 9a51bd0a63
9 changed files with 553 additions and 6 deletions

View file

@ -1425,6 +1425,11 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
/*
* client... remote END_STREAM implies we weren't going to
* send anything else anyway.
*/
if (h2n->swsi->client_h2_substream &&
h2n->flags & LWS_H2_FLAG_END_STREAM) {
lwsl_info("%s: %p: DATA: end stream\n", __func__, h2n->swsi);
@ -1432,7 +1437,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN)
lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_REMOTE);
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL) {
//if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
{
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
@ -1442,7 +1448,6 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
lwsl_debug("tx completed returned close\n");
}
}
break;
case LWS_H2_FRAME_TYPE_PING:

View file

@ -5944,6 +5944,12 @@ enum lws_tls_cert_info {
LWS_TLS_CERT_INFO_VERIFIED,
/**< fills .verified with a bool representing peer cert validity,
* call returns -1 if no cert */
LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY,
/**< the certificate's public key, as an opaque bytestream. These
* opaque bytestreams can only be compared with each other using the
* same tls backend, ie, OpenSSL or mbedTLS. The different backends
* produce different, incompatible representations for the same cert.
*/
};
union lws_tls_cert_info_results {

View file

@ -408,6 +408,56 @@ lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type,
case LWS_TLS_CERT_INFO_USAGE:
buf->usage = x509->key_usage;
break;
case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:
{
char *p = buf->ns.name;
size_t r = len, u;
switch (mbedtls_pk_get_type(&x509->pk)) {
case MBEDTLS_PK_RSA:
{
mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk);
if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u))
return -1;
r -= u;
p += u;
if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u))
return -1;
p += u;
buf->ns.len = lws_ptr_diff(p, buf->ns.name);
break;
}
case MBEDTLS_PK_ECKEY:
{
mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk);
if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u))
return -1;
r -= u;
p += u;
if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u))
return -1;
r -= u;
p += u;
if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u))
return -1;
p += u;
buf->ns.len = lws_ptr_diff(p, buf->ns.name);
break;
}
default:
lwsl_notice("%s: x509 has unsupported pubkey type %d\n",
__func__,
mbedtls_pk_get_type(&x509->pk));
return -1;
}
break;
}
default:
return -1;
}
@ -428,7 +478,11 @@ LWS_VISIBLE int
lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
union lws_tls_cert_info_results *buf, size_t len)
{
mbedtls_x509_crt *x509 = ssl_get_peer_mbedtls_x509_crt(wsi->ssl);
mbedtls_x509_crt *x509;
wsi = lws_get_network_wsi(wsi);
x509 = ssl_get_peer_mbedtls_x509_crt(wsi->ssl);
if (!x509)
return -1;

View file

@ -1,7 +1,7 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -594,6 +594,37 @@ lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
#else
return -1;
#endif
case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:
{
size_t klen = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL);
uint8_t *tmp, *ptmp;
if (klen <= 0 || klen > len)
return -1;
tmp = (uint8_t *)OPENSSL_malloc(klen);
if (!tmp)
return -1;
ptmp = tmp;
if (i2d_X509_PUBKEY(
X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen ||
!ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) {
lwsl_info("%s: cert public key extraction failed\n",
__func__);
if (ptmp)
OPENSSL_free(tmp);
return -1;
}
buf->ns.len = klen;
memcpy(buf->ns.name, tmp, klen);
OPENSSL_free(tmp);
return 0;
}
default:
return -1;
}
@ -621,10 +652,17 @@ lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
union lws_tls_cert_info_results *buf, size_t len)
{
int rc = 0;
X509 *x509 = SSL_get_peer_certificate(wsi->ssl);
X509 *x509;
wsi = lws_get_network_wsi(wsi);
x509 = SSL_get_peer_certificate(wsi->ssl);
if (!x509) {
lwsl_notice("no peer cert\n");
if (!x509)
return -1;
}
switch (type) {
case LWS_TLS_CERT_INFO_VERIFIED:

View file

@ -1,5 +1,6 @@
|name|demonstrates|
---|---
minimal-http-client-certinfo|Shows how to gain detailed information on the peer certificate
minimal-http-client-hugeurl|Sends a > 2.5KB URL to warmcat.com
minimal-http-client-multi|Connects to and reads https://warmcat.com, 8 times concurrently
minimal-http-client-post|POSTs a form containing an uploaded file and a form variable, and captures the response

View file

@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-http-client-certinfo)
set(SRCS minimal-http-client-certinfo.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View file

@ -0,0 +1,71 @@
# lws minimal http client certinfo
This demonstrates how to dump information from the peer
certificate largely independent of the tls backend.
## build
```
$ cmake . && make
```
## usage
The application goes to https://warmcat.com and receives the page data.
Before receiving the page it dumps information on the server's cert.
This works independently of the tls backend being OpenSSL or mbedTLS.
However the public keys cannot be compared between the two tls
backends, since they produce different representations.
```
$ ./lws-minimal-http-client-certinfo
[2018/04/05 21:39:26:5882] USER: LWS minimal http client
[2018/04/05 21:39:26:5897] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 on
[2018/04/05 21:39:26:5955] NOTICE: created client ssl context for default
[2018/04/05 21:39:28:0824] NOTICE: lws_http_client_http_response 200
[2018/04/05 21:39:28:0824] NOTICE: Peer Cert CN : warmcat.com
[2018/04/05 21:39:28:0824] NOTICE: Peer Cert issuer : /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited
[2018/04/05 21:39:28:0825] NOTICE: Peer Cert Valid from: Mon Nov 3 00:00:00 2014
[2018/04/05 21:39:28:0825] NOTICE: Peer Cert Valid to : Sat Nov 2 23:59:59 2019
[2018/04/05 21:39:28:0825] NOTICE: Peer Cert usage bits: 0xa0
[2018/04/05 21:39:28:0825] NOTICE: Peer Cert public key:
[2018/04/05 21:39:28:0825] NOTICE:
[2018/04/05 21:39:28:0825] NOTICE: 0000: 30 82 01 22 30 0D 06 09 2A 86 48 86 F7 0D 01 01 0.."0...*.H.....
[2018/04/05 21:39:28:0825] NOTICE: 0010: 01 05 00 03 82 01 0F 00 30 82 01 0A 02 82 01 01 ........0.......
[2018/04/05 21:39:28:0825] NOTICE: 0020: 00 EC 39 C1 98 25 A8 99 AC 01 9B D2 16 C0 CA A3 ..9..%..........
[2018/04/05 21:39:28:0825] NOTICE: 0030: 0E 19 57 E5 3D 23 F3 79 7E 63 BF CD B8 88 D1 16 ..W.=#.y~c......
[2018/04/05 21:39:28:0825] NOTICE: 0040: C6 F0 A6 ED 66 CB F3 C3 D6 7E A7 A3 AB 00 0A 3E ....f....~.....>
[2018/04/05 21:39:28:0825] NOTICE: 0050: AD EF 20 44 85 5A 61 F0 71 20 BD E3 D1 4B B6 53 .. D.Za.q ...K.S
[2018/04/05 21:39:28:0825] NOTICE: 0060: 57 AA 81 E6 ED 74 36 40 E7 FC 62 24 AD E8 82 1D W....t6@..b$....
[2018/04/05 21:39:28:0826] NOTICE: 0070: 89 C4 3D 64 6C A8 34 4B DB FB DD 7D D2 2D FB 86 ..=dl.4K...}.-..
[2018/04/05 21:39:28:0826] NOTICE: 0080: 97 EA 6B E2 C9 39 D6 19 DE A8 90 E7 86 8F CF 0A ..k..9..........
[2018/04/05 21:39:28:0826] NOTICE: 0090: CD 09 3C AF FB 0A FF 85 E8 93 D1 4B A0 C5 21 AD ..<........K..!.
[2018/04/05 21:39:28:0826] NOTICE: 00A0: 58 52 30 0E 4B FE 4F C8 01 B9 BD 0F D4 E4 64 7B XR0.K.O.......d{
[2018/04/05 21:39:28:0826] NOTICE: 00B0: 04 B4 D2 68 69 8F F1 D5 FD B0 1A CE 55 43 08 B7 ...hi.......UC..
[2018/04/05 21:39:28:0826] NOTICE: 00C0: 9F 57 0D 4E E1 CA E8 5C B4 2A 6B AB 05 B5 57 67 .W.N...\.*k...Wg
[2018/04/05 21:39:28:0826] NOTICE: 00D0: B8 FD 20 F4 4F 6B 0E 47 7C AD EB B4 99 2C 9B 53 .. .Ok.G|....,.S
[2018/04/05 21:39:28:0826] NOTICE: 00E0: DF EA 67 8D 8A 9D A7 17 01 F9 4E BD 56 43 50 53 ..g.......N.VCPS
[2018/04/05 21:39:28:0826] NOTICE: 00F0: 08 4E FE 6A 85 4A 4D 45 03 DA 01 00 96 7A C0 A9 .N.j.JME.....z..
[2018/04/05 21:39:28:0826] NOTICE: 0100: C2 32 5E 1A 9F 6F 7B E2 02 5E 70 12 D3 8E 76 6A .2^..o{..^p...vj
[2018/04/05 21:39:28:0826] NOTICE: 0110: 0B 59 A4 D7 31 9D C6 86 08 53 2E 02 8A 1E B1 FB .Y..1....S......
[2018/04/05 21:39:28:0826] NOTICE: 0120: 7B 02 03 01 00 01 {.....
[2018/04/05 21:39:28:0826] NOTICE:
[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 503
[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
...
[2018/04/05 21:39:28:3777] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:3777] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 503
[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 512
[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 471
[2018/04/05 21:39:28:3778] USER: LWS_CALLBACK_COMPLETED_CLIENT_HTTP
[2018/04/05 21:39:28:3787] USER: Completed
```

View file

@ -0,0 +1,201 @@
/*
* lws-minimal-http-client
*
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This demonstrates the a minimal http client using lws.
*
* It visits https://warmcat.com/ and receives the html page there. You
* can dump the page data by changing the #if 0 below.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted;
static struct lws *client_wsi;
static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
uint8_t buf[1280];
union lws_tls_cert_info_results *ci =
(union lws_tls_cert_info_results *)buf;
switch (reason) {
/* because we are protocols[0] ... */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
client_wsi = NULL;
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
lwsl_notice("lws_http_client_http_response %d\n",
lws_http_client_http_response(wsi));
if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
ci, sizeof(buf) - sizeof(*ci)))
lwsl_notice(" Peer Cert CN : %s\n", ci->ns.name);
if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_ISSUER_NAME,
ci, sizeof(ci->ns.name)))
lwsl_notice(" Peer Cert issuer : %s\n", ci->ns.name);
if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_FROM,
ci, 0))
lwsl_notice(" Peer Cert Valid from: %s", ctime(&ci->time));
if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_TO,
ci, 0))
lwsl_notice(" Peer Cert Valid to : %s", ctime(&ci->time));
if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_USAGE,
ci, 0))
lwsl_notice(" Peer Cert usage bits: 0x%x\n", ci->usage);
if (!lws_tls_peer_cert_info(wsi,
LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY,
ci, sizeof(buf) - sizeof(*ci))) {
lwsl_notice(" Peer Cert public key:\n");
lwsl_hexdump_notice(ci->ns.name, ci->ns.len);
}
break;
/* chunks of chunked content, with header removed */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
#if 0 /* enable to dump the html */
{
const char *p = in;
while (len--)
if (*p < 0x7f)
putchar(*p++);
else
putchar('.');
}
#endif
return 0; /* don't passthru */
/* uninterpreted http content */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
{
char buffer[1024 + LWS_PRE];
char *px = buffer + LWS_PRE;
int lenx = sizeof(buffer) - LWS_PRE;
if (lws_http_client_read(wsi, &px, &lenx) < 0)
return -1;
}
return 0; /* don't passthru */
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
client_wsi = NULL;
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static const struct lws_protocols protocols[] = {
{
"http",
callback_http,
0,
0,
},
{ NULL, NULL, 0, 0 }
};
static void
sigint_handler(int sig)
{
interrupted = 1;
}
static int findswitch(int argc, char **argv, const char *val)
{
while (--argc > 0) {
if (!strcmp(argv[argc], val))
return argc;
}
return 0;
}
int main(int argc, char **argv)
{
struct lws_context_creation_info info;
struct lws_client_connect_info i;
struct lws_context *context;
int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
/*
* For LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
*
* | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT |
* LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
*/ ;
int n = 0, m;
signal(SIGINT, sigint_handler);
/* you can set the log level on commandline with, eg, -d 15 */
m = findswitch(argc, argv, "-d");
if (m && m + 1 < argc)
logs = atoi(argv[m + 1]);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal http client\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
info.protocols = protocols;
#if defined(LWS_WITH_MBEDTLS)
/*
* OpenSSL uses the system trust store. mbedTLS has to be told which
* CA to trust explicitly.
*/
info.client_ssl_ca_filepath = "./warmcat.com.cer";
#endif
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
i.port = 443;
i.address = "warmcat.com";
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.ssl_connection = /* LCCSCF_NOT_H2 | */ LCCSCF_USE_SSL;
i.method = "GET";
i.protocol = protocols[0].name;
i.pwsi = &client_wsi;
lws_client_connect_via_info(&i);
while (n >= 0 && client_wsi && !interrupted)
n = lws_service(context, 1000);
lws_context_destroy(context);
lwsl_user("Completed\n");
return 0;
}

View file

@ -0,0 +1,92 @@
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
+AZxAeKCINT+b72x
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
pu/xO28QOG8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----