4e7ce6f42b
* Update deps * Change azure dep to match plugin
429 lines
14 KiB
Go
429 lines
14 KiB
Go
package duoapi
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
initialBackoffMS = 1000
|
|
maxBackoffMS = 32000
|
|
backoffFactor = 2
|
|
rateLimitHttpCode = 429
|
|
)
|
|
|
|
var spaceReplacer *strings.Replacer = strings.NewReplacer("+", "%20")
|
|
|
|
func canonParams(params url.Values) string {
|
|
// Values must be in sorted order
|
|
for key, val := range params {
|
|
sort.Strings(val)
|
|
params[key] = val
|
|
}
|
|
// Encode will place Keys in sorted order
|
|
ordered_params := params.Encode()
|
|
// Encoder turns spaces into +, but we need %XX escaping
|
|
return spaceReplacer.Replace(ordered_params)
|
|
}
|
|
|
|
func canonicalize(method string,
|
|
host string,
|
|
uri string,
|
|
params url.Values,
|
|
date string) string {
|
|
var canon [5]string
|
|
canon[0] = date
|
|
canon[1] = strings.ToUpper(method)
|
|
canon[2] = strings.ToLower(host)
|
|
canon[3] = uri
|
|
canon[4] = canonParams(params)
|
|
return strings.Join(canon[:], "\n")
|
|
}
|
|
|
|
func sign(ikey string,
|
|
skey string,
|
|
method string,
|
|
host string,
|
|
uri string,
|
|
date string,
|
|
params url.Values) string {
|
|
canon := canonicalize(method, host, uri, params, date)
|
|
mac := hmac.New(sha1.New, []byte(skey))
|
|
mac.Write([]byte(canon))
|
|
sig := hex.EncodeToString(mac.Sum(nil))
|
|
auth := ikey + ":" + sig
|
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
|
}
|
|
|
|
type DuoApi struct {
|
|
ikey string
|
|
skey string
|
|
host string
|
|
userAgent string
|
|
apiClient httpClient
|
|
authClient httpClient
|
|
sleepSvc sleepService
|
|
}
|
|
|
|
type httpClient interface {
|
|
Do(req *http.Request) (*http.Response, error)
|
|
}
|
|
type sleepService interface {
|
|
Sleep(duration time.Duration)
|
|
}
|
|
type timeSleepService struct{}
|
|
|
|
func (svc timeSleepService) Sleep(duration time.Duration) {
|
|
time.Sleep(duration + (time.Duration(rand.Intn(1000)) * time.Millisecond))
|
|
}
|
|
|
|
type apiOptions struct {
|
|
timeout time.Duration
|
|
insecure bool
|
|
proxy func(*http.Request) (*url.URL, error)
|
|
}
|
|
|
|
// Optional parameter for NewDuoApi, used to configure timeouts on API calls.
|
|
func SetTimeout(timeout time.Duration) func(*apiOptions) {
|
|
return func(opts *apiOptions) {
|
|
opts.timeout = timeout
|
|
return
|
|
}
|
|
}
|
|
|
|
// Optional parameter for testing only. Bypasses all TLS certificate validation.
|
|
func SetInsecure() func(*apiOptions) {
|
|
return func(opts *apiOptions) {
|
|
opts.insecure = true
|
|
}
|
|
}
|
|
|
|
// Optional parameter for NewDuoApi, used to configure an HTTP Connect proxy
|
|
// server for all outbound communications.
|
|
func SetProxy(proxy func(*http.Request) (*url.URL, error)) func(*apiOptions) {
|
|
return func(opts *apiOptions) {
|
|
opts.proxy = proxy
|
|
}
|
|
}
|
|
|
|
// Build an return a DuoApi struct.
|
|
// ikey is your Duo integration key
|
|
// skey is your Duo integration secret key
|
|
// host is your Duo host
|
|
// userAgent allows you to specify the user agent string used when making
|
|
// the web request to Duo.
|
|
// options are optional parameters. Use SetTimeout() to specify a timeout value
|
|
// for Rest API calls. Use SetProxy() to specify proxy settings for Duo API calls.
|
|
//
|
|
// Example: duoapi.NewDuoApi(ikey,skey,host,userAgent,duoapi.SetTimeout(10*time.Second))
|
|
func NewDuoApi(ikey string,
|
|
skey string,
|
|
host string,
|
|
userAgent string,
|
|
options ...func(*apiOptions)) *DuoApi {
|
|
opts := apiOptions{proxy: http.ProxyFromEnvironment}
|
|
for _, o := range options {
|
|
o(&opts)
|
|
}
|
|
|
|
// Certificate pinning
|
|
certPool := x509.NewCertPool()
|
|
certPool.AppendCertsFromPEM([]byte(duoPinnedCert))
|
|
|
|
tr := &http.Transport{
|
|
Proxy: opts.proxy,
|
|
TLSClientConfig: &tls.Config{
|
|
RootCAs: certPool,
|
|
InsecureSkipVerify: opts.insecure,
|
|
},
|
|
}
|
|
return &DuoApi{
|
|
ikey: ikey,
|
|
skey: skey,
|
|
host: host,
|
|
userAgent: userAgent,
|
|
apiClient: &http.Client{
|
|
Timeout: opts.timeout,
|
|
Transport: tr,
|
|
},
|
|
authClient: &http.Client{
|
|
Transport: tr,
|
|
},
|
|
sleepSvc: timeSleepService{},
|
|
}
|
|
}
|
|
|
|
type requestOptions struct {
|
|
timeout bool
|
|
}
|
|
|
|
type DuoApiOption func(*requestOptions)
|
|
|
|
// Pass to Request or SignedRequest to configure a timeout on the request
|
|
func UseTimeout(opts *requestOptions) {
|
|
opts.timeout = true
|
|
}
|
|
|
|
func (duoapi *DuoApi) buildOptions(options ...DuoApiOption) *requestOptions {
|
|
opts := &requestOptions{}
|
|
for _, o := range options {
|
|
o(opts)
|
|
}
|
|
return opts
|
|
}
|
|
|
|
// API calls will return a StatResult object. On success, Stat is 'OK'.
|
|
// On error, Stat is 'FAIL', and Code, Message, and Message_Detail
|
|
// contain error information.
|
|
type StatResult struct {
|
|
Stat string
|
|
Code *int32
|
|
Message *string
|
|
Message_Detail *string
|
|
}
|
|
|
|
// Make an unsigned Duo Rest API call. See Duo's online documentation
|
|
// for the available REST API's.
|
|
// method is POST or GET
|
|
// uri is the URI of the Duo Rest call
|
|
// params HTTP query parameters to include in the call.
|
|
// options Optional parameters. Use UseTimeout to toggle whether the
|
|
// Duo Rest API call should timeout or not.
|
|
//
|
|
// Example: duo.Call("GET", "/auth/v2/ping", nil, duoapi.UseTimeout)
|
|
func (duoapi *DuoApi) Call(method string,
|
|
uri string,
|
|
params url.Values,
|
|
options ...DuoApiOption) (*http.Response, []byte, error) {
|
|
|
|
url := url.URL{
|
|
Scheme: "https",
|
|
Host: duoapi.host,
|
|
Path: uri,
|
|
RawQuery: params.Encode(),
|
|
}
|
|
|
|
return duoapi.makeRetryableHttpCall(method, url, nil, nil, options...)
|
|
}
|
|
|
|
// Make a signed Duo Rest API call. See Duo's online documentation
|
|
// for the available REST API's.
|
|
// method is POST or GET
|
|
// uri is the URI of the Duo Rest call
|
|
// params HTTP query parameters to include in the call.
|
|
// options Optional parameters. Use UseTimeout to toggle whether the
|
|
// Duo Rest API call should timeout or not.
|
|
//
|
|
// Example: duo.SignedCall("GET", "/auth/v2/check", nil, duoapi.UseTimeout)
|
|
func (duoapi *DuoApi) SignedCall(method string,
|
|
uri string,
|
|
params url.Values,
|
|
options ...DuoApiOption) (*http.Response, []byte, error) {
|
|
|
|
now := time.Now().UTC().Format(time.RFC1123Z)
|
|
auth_sig := sign(duoapi.ikey, duoapi.skey, method, duoapi.host, uri, now, params)
|
|
|
|
url := url.URL{
|
|
Scheme: "https",
|
|
Host: duoapi.host,
|
|
Path: uri,
|
|
}
|
|
method = strings.ToUpper(method)
|
|
|
|
if method == "GET" {
|
|
url.RawQuery = params.Encode()
|
|
}
|
|
|
|
headers := make(map[string]string)
|
|
headers["Authorization"] = auth_sig
|
|
headers["Date"] = now
|
|
var requestBody io.ReadCloser = nil
|
|
if method == "POST" || method == "PUT" {
|
|
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
requestBody = ioutil.NopCloser(strings.NewReader(params.Encode()))
|
|
}
|
|
|
|
return duoapi.makeRetryableHttpCall(method, url, headers, requestBody, options...)
|
|
}
|
|
|
|
func (duoapi *DuoApi) makeRetryableHttpCall(
|
|
method string,
|
|
url url.URL,
|
|
headers map[string]string,
|
|
body io.ReadCloser,
|
|
options ...DuoApiOption) (*http.Response, []byte, error) {
|
|
|
|
opts := duoapi.buildOptions(options...)
|
|
|
|
client := duoapi.authClient
|
|
if opts.timeout {
|
|
client = duoapi.apiClient
|
|
}
|
|
|
|
backoffMs := initialBackoffMS
|
|
for {
|
|
request, err := http.NewRequest(method, url.String(), nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if headers != nil {
|
|
for k, v := range headers {
|
|
request.Header.Set(k, v)
|
|
}
|
|
}
|
|
if body != nil {
|
|
request.Body = body
|
|
}
|
|
|
|
resp, err := client.Do(request)
|
|
var body []byte
|
|
if err != nil {
|
|
return resp, body, err
|
|
}
|
|
|
|
if backoffMs > maxBackoffMS || resp.StatusCode != rateLimitHttpCode {
|
|
body, err = ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
return resp, body, err
|
|
}
|
|
|
|
duoapi.sleepSvc.Sleep(time.Millisecond * time.Duration(backoffMs))
|
|
backoffMs *= backoffFactor
|
|
}
|
|
}
|
|
|
|
const duoPinnedCert string = `
|
|
subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
|
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
|
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
|
|
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
|
|
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
|
|
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
|
|
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
|
|
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
|
|
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
|
|
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
|
|
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
|
|
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
|
|
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
|
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
|
|
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
|
|
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
|
|
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
|
|
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
|
|
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
|
|
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
|
|
-----END CERTIFICATE-----
|
|
|
|
subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
|
|
-----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-----
|
|
|
|
subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
|
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
|
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
|
|
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
|
|
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
|
|
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
|
|
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
|
|
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
|
|
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
|
|
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
|
|
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
|
|
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
|
|
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
|
|
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
|
|
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
|
|
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
|
|
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
|
|
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
|
|
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
|
|
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
|
|
+OkuE6N36B9K
|
|
-----END CERTIFICATE-----
|
|
|
|
subject= /C=US/O=SecureTrust Corporation/CN=SecureTrust CA
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
|
|
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
|
|
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
|
|
MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
|
|
cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
|
|
AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
|
|
Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
|
|
0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
|
|
wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
|
|
7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
|
|
8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
|
|
BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
|
|
/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
|
|
JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
|
|
NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
|
|
6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
|
|
3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
|
|
D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
|
|
CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
|
|
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
|
|
-----END CERTIFICATE-----
|
|
|
|
subject= /C=US/O=SecureTrust Corporation/CN=Secure Global CA
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
|
|
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
|
|
GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
|
|
MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
|
|
Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
|
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
|
|
iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
|
|
/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
|
|
jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
|
|
HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
|
|
sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
|
|
gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
|
|
MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
|
|
KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
|
|
AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
|
|
URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
|
|
H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
|
|
I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
|
|
iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
|
|
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
|
|
-----END CERTIFICATE-----`
|