diff --git a/go.mod b/go.mod index 02747f7..10e372c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f + golang.org/x/net v0.17.0 ) -require golang.org/x/text v0.3.6 // indirect +require golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index 26a026b..d6484e9 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,43 @@ github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b h1:NGgE5ELokSf2tZ/bydyDUKrvd/jP8lrAoPNeBuMOTOk= github.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b/go.mod h1:zT/uzhdQGTqlwTq7Lpbj3JoJQWfPfIJ1tE0OidAmih8= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index f312b6a..d1cc437 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,8 @@ func tlsVersionToString(tlsVersion uint16) string { func cipherSuiteToString(cipherSuite uint16) string { switch cipherSuite { + + // TLS 1.0 - 1.2 cipher suites. case tls.TLS_RSA_WITH_RC4_128_SHA: return "TLS_RSA_WITH_RC4_128_SHA" case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: @@ -77,12 +79,17 @@ func cipherSuiteToString(cipherSuite uint16) string { return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" + + // TLS 1.3 cipher suites. case tls.TLS_AES_128_GCM_SHA256: return "TLS_AES_128_GCM_SHA256" case tls.TLS_AES_256_GCM_SHA384: return "TLS_AES_256_GCM_SHA384" case tls.TLS_CHACHA20_POLY1305_SHA256: return "TLS_CHACHA20_POLY1305_SHA256" + + // TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator + // that the client is doing version fallback. See RFC 7507. case tls.TLS_FALLBACK_SCSV: return "TLS_FALLBACK_SCSV" default: @@ -179,8 +186,8 @@ var defaultTlsKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n" + "-----END RSA PRIVATE KEY-----\n" var defaultTlsCertPEM = "-----BEGIN CERTIFICATE-----\n" + - "MIIFSTCCAzGgAwIBAgIIYKkL+dNc+VwwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE\n" + - "AwwMRGVtb19Sb290X0NBMB4XDTIxMTIyMDIxMzIwMFoXDTIyMTIyMDIxMzIwMFow\n" + + "MIIFSTCCAzGgAwIBAgIIBXrCrEwd+PswDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE\n" + + "AwwMRGVtb19Sb290X0NBMB4XDTIzMTAyNzE5MjkwMFoXDTI0MTAyNzE5MjkwMFow\n" + "FDESMBAGA1UEAxMJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n" + "CgKCAgEAsM0gklnSFVwV87ylxqeObB60xYdoRKh+vIxvzID3/VuiJYWzSbw8RrGC\n" + "siQA926nAjYZWtfJsN+7S4N5eCTj8ofysjMmfPbZp4S4aXZA9M36E3DMHOjJmFW4\n" + @@ -196,18 +203,18 @@ var defaultTlsCertPEM = "-----BEGIN CERTIFICATE-----\n" + "mDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSs/H3eu/eWzu+CMQVKGvxrfNTdSjAL\n" + "BgNVHQ8EBAMCA+gwEwYDVR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9j\n" + "YWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNhIGNl\n" + - "cnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQAS1r6Dh3DoMy1Nq/ChpFI3JnI9\n" + - "8Hg+JT9eADyA7TUceC1gjYkaZ3/6FiYt9er8lwj49griCz5NWc//MvUWizQKCnpp\n" + - "mBYG1PfGvMx5t9NlCJQRyF0M/mqL0qeiR3hccOxWUhYjszz6UEzjDZFmyOcn8kVq\n" + - "uq2dwc+7V5S20NnjbF3LK4JrV50+L+HPwM3qT2yuCVhAnlnVq1MVhoOsvKx97QvL\n" + - "28yXonPIA1Z3nPcBytFgofKiMC8y0Rn3uteNoqPLGFt8+dYtMgXDnI7vxDwXgnqh\n" + - "ibfclTpSaTOAosXhTGSB2w1vZvtLyrpoE+tJAoOcSTvL5KVX1/dcISb3wuvK5VCf\n" + - "8qvkgA/Fc9zhqBVlkZPoJnNfwI9qgzcflwjcYar8QyZg/pb3N8T/BwfZPWQ/83j4\n" + - "qmP7Yz0oNvuIZpoN/Yi4eBdub7KTsu4OfVgcBPaRL+iH4DvPZNboHu44JkfEw2PA\n" + - "0ZY7ZkUxgJvB1F+FezRE1jQe8PPlC/KNH/S2e/h2Zd+xXnZ25IG20lLdvS/ax6zm\n" + - "m9U0NXIlzL/1akuSugL90YLcnnaIhz4LHyjOKOXuIxWlqPGtvPASCAbPj5dfZSWt\n" + - "F4ahnoXyMwMwLfLUF1sQHZ3tUlx3w4+8M3Hj336U32ldHskWJU1ME0k8NQDqSl2W\n" + - "aUpT228IkSGeJdmSjQ==\n" + + "cnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQA5NiIxXuXXpWh08w8kwJiEN+Ft\n" + + "M4zR1jZYXa799nKYqrrF3dzFeEn8PSh6Rp5D6l5timlyOgIA7Uk3aLgPWwuCEO+N\n" + + "hT7O2BGZEpcdwOh91U4zGCLcuWGzz0VvBEYs9VMucyuGF7/JOicLEfl5lUFruj8h\n" + + "t/HFz2F6dPdFs5+FX81/5lPoidZffxYYDeEBdAGq9Slc2LDGvPM/CxME7WsvvVN4\n" + + "VdUfwW6jIwrlNSevGV+bf9F7o8B03M02c+rlZdMbcXzyh4HCZ3RK5r6wzBbOJPAl\n" + + "TUiXi9GolFQTLTVUzHbhT+Ztqyn3JJEWidNxWEY3GhP3KMopVOQSZCDuLQBaAG5n\n" + + "UU9NSEohuFLx+qKn5+nkl5YppbV1TkkN3Xm06rq8hezCU7/AeAMisrFl6ODA3VSa\n" + + "X0xsG++xbxpyJyAbFOPNruOUGG6S5XIQ6evpRNHb5h7LVWqTvr9gu7H4gNnsHn+F\n" + + "w3yvPdXftpB8bQBf3RMwLHhZjvly/xRdJQ4JFdgHycKRSCK5lc2ryp5NcdbOSkfP\n" + + "fV0CCfJ+wpeZgdqc6uvXzdwbYBd3slBl23WhSLLgWBfK7+n/KKrTauP6b110h6qH\n" + + "+LEENKgqS6xvEitdnSVp7qPhREpTUIea1bVS6fweCdGGLWweUtBNRG5wULVrDa+c\n" + + "HDpCHCwQXsFz82P3KA==\n" + "-----END CERTIFICATE-----\n" func main() { @@ -270,6 +277,18 @@ func main() { MinVersion: tlsVersion, MaxVersion: tlsVersion, } + + // Server configurations must set one of config.Certificates, config.GetCertificate or + // config.GetConfigForClient. + // We chose to set config.GetCertificate. + // + // GetCertificate returns a Certificate based on the given + // ClientHelloInfo. It will only be called if the client supplies SNI + // information or if Certificates is empty. + // + // If GetCertificate is nil or returns nil, then the certificate is + // retrieved from NameToCertificate. If NameToCertificate is nil, the + // best element of Certificates will be used. config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { fmt.Printf("\n======== ClientHello ========\n") @@ -283,15 +302,18 @@ func main() { return &cert, nil } - config.VerifyConnection = func(state tls.ConnectionState) error { - fmt.Printf("\n======== VerifyConnection ========\n") - fmt.Printf("HandshakeComplete : %v\n", state.HandshakeComplete) - fmt.Printf("Version : %s(%v)\n", tlsVersionToString(state.Version), state.Version) - fmt.Printf("CipherSuite : %s(%v)\n\n", cipherSuiteToString(state.CipherSuite), state.CipherSuite) - - return nil - } + // VerifyPeerCertificate, if not nil, is called after normal + // certificate verification by either a TLS client or server. It + // receives the raw ASN.1 certificates provided by the peer and also + // any verified chains that normal processing found. If it returns a + // non-nil error, the handshake is aborted and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. If normal verification is disabled by + // setting InsecureSkipVerify, or (for a server) when ClientAuth is + // RequestClientCert or RequireAnyClientCert, then this callback will + // be considered but the verifiedChains argument will always be nil. config.VerifyPeerCertificate = func(certificates [][]byte, _ [][]*x509.Certificate) error { fmt.Printf("\n======== VerifyPeerCertificate ========\n") @@ -315,6 +337,27 @@ func main() { return nil } + // VerifyConnection, if not nil, is called after normal certificate + // verification and after VerifyPeerCertificate by either a TLS client + // or server. If it returns a non-nil error, the handshake is aborted + // and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. This callback will run for all connections + // regardless of InsecureSkipVerify or ClientAuth settings. + config.VerifyConnection = func(state tls.ConnectionState) error { + fmt.Printf("\n======== VerifyConnection ========\n") + + fmt.Printf("Version : %s(%v)\n", tlsVersionToString(state.Version), state.Version) + fmt.Printf("HandshakeComplete : %v\n", state.HandshakeComplete) + fmt.Printf("DidResume : %v\n", state.DidResume) + fmt.Printf("CipherSuite : %s(%v)\n", cipherSuiteToString(state.CipherSuite), state.CipherSuite) + fmt.Printf("NegotiatedProtocol : %v\n", state.NegotiatedProtocol) + fmt.Printf("ServerName : %v\n\n", state.ServerName) + + return nil + } + address := "localhost:" + fmt.Sprintf("%d", *serverPort) server := &http.Server{ Addr: address, @@ -344,6 +387,7 @@ func main() { http2.ConfigureServer(server, nil) http.HandleFunc("/", handler) + fmt.Printf("INFO: Server is running at https://localhost:%v\n", *serverPort) fmt.Printf("INFO: Server listening...\n") server.ListenAndServeTLS("", "") } @@ -356,15 +400,23 @@ func handler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Connected!\n\n") io.WriteString(w, "=========== TLS Connection State ===========\n") - io.WriteString(w, "HandshakeComplete : "+fmt.Sprintf("%v", state.HandshakeComplete)+"\n") - io.WriteString(w, "Version : "+tlsVersionToString(state.Version)+"("+fmt.Sprintf("%v", state.Version)+")\n") - io.WriteString(w, "CipherSuite : "+cipherSuiteToString(state.CipherSuite)+"("+fmt.Sprintf("%v", state.CipherSuite)+")\n") - io.WriteString(w, "NegotiatedProtocol : "+state.NegotiatedProtocol+"\n\n") + io.WriteString(w, "Version : "+tlsVersionToString(state.Version)+"("+fmt.Sprintf("%v", state.Version)+")\n") + io.WriteString(w, "HandshakeComplete : "+fmt.Sprintf("%v", state.HandshakeComplete)+"\n") + io.WriteString(w, "DidResume : "+fmt.Sprintf("%v", state.DidResume)+"\n") + io.WriteString(w, "CipherSuite : "+cipherSuiteToString(state.CipherSuite)+"("+fmt.Sprintf("%v", state.CipherSuite)+")\n") + io.WriteString(w, "NegotiatedProtocol : "+state.NegotiatedProtocol+"\n") + io.WriteString(w, "ServerName : "+state.ServerName+"\n\n") io.WriteString(w, "=========== Request State ===========\n") - io.WriteString(w, "Protocol : "+r.Proto+"\n") - io.WriteString(w, "Remote : "+r.RemoteAddr+"\n") - io.WriteString(w, "RequestURI : "+r.RequestURI+"\n\n") + io.WriteString(w, "Method : "+r.Method+"\n") + io.WriteString(w, "Protocol : "+r.Proto+"\n") + io.WriteString(w, "Header : "+fmt.Sprintf("%v", r.Header)+"\n") + io.WriteString(w, "Host : "+r.Host+"\n") + io.WriteString(w, "Form : "+fmt.Sprintf("%v", r.Form)+"\n") + io.WriteString(w, "PostForm : "+fmt.Sprintf("%v", r.PostForm)+"\n") + io.WriteString(w, "Trailer : "+fmt.Sprintf("%v", r.Trailer)+"\n") + io.WriteString(w, "RemoteAddr : "+r.RemoteAddr+"\n") + io.WriteString(w, "RequestURI : "+r.RequestURI+"\n\n") io.WriteString(w, "=========== Peer Certificate ===========\n") if len(state.PeerCertificates) > 0 { diff --git a/tls_server_cert.pem b/tls_server_cert.pem index 876196f..a4365a4 100644 --- a/tls_server_cert.pem +++ b/tls_server_cert.pem @@ -1,6 +1,6 @@ -----BEGIN CERTIFICATE----- -MIIFSTCCAzGgAwIBAgIIYKkL+dNc+VwwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE -AwwMRGVtb19Sb290X0NBMB4XDTIxMTIyMDIxMzIwMFoXDTIyMTIyMDIxMzIwMFow +MIIFSTCCAzGgAwIBAgIIBXrCrEwd+PswDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE +AwwMRGVtb19Sb290X0NBMB4XDTIzMTAyNzE5MjkwMFoXDTI0MTAyNzE5MjkwMFow FDESMBAGA1UEAxMJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC CgKCAgEAsM0gklnSFVwV87ylxqeObB60xYdoRKh+vIxvzID3/VuiJYWzSbw8RrGC siQA926nAjYZWtfJsN+7S4N5eCTj8ofysjMmfPbZp4S4aXZA9M36E3DMHOjJmFW4 @@ -16,16 +16,16 @@ Y3RIPlrsuhSwe6V6QYpVH5gY1ZXsJ5Io3IDaN1nsK5qZ0dy9ijaAYJbymzfL5DwR mDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSs/H3eu/eWzu+CMQVKGvxrfNTdSjAL BgNVHQ8EBAMCA+gwEwYDVR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9j YWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNhIGNl -cnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQAS1r6Dh3DoMy1Nq/ChpFI3JnI9 -8Hg+JT9eADyA7TUceC1gjYkaZ3/6FiYt9er8lwj49griCz5NWc//MvUWizQKCnpp -mBYG1PfGvMx5t9NlCJQRyF0M/mqL0qeiR3hccOxWUhYjszz6UEzjDZFmyOcn8kVq -uq2dwc+7V5S20NnjbF3LK4JrV50+L+HPwM3qT2yuCVhAnlnVq1MVhoOsvKx97QvL -28yXonPIA1Z3nPcBytFgofKiMC8y0Rn3uteNoqPLGFt8+dYtMgXDnI7vxDwXgnqh -ibfclTpSaTOAosXhTGSB2w1vZvtLyrpoE+tJAoOcSTvL5KVX1/dcISb3wuvK5VCf -8qvkgA/Fc9zhqBVlkZPoJnNfwI9qgzcflwjcYar8QyZg/pb3N8T/BwfZPWQ/83j4 -qmP7Yz0oNvuIZpoN/Yi4eBdub7KTsu4OfVgcBPaRL+iH4DvPZNboHu44JkfEw2PA -0ZY7ZkUxgJvB1F+FezRE1jQe8PPlC/KNH/S2e/h2Zd+xXnZ25IG20lLdvS/ax6zm -m9U0NXIlzL/1akuSugL90YLcnnaIhz4LHyjOKOXuIxWlqPGtvPASCAbPj5dfZSWt -F4ahnoXyMwMwLfLUF1sQHZ3tUlx3w4+8M3Hj336U32ldHskWJU1ME0k8NQDqSl2W -aUpT228IkSGeJdmSjQ== +cnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQA5NiIxXuXXpWh08w8kwJiEN+Ft +M4zR1jZYXa799nKYqrrF3dzFeEn8PSh6Rp5D6l5timlyOgIA7Uk3aLgPWwuCEO+N +hT7O2BGZEpcdwOh91U4zGCLcuWGzz0VvBEYs9VMucyuGF7/JOicLEfl5lUFruj8h +t/HFz2F6dPdFs5+FX81/5lPoidZffxYYDeEBdAGq9Slc2LDGvPM/CxME7WsvvVN4 +VdUfwW6jIwrlNSevGV+bf9F7o8B03M02c+rlZdMbcXzyh4HCZ3RK5r6wzBbOJPAl +TUiXi9GolFQTLTVUzHbhT+Ztqyn3JJEWidNxWEY3GhP3KMopVOQSZCDuLQBaAG5n +UU9NSEohuFLx+qKn5+nkl5YppbV1TkkN3Xm06rq8hezCU7/AeAMisrFl6ODA3VSa +X0xsG++xbxpyJyAbFOPNruOUGG6S5XIQ6evpRNHb5h7LVWqTvr9gu7H4gNnsHn+F +w3yvPdXftpB8bQBf3RMwLHhZjvly/xRdJQ4JFdgHycKRSCK5lc2ryp5NcdbOSkfP +fV0CCfJ+wpeZgdqc6uvXzdwbYBd3slBl23WhSLLgWBfK7+n/KKrTauP6b110h6qH ++LEENKgqS6xvEitdnSVp7qPhREpTUIea1bVS6fweCdGGLWweUtBNRG5wULVrDa+c +HDpCHCwQXsFz82P3KA== -----END CERTIFICATE-----