"tlsauth" contrib package
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This package adds authentication middlewares via TLS client certificates.
This commit is contained in:
parent
a1c186878d
commit
8229f31f70
|
@ -177,6 +177,7 @@ func prepareCGIEnv(
|
|||
environ = append(
|
||||
environ,
|
||||
"TLS_CLIENT_HASH="+fingerprint(cert.Raw),
|
||||
"TLS_CLIENT_CERT="+hex.EncodeToString(cert.Raw),
|
||||
"TLS_CLIENT_ISSUER="+cert.Issuer.String(),
|
||||
"TLS_CLIENT_ISSUER_CN="+cert.Issuer.CommonName,
|
||||
"TLS_CLIENT_SUBJECT="+cert.Subject.String(),
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package tlsauth
|
||||
|
||||
import "crypto/x509"
|
||||
|
||||
// Approver is a function that validates a certificate.
|
||||
//
|
||||
// It should not be have to handle a nil argument.
|
||||
type Approver func(*x509.Certificate) bool
|
||||
|
||||
// RequireSpecificIdentity builds an approver that demands one specific client certificate.
|
||||
func RequireSpecificIdentity(identity *x509.Certificate) Approver { return identity.Equal }
|
||||
|
||||
// Allow is an approver which permits anything.
|
||||
func Allow(_ *x509.Certificate) bool { return true }
|
||||
|
||||
// Reject is an approver which denies everything.
|
||||
func Reject(_ *x509.Certificate) bool { return false }
|
|
@ -0,0 +1,47 @@
|
|||
package tlsauth_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"tildegit.org/tjp/gus/contrib/tlsauth"
|
||||
)
|
||||
|
||||
func TestRequireSpecificIdentity(t *testing.T) {
|
||||
cert1, err := leafCert("testdata/client1.crt", "testdata/client1.key")
|
||||
assert.Nil(t, err)
|
||||
|
||||
cert2, err := leafCert("testdata/client2.crt", "testdata/client2.key")
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.True(t, cert1.Equal(cert1))
|
||||
assert.False(t, cert1.Equal(cert2))
|
||||
assert.False(t, cert2.Equal(cert1))
|
||||
assert.True(t, cert2.Equal(cert2))
|
||||
|
||||
assert.True(t, tlsauth.RequireSpecificIdentity(cert1)(cert1))
|
||||
assert.False(t, tlsauth.RequireSpecificIdentity(cert1)(cert2))
|
||||
assert.False(t, tlsauth.RequireSpecificIdentity(cert2)(cert1))
|
||||
assert.True(t, tlsauth.RequireSpecificIdentity(cert2)(cert2))
|
||||
}
|
||||
|
||||
func leafCert(certfile, keyfile string) (*x509.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certfile, keyfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cert.Leaf != nil {
|
||||
return cert.Leaf, nil
|
||||
}
|
||||
|
||||
if len(cert.Certificate) == 0 {
|
||||
return nil, errors.New("no certificate blocks found")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(cert.Certificate[0])
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package tlsauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
|
||||
"tildegit.org/tjp/gus"
|
||||
)
|
||||
|
||||
// Identity returns the client certificate for the request or nil if there is none.
|
||||
func Identity(request *gus.Request) *x509.Certificate {
|
||||
if request.TLSState == nil || len(request.TLSState.PeerCertificates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return request.TLSState.PeerCertificates[0]
|
||||
}
|
||||
|
||||
// RequiredAuth produces an auth predicate.
|
||||
//
|
||||
// The check requires both that there is a client certificate associated with the
|
||||
// request and that it passes the provided approver.
|
||||
func RequiredAuth(approve Approver) func(context.Context, *gus.Request) bool {
|
||||
return func(_ context.Context, request *gus.Request) bool {
|
||||
identity := Identity(request)
|
||||
if identity == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return approve(identity)
|
||||
}
|
||||
}
|
||||
|
||||
// OptionalAuth produces an auth predicate.
|
||||
//
|
||||
// The check allows through any request with no client certificate, but if
|
||||
// there is one present then it requires that it pass the provided approver.
|
||||
func OptionalAuth(approve Approver) func(context.Context, *gus.Request) bool {
|
||||
return func(_ context.Context, request *gus.Request) bool {
|
||||
identity := Identity(request)
|
||||
if identity == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return approve(identity)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package tlsauth_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"tildegit.org/tjp/gus"
|
||||
"tildegit.org/tjp/gus/contrib/tlsauth"
|
||||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
func TestIdentify(t *testing.T) {
|
||||
invoked := false
|
||||
|
||||
var leafCert *x509.Certificate
|
||||
server, client, clientCert := setup(t,
|
||||
"testdata/server.crt", "testdata/server.key",
|
||||
"testdata/client1.crt", "testdata/client1.key",
|
||||
func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
invoked = true
|
||||
|
||||
ident := tlsauth.Identity(request)
|
||||
if assert.NotNil(t, ident) {
|
||||
assert.True(t, ident.Equal(leafCert))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
leafCert, err := x509.ParseCertificate(clientCert.Certificate[0])
|
||||
require.Nil(t, err)
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
requestPath(t, client, server, "/")
|
||||
assert.True(t, invoked)
|
||||
}
|
||||
|
||||
func TestRequiredAuth(t *testing.T) {
|
||||
invoked1 := false
|
||||
invoked2 := false
|
||||
|
||||
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
invoked1 = true
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
|
||||
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
invoked2 = true
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
|
||||
authMiddleware := gus.Filter(tlsauth.RequiredAuth(tlsauth.Allow), nil)
|
||||
|
||||
handler1 = gus.Filter(
|
||||
func(_ context.Context, req *gus.Request) bool {
|
||||
return strings.HasPrefix(req.Path, "/one")
|
||||
},
|
||||
nil,
|
||||
)(authMiddleware(handler1))
|
||||
handler2 = authMiddleware(handler2)
|
||||
|
||||
server, client, _ := setup(t,
|
||||
"testdata/server.crt", "testdata/server.key",
|
||||
"testdata/client1.crt", "testdata/client1.key",
|
||||
gus.FallthroughHandler(handler1, handler2),
|
||||
)
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
requestPath(t, client, server, "/one")
|
||||
assert.True(t, invoked1)
|
||||
|
||||
client, _ = clientFor(t, server, "", "") // no client cert this time
|
||||
requestPath(t, client, server, "/two")
|
||||
assert.False(t, invoked2)
|
||||
}
|
||||
|
||||
func TestOptionalAuth(t *testing.T) {
|
||||
invoked1 := false
|
||||
invoked2 := false
|
||||
|
||||
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
if !strings.HasPrefix(request.Path, "/one") {
|
||||
return nil
|
||||
}
|
||||
|
||||
invoked1 = true
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
|
||||
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
invoked2 = true
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
|
||||
mw := gus.Filter(tlsauth.OptionalAuth(tlsauth.Reject), nil)
|
||||
handler := gus.FallthroughHandler(mw(handler1), mw(handler2))
|
||||
|
||||
server, client, _ := setup(t,
|
||||
"testdata/server.crt", "testdata/server.key",
|
||||
"testdata/client1.crt", "testdata/client1.key",
|
||||
handler,
|
||||
)
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
requestPath(t, client, server, "/one")
|
||||
assert.False(t, invoked1)
|
||||
|
||||
client, _ = clientFor(t, server, "", "")
|
||||
requestPath(t, client, server, "/two")
|
||||
assert.True(t, invoked2)
|
||||
}
|
||||
|
||||
func setup(
|
||||
t *testing.T,
|
||||
serverCertPath string,
|
||||
serverKeyPath string,
|
||||
clientCertPath string,
|
||||
clientKeyPath string,
|
||||
handler gus.Handler,
|
||||
) (gus.Server, gemini.Client, tls.Certificate) {
|
||||
serverTLS, err := gemini.FileTLS(serverCertPath, serverKeyPath)
|
||||
require.Nil(t, err)
|
||||
|
||||
server, err := gemini.NewServer(
|
||||
context.Background(),
|
||||
serverTLS,
|
||||
"tcp",
|
||||
"127.0.0.1:0",
|
||||
handler,
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
client, clientCert := clientFor(t, server, clientCertPath, clientKeyPath)
|
||||
|
||||
return server, client, clientCert
|
||||
}
|
||||
|
||||
func clientFor(
|
||||
t *testing.T,
|
||||
server gus.Server,
|
||||
certPath string,
|
||||
keyPath string,
|
||||
) (gemini.Client, tls.Certificate) {
|
||||
var clientCert tls.Certificate
|
||||
var certs []tls.Certificate
|
||||
if certPath != "" {
|
||||
c, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
require.Nil(t, err)
|
||||
|
||||
clientCert = c
|
||||
certs = []tls.Certificate{c}
|
||||
}
|
||||
|
||||
return gemini.NewClient(&tls.Config{
|
||||
Certificates: certs,
|
||||
InsecureSkipVerify: true,
|
||||
}), clientCert
|
||||
}
|
||||
|
||||
func requestPath(t *testing.T, client gemini.Client, server gus.Server, path string) *gus.Response {
|
||||
u, err := url.Parse("gemini://" + server.Address() + path)
|
||||
require.Nil(t, err)
|
||||
|
||||
response, err := client.RoundTrip(&gus.Request{URL: u})
|
||||
require.Nil(t, err)
|
||||
|
||||
return response
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package tlsauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tildegit.org/tjp/gus"
|
||||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
// GeminiAuth builds an authentication middleware from approval criteria.
|
||||
//
|
||||
// If a request does not contain a client certificate it will be rejected
|
||||
// with a "60 certificate required" response. If the client identity does
|
||||
// not pass the approver it will be rejected with "62 certificate invalid".
|
||||
func GeminiAuth(approver Approver) gus.Middleware {
|
||||
return func(inner gus.Handler) gus.Handler {
|
||||
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
||||
identity := Identity(request)
|
||||
if identity == nil {
|
||||
return geminiMissingCert(ctx, request)
|
||||
}
|
||||
if !approver(identity) {
|
||||
return geminiCertNotAuthorized(ctx, request)
|
||||
}
|
||||
|
||||
return inner(ctx, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GeminiOptionalAuth builds auth middleware which doesn't require an identity.
|
||||
//
|
||||
// If there is no client certificate the request will pass through the middleware.
|
||||
// It will only be rejected with "62 certificate invalid" if there *is* a client
|
||||
// certificate, but it fails the approval.
|
||||
func GeminiOptionalAuth(approver Approver) gus.Middleware {
|
||||
return func(inner gus.Handler) gus.Handler {
|
||||
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
||||
identity := Identity(request)
|
||||
if identity != nil && !approver(identity) {
|
||||
return geminiCertNotAuthorized(ctx, request)
|
||||
}
|
||||
|
||||
return inner(ctx, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GeminiRequireCertificate is a middleware that only requires a client certificate.
|
||||
var GeminiRequireCertificate = GeminiAuth(Allow)
|
||||
|
||||
func geminiMissingCert(_ context.Context, _ *gus.Request) *gus.Response {
|
||||
return gemini.RequireCert("A client certificate is required.")
|
||||
}
|
||||
|
||||
func geminiCertNotAuthorized(_ context.Context, _ *gus.Request) *gus.Response {
|
||||
return gemini.CertAuthFailure("Client certificate not authorized.")
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package tlsauth_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"tildegit.org/tjp/gus"
|
||||
"tildegit.org/tjp/gus/contrib/tlsauth"
|
||||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
func TestGeminiAuth(t *testing.T) {
|
||||
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
if !strings.HasPrefix(request.Path, "/one") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
if !strings.HasPrefix(request.Path, "/two") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
handler3 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
if !strings.HasPrefix(request.Path, "/three") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
handler4 := func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
|
||||
handler := gus.FallthroughHandler(
|
||||
tlsauth.GeminiAuth(tlsauth.Allow)(handler1),
|
||||
tlsauth.GeminiAuth(tlsauth.Allow)(handler2),
|
||||
tlsauth.GeminiAuth(tlsauth.Reject)(handler3),
|
||||
tlsauth.GeminiAuth(tlsauth.Reject)(handler4),
|
||||
)
|
||||
|
||||
server, authClient, _ := setup(t,
|
||||
"testdata/server.crt", "testdata/server.key",
|
||||
"testdata/client1.crt", "testdata/client1.key",
|
||||
handler,
|
||||
)
|
||||
|
||||
authlessClient, _ := clientFor(t, server, "", "")
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
resp := requestPath(t, authClient, server, "/one")
|
||||
assert.Equal(t, gemini.StatusSuccess, resp.Status)
|
||||
|
||||
resp = requestPath(t, authlessClient, server, "/two")
|
||||
assert.Equal(t, gemini.StatusClientCertificateRequired, resp.Status)
|
||||
|
||||
resp = requestPath(t, authClient, server, "/three")
|
||||
assert.Equal(t, gemini.StatusCertificateNotAuthorized, resp.Status)
|
||||
|
||||
resp = requestPath(t, authlessClient, server, "/four")
|
||||
assert.Equal(t, gemini.StatusClientCertificateRequired, resp.Status)
|
||||
}
|
||||
|
||||
func TestGeminiOptionalAuth(t *testing.T) {
|
||||
pathHandler := func(path string) gus.Handler {
|
||||
return func(_ context.Context, request *gus.Request) *gus.Response {
|
||||
if !strings.HasPrefix(request.Path, path) {
|
||||
return nil
|
||||
}
|
||||
return gemini.Success("", &bytes.Buffer{})
|
||||
}
|
||||
}
|
||||
|
||||
handler := gus.FallthroughHandler(
|
||||
tlsauth.GeminiOptionalAuth(tlsauth.Allow)(pathHandler("/one")),
|
||||
tlsauth.GeminiOptionalAuth(tlsauth.Allow)(pathHandler("/two")),
|
||||
tlsauth.GeminiOptionalAuth(tlsauth.Reject)(pathHandler("/three")),
|
||||
tlsauth.GeminiOptionalAuth(tlsauth.Reject)(pathHandler("/four")),
|
||||
)
|
||||
|
||||
server, authClient, _ := setup(t,
|
||||
"testdata/server.crt", "testdata/server.key",
|
||||
"testdata/client1.crt", "testdata/client1.key",
|
||||
handler,
|
||||
)
|
||||
authlessClient, _ := clientFor(t, server, "", "")
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
resp := requestPath(t, authClient, server, "/one")
|
||||
assert.Equal(t, gemini.StatusSuccess, resp.Status)
|
||||
|
||||
resp = requestPath(t, authlessClient, server, "/two")
|
||||
assert.Equal(t, gemini.StatusSuccess, resp.Status)
|
||||
|
||||
resp = requestPath(t, authClient, server, "/three")
|
||||
assert.Equal(t, gemini.StatusCertificateNotAuthorized, resp.Status)
|
||||
|
||||
resp = requestPath(t, authlessClient, server, "/four")
|
||||
assert.Equal(t, gemini.StatusSuccess, resp.Status)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICnDCCAYQCAQAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJdGVzdCB1c2Vy
|
||||
MB4XDTIzMDEyMDAyMDkwNloXDTMzMDExOTA3MDAwMFowFDESMBAGA1UEAwwJdGVz
|
||||
dCB1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Ar3h421bfzG
|
||||
DNvnZBWx63frF1ym93FqWaXA0qVTJYjnLcraTvNCzVt3zi8A9xgIQMy57Yy/QkQf
|
||||
04TNj6oYPD7HumH1oblLsFIHk6ODtM5hv5RrGrWpKKS/SSb2HaKI+bjXaZCfYHNc
|
||||
5ZKtH91c0wlRUYW8A4Cov6qydqv8jzWPYb3gIFsbkOLfdiZUdTFNg3hPF9mRNWv2
|
||||
OlAR7bkyrKCIcsYdOxDxmnZOZqJD9m92n/k/teb4yQnK1BY08HLzIUyb1jWw71ak
|
||||
5keHvmGmxqvu8W6B5PJBrQwnfAqFnxGLbNZMr6lGHCJvjQiI2P6T39jb2LqOmOND
|
||||
0zBFGJsLAwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAvyDck+fSTvALbf9AGpn9n
|
||||
dIaAcKB/o+a9IHAex7ZNywoh1i41gnahuFSnjFbkruchtzG0blpt5/jcIU0Eqg2u
|
||||
hR8ILLavVxdYSitpE/FTpmUQ70rieOy6kSp60P6fi7pRC47pnlPb9WOBtMvjCR5M
|
||||
ATMClARo9m2tQVEh1k6ZFkgsPfoYCWxfPgBpCGMzFnXQNWiWN6DuB0xUVOugNncH
|
||||
OiIDDOpFQuq9ZP/w9TVjgVHKJdet50F4moXdqXfeAitkKOIAJozCNwq0ayMfQDS5
|
||||
rRMUmlNUJA3qFZdmuyXSC/N+Rj/aQVcVFY1Y2KNlKfcArFgpDwUTPDfsoC9cBZBA
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcCveHjbVt/MYM
|
||||
2+dkFbHrd+sXXKb3cWpZpcDSpVMliOctytpO80LNW3fOLwD3GAhAzLntjL9CRB/T
|
||||
hM2Pqhg8Pse6YfWhuUuwUgeTo4O0zmG/lGsatakopL9JJvYdooj5uNdpkJ9gc1zl
|
||||
kq0f3VzTCVFRhbwDgKi/qrJ2q/yPNY9hveAgWxuQ4t92JlR1MU2DeE8X2ZE1a/Y6
|
||||
UBHtuTKsoIhyxh07EPGadk5mokP2b3af+T+15vjJCcrUFjTwcvMhTJvWNbDvVqTm
|
||||
R4e+YabGq+7xboHk8kGtDCd8CoWfEYts1kyvqUYcIm+NCIjY/pPf2NvYuo6Y40PT
|
||||
MEUYmwsDAgMBAAECggEABUqeOTxHKKXzfUusfNOou6jelmk7+qdXj2BVCru/DCAG
|
||||
rys5pLxk1ttkPikTNN33FNfXgMbpsoZA3a1L5DCK9Kft1aWVapYyI8NVO0+rUyXD
|
||||
ZAAFs1a7AqczkmbFdGD8OkUfqQI5UvBzQ3ILh8CjAtAujG9S3iKx7CoGsKPiJu9v
|
||||
XHXTa0ccKiJpqKvLDaUYUcfz+5DLVaFxH+AsDb8ew8Ec8HMOmpyAEHfYyRAdcbeu
|
||||
bh4XtG4ccTpqFtZS+1YEqUowq8lwIYM6gC9m4Mqj+/ZfRl9Ul5CSLpk9GpaWqyAt
|
||||
HI5JHoCDWI9RPuQ55yajpzPvcbCidV/nQxiocs60gQKBgQD67u9cMau9TZzu/aML
|
||||
A2nk3DoYzPIoZ7cD5LdxV6EWZJ507+MIcbqdBAuarrkcfrToiN4sq/502ExHCclO
|
||||
lorMj8cO+AetLMzRfIVTsVrBHjvoVTNOYffF+d8qZ7sYIR2MsuT4XZbJbVb3fD+5
|
||||
52PTNOJF07lup7rr7dudVZXJqwKBgQDgfFwt/JGSClOeGhBTm1avAinIkEzyzjr/
|
||||
HYHlzChFZeqSm5P3RuI9sGvMQs9oJQcze7tEAndRXhVXZ3jJfiFUKb+1exNjcWyV
|
||||
yFAACi5n1Yl9ZJRieQsXxhzLE7XiKvknipvwyA+Wf5SY2AR0uvAQSZFyBP3aF8I6
|
||||
0dntXxjcCQKBgBQmwgQmXQNby0GKDuDgik19vhClzMCf65udb3njrqUMuYjshs/z
|
||||
yie33nKym10Fc+PEsgrmWQ3rHN0LlSYBgu4AkdzK4Frw4RXlqRNah85AblEvHmqq
|
||||
BWrZsSlHoUfDyQq2hxrG8UgFxtkjGj8ErQiWE6HF6ftP7vvpYxyUde33AoGBAKCW
|
||||
9k6DhxPFRWot1Q4qawmJb6Cl8hYkiDnmvv1IXmq+7N7yYxibYc+lvIgEJ2GmU491
|
||||
7VJKvVa9CmFbiSIDlA4kS5ulLwqNopNIGEre/bGUJeZJJImJc/EO7ZwtPolMGq85
|
||||
zjKiu9v66q+ooQ9sh73vExhRD9SL+Iuhgdf4ls0ZAoGBAMwcYLCPVI01EDpKR6RA
|
||||
tVcHQ2SDeG0i5C93DtNLFoe/pWLtB9VKqHpNVduP4u9m6g5tGIQUd5tb167Fv37c
|
||||
JMXg5tH3fzPSNyrwxVYgPghByCnU+2EuSoW8Le907LqEzSK8bGWWAIhEEdGPpCXd
|
||||
9U3aIgpCmgCxfMwT/pGQNcIC
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICrDCCAZQCAQAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRYW5vdGhlciB0
|
||||
ZXN0IHVzZXIwHhcNMjMwMTIwMDIwOTE3WhcNMzMwMTE5MDcwMDAwWjAcMRowGAYD
|
||||
VQQDDBFhbm90aGVyIHRlc3QgdXNlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBALoYWzKowSNs4sI94rbqHQQB03vU6i0Yv6A4FMS8qtiOKIUjoEvPKD3V
|
||||
QyTWv8caYJlQX8C2BcOpCQ6ZXdycTGAA2Mo0ssRnGCmhIHmzKOvpFYAcLgTTiTK/
|
||||
nuOU+pOR29V2gwQtcTU0zxs1C9YOn946TErwkYIUf7fIDUWjSCuLgFa4H2qtJ5D6
|
||||
dklEWe73Fdd1BSywrcI7Y5jCL+quPLpVxYQv3OS6/yk3XV91O+CXCeTskE8pFR+d
|
||||
m8Qhf2ZVov/cI1CZ9+DerYlX/9vlCGyb6EdvV+2DjPId+INh/ac8RSOK8RwL3U8j
|
||||
qPVcsK/sKRapBECT++C5KJYJZ/rqkMkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||
DyUuK7A+jxgHHezZ+7RzYwWkBUL/tRtXfwax114MYVJPN25rJ0T0lDncgXjE6qg1
|
||||
cSK4ERJ+qi6Bj+ACXh/iaLNGnidWcGg6HHCCJCiTjco2SIxq0ytEe8znN6uhVj+d
|
||||
pSdt8ICDhimr+yMZUYs0q7rMzPFUJSUPGi50mo/BubSuPzImaBLbS5TJ/sF/EAbC
|
||||
Q+zRo5qjlehZatlXUYp+Zh5pHTvLDtmkHU5dQibbf8cYncuCCtjpbsjLXGNsi29c
|
||||
OWqk+BKOEjGfCgUDjxzt5TXREA5MsCIVnrnU2lkhUUgfDVIoLK32p21NMKlXiIH0
|
||||
yGVD41bZ6d+ZDO5LU0LjzA==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6GFsyqMEjbOLC
|
||||
PeK26h0EAdN71OotGL+gOBTEvKrYjiiFI6BLzyg91UMk1r/HGmCZUF/AtgXDqQkO
|
||||
mV3cnExgANjKNLLEZxgpoSB5syjr6RWAHC4E04kyv57jlPqTkdvVdoMELXE1NM8b
|
||||
NQvWDp/eOkxK8JGCFH+3yA1Fo0gri4BWuB9qrSeQ+nZJRFnu9xXXdQUssK3CO2OY
|
||||
wi/qrjy6VcWEL9zkuv8pN11fdTvglwnk7JBPKRUfnZvEIX9mVaL/3CNQmffg3q2J
|
||||
V//b5Qhsm+hHb1ftg4zyHfiDYf2nPEUjivEcC91PI6j1XLCv7CkWqQRAk/vguSiW
|
||||
CWf66pDJAgMBAAECggEARzlpNjNmcGONSmCbM/zYjB8SzTNJSWdOeEjekgnPrcCC
|
||||
+6oOANXRhhDoeOIEVnTfAe7EJyLDhAZfJApI5VWg2aGZV5Lh1M/MbKpxnoKWp+v2
|
||||
waiHaGt5+EVkz5/GY9KQe9u2+1NVH9MNbVFZLV09jLVtW8VFO68SzskvYzbCOX+s
|
||||
0xzaKIvgZGUxgYIR8+W422hugKxSi31RTG/Os7MViKH0eEavFZDzc4qbesOxFWKA
|
||||
ayfSIfeJVPZnDG5SQTT63QxNLHz9b8oZlmXagfxgxvHVCJc9wgOayQ3fM01a/QLP
|
||||
XMLmDOhUn+9SB2c09Ky6tGasQpCLdzXjlmqWcemobQKBgQDb4DjBqT2KhhFkuX7G
|
||||
3c+9ak4XhGkvDXZ90McUrZ5gH8T6t49tXokrzvWZhf36MsLhbCK+x4IEkPgGhTOD
|
||||
ZkdIDRU/Fn/mnRaLZuoO4Y39lXm/x1evHI8mP5ker9pJwlKeUNp8qH+UVeG23UCO
|
||||
j0fMaVi+3R1ILZugOXOyL9xBUwKBgQDYq1kcZts6NC2QFwHToZgN1rifHsYpK6v5
|
||||
9g93V/JyAPgBbihGfjkp4pwpD0qI7/VZsw14zNKkSSAeFLaekWtc5b31ejmlW5ZQ
|
||||
PZqVdhcftWVgO0zmtBkqZzDsY7Itdzgsd1y/CiVCU6daoMcPyc/GY//KAR6aOkqv
|
||||
w6zMOzhV8wKBgBmm+Tgu5I0qwxC7S30sF7aDloTL3/GrYm2fU/qnnticHEEb9VHP
|
||||
O7WuuZyls1HjZjUihpM3d4XM3AL2u2HTJvHTBO2NVHK1VRICecAutIAnVkL3oNU/
|
||||
Qbw2o0ifP5pnX3g0+qich/XoZyMMgSGgucGxcLcj2Oy41XVF/qeFSe8dAoGAB9az
|
||||
I57pAYMPvu3GKCTpfl6tUTxoyOaGk0V+q7+nys4UKuEUXfCFGunS4n1mIewkgTlE
|
||||
HIG0gTMQEWaIcNYr/zFknPPuD/hvSLnh1NRv47rJTyD2GEadvnX7RCIbOR/eDWXI
|
||||
GjVAwdSK8nFsojqX5MKLZ4CA3e2L9C3tG3ptAFcCgYEAgcAXUyCy8bs6pqwRsVbk
|
||||
g/vpHrOpCyWbpKTu9ErXsc6Lf6wGP/yzAHuUSznPSEs7JPCqCk9SGumS20OTKrmp
|
||||
yvFdaOrdVh51NY+ag8I5FpcWifc6YD14qenxfRapqjxi2r9WnptVfQvTNotsa44S
|
||||
V7ueLsLSkYsIfAq9bYQsg5U=
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC7jCCAdYCAQcwDQYJKoZIhvcNAQELBQAwPTESMBAGA1UEAwwJbG9jYWxob3N0
|
||||
MQswCQYDVQQGEwJVUzEaMBgGA1UEBwwRU2FuIEZyYW5jaXNjbywgQ0EwHhcNMjMw
|
||||
MTExMjAwMDU5WhcNMjUwNDE1MjAwMDU5WjA9MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
|
||||
CzAJBgNVBAYTAlVTMRowGAYDVQQHDBFTYW4gRnJhbmNpc2NvLCBDQTCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALlaPa1AxDQnMo0qQxY5/Bf7MNf1x6tN
|
||||
xjkpMnQnPM+cHmmlkEhI1zwLk/LrLxwq7+OOxMTPrJglrAiDAp1uCZHjKcTMFnwO
|
||||
9M5vf8LjtYBjZd8+OSHyYV37gxw7h9/Wsxl+1Yw40QaJKM9auj2xOyaDj5Ou9+yp
|
||||
CfbGSpVUTnqReOVFg2QSNwEviOZu1SvAouPyO98WKoXjn7K5mxE545e4mgF1EMht
|
||||
jB5kH6kXqZSUszlGA1MkX3AlDsYJIcYnDwelNvw6XTPpkT2wNehxPyD0iP4rs+W4
|
||||
5hgV8wYokpgrM3xxe0c4mop5bzrp2Hyz3WxnF7KwtJgHW/6YxhG73skCAwEAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAfI+UE/3d0Fb8BZ2gtv1kUh8yx75LUbpg1aOEsZdP
|
||||
Rji+GkL5xiFDsm7BwqTKziAjDtjL2qtGcJJ835shsGiUSK6qJuf9C944utUvCoFm
|
||||
b4aUZ8fTmN7PkwRS61nIcHaS1zkiFzUdvbquV3QWSnl9kC+yDLHT0Z535tcvCMVM
|
||||
bO7JMj1sxml4Y9B/hfY7zAZJt1giSNH1iDeX2pTpmPPI40UsRn98cC8HZ0d8wFrv
|
||||
yc3hKkz8E+WTgZUf7jFk/KX/T5uwu+Y85emwfbb82KIR3oqhkJIfOfpqop2duZXB
|
||||
hMuO1QWEBkZ/hpfrAsN/foz8v46P9qgW8gfOfzhyBcqLvA==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAuVo9rUDENCcyjSpDFjn8F/sw1/XHq03GOSkydCc8z5weaaWQ
|
||||
SEjXPAuT8usvHCrv447ExM+smCWsCIMCnW4JkeMpxMwWfA70zm9/wuO1gGNl3z45
|
||||
IfJhXfuDHDuH39azGX7VjDjRBokoz1q6PbE7JoOPk6737KkJ9sZKlVROepF45UWD
|
||||
ZBI3AS+I5m7VK8Ci4/I73xYqheOfsrmbETnjl7iaAXUQyG2MHmQfqReplJSzOUYD
|
||||
UyRfcCUOxgkhxicPB6U2/DpdM+mRPbA16HE/IPSI/iuz5bjmGBXzBiiSmCszfHF7
|
||||
RziainlvOunYfLPdbGcXsrC0mAdb/pjGEbveyQIDAQABAoIBAQC36ylkLu4Bahup
|
||||
I5RqC6NwEFpJEKLOAmB8+7oKs5yNzTYIUra2Y0DfXgWyd1fJtXlP7aymNgPm/QqV
|
||||
b5o6qKNqVWRu2Kw+8YBNDypRMi45dWfyewWp/55J6XYRn6iVna8dz1MKzp3qxFLw
|
||||
XfCLor802jqvqmBsPteaPOxo/LzatKhXp/mcO/hsxeMr1iSUVHTrQEIU/aIkmAqT
|
||||
/eXp/zVZk7O9Tx8wwCijB3v7j3zTEkcKSwFlAp0w01XeqllmqA5P9rW3vVGXJVIM
|
||||
t6t9C8XcJWPIOURz3JWZJpUBSZsyNe2N/wbCgkQV81A0s+4praKzgDbjE+njb0C/
|
||||
1CClbHV5AoGBAO/mnOzHe7ZJyYfuiu6ZR2REBY61n2J6DkL1stkN5xd+Op25afHT
|
||||
jLBjU98hM/AMtP1aHWFQpdEe0uyqRjV6PbpNE8j/m9AVfjZxzwR4ITW2xqUhXOSz
|
||||
89o832RO54TTr19YGnIhdU8dDQmYOcKmCSuw6KwCfHwBzkFuDFZGk/4/AoGBAMXK
|
||||
gzNyX3tN9Ug5AUo/Az4jQRSoyLjfnce0a0TF4jxEacUBx2COq3zaV/VADEFBla1t
|
||||
5roOAUyJ3V6fXtZnoqwZPYh6iGP8p7Tj6vyXI4SDktV0uAV57qSdajqxTrA7yoXr
|
||||
zrbxv3U/3vXr3JTsP42U5zp1m5n1VfVqCXBkynD3AoGBAOvs7JjDWXuctzASPNmH
|
||||
LjmB18FQBk3vYQUi4l8pmAF3pyejx3gGJw70r+/4lD5YEMozjD8+88Njv+T1U5SW
|
||||
Agysbm+2SMJr0LK0W/W2Olq7xEFzPQrBmmgeg0b/fhoXoBlw6JkjJF3IYSD1bqBp
|
||||
bw1jrn4y979weynHkyRpxnM7AoGBALGSzRPlPR/gr7P1qdjUlb61u/omRn7kFC11
|
||||
J1EJL8HX0fXTUQK5U/C1vn4q0FXN4elgX+LuK/BhXeNTxbtMM9m6l2nuSIEsFgzr
|
||||
Cs9XicWwsqT9MzGHdN9JjFPBV9oU9BAj0uSgSbmkbDHxXYo+SBh+dNIhQF+KyW+Z
|
||||
kXvcoXulAoGAA2hnEA17nJ7Vj1DZ4CoRblgjZFAMB64slcSesaorp3WWehvaXO8u
|
||||
jbvWuvj58DgvTLiv8xPIn4Zsjd0a77ysifvUcmxSRa/k9UIle/lwjmXGjQ1GSMEI
|
||||
FB5ZTqjLZwS9Y5BDxlPcYF7vqE9fNpcxmcfHGmSF5YAHvFOfGH6B63M=
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -1,18 +0,0 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIC0zCCAbsCAQAwPTESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJVUzEa
|
||||
MBgGA1UEBwwRU2FuIEZyYW5jaXNjbywgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQC5Wj2tQMQ0JzKNKkMWOfwX+zDX9cerTcY5KTJ0JzzPnB5ppZBI
|
||||
SNc8C5Py6y8cKu/jjsTEz6yYJawIgwKdbgmR4ynEzBZ8DvTOb3/C47WAY2XfPjkh
|
||||
8mFd+4McO4ff1rMZftWMONEGiSjPWro9sTsmg4+TrvfsqQn2xkqVVE56kXjlRYNk
|
||||
EjcBL4jmbtUrwKLj8jvfFiqF45+yuZsROeOXuJoBdRDIbYweZB+pF6mUlLM5RgNT
|
||||
JF9wJQ7GCSHGJw8HpTb8Ol0z6ZE9sDXocT8g9Ij+K7PluOYYFfMGKJKYKzN8cXtH
|
||||
OJqKeW866dh8s91sZxeysLSYB1v+mMYRu97JAgMBAAGgUTBPBgkqhkiG9w0BCQ4x
|
||||
QjBAMDEGA1UdJQQqMCgGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsG
|
||||
AQUFBwMEMAsGA1UdDwQEAwIFIDANBgkqhkiG9w0BAQsFAAOCAQEAOKb0Mnnm7oLT
|
||||
0fz7+CQ4KYva/dmr75k38PPRXGs/7Ls6nhu59yNhudHJtRyjaAzffwfg1NWxKlUV
|
||||
gDf+4K6S+cjz6bWVdU4XwH37V01GWWgzmwDGEsoZZpNstuq87BhI62BKQFKqJrw2
|
||||
pqNYoM+p4K7OnOUNT60LshzThguMb4h53YcTXyv7wAf9LABc4v0daVErunDZ5Elh
|
||||
QwlUZT/pngTLJiXDjrWB3PGnniTbC0OYhKKmFbX/dIR/TlUH7Fcc4mE9f514mU0n
|
||||
zys/mc57gBTdI11oIw1fkQJ6f3LDk3MsFfJntwhxjVeSXJUNOBwsxmxdyigjsifY
|
||||
J+SpNczO1Q==
|
||||
-----END CERTIFICATE REQUEST-----
|
Reference in New Issue