feature: Add provided certificates check before to generate ACME certificate when OnHostRule is activated
- ADD TI to check the new behaviour with onHostRule and provided certificates - ADD TU on the getProvidedCertificate method
This commit is contained in:
parent
f99f3b987e
commit
631079a12f
8 changed files with 258 additions and 26 deletions
54
acme/acme.go
54
acme/acme.go
|
@ -328,14 +328,11 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
|
||||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||||
account := a.store.Get().(*Account)
|
account := a.store.Get().(*Account)
|
||||||
//use regex to test for wildcard certs that might have been added into TLSConfig
|
|
||||||
for k := range a.TLSConfig.NameToCertificate {
|
if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil {
|
||||||
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
|
return providedCertificate, nil
|
||||||
match, _ := regexp.MatchString(selector, domain)
|
|
||||||
if match {
|
|
||||||
return a.TLSConfig.NameToCertificate[k], nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||||
log.Debugf("ACME got challenge %s", domain)
|
log.Debugf("ACME got challenge %s", domain)
|
||||||
return challengeCert, nil
|
return challengeCert, nil
|
||||||
|
@ -520,8 +517,20 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
|
||||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
a.jobs.In() <- func() {
|
a.jobs.In() <- func() {
|
||||||
log.Debugf("LoadCertificateForDomains %s...", domains)
|
log.Debugf("LoadCertificateForDomains %v...", domains)
|
||||||
|
|
||||||
|
if len(domains) == 0 {
|
||||||
|
// no domain
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||||
|
|
||||||
|
// Check provided certificates
|
||||||
|
if a.getProvidedCertificate(domains) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
if a.client == nil {
|
if a.client == nil {
|
||||||
return fmt.Errorf("ACME client still not built")
|
return fmt.Errorf("ACME client still not built")
|
||||||
|
@ -540,11 +549,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
}
|
}
|
||||||
account := a.store.Get().(*Account)
|
account := a.store.Get().(*Account)
|
||||||
var domain Domain
|
var domain Domain
|
||||||
if len(domains) == 0 {
|
if len(domains) > 1 {
|
||||||
// no domain
|
|
||||||
return
|
|
||||||
|
|
||||||
} else if len(domains) > 1 {
|
|
||||||
domain = Domain{Main: domains[0], SANs: domains[1:]}
|
domain = Domain{Main: domains[0], SANs: domains[1:]}
|
||||||
} else {
|
} else {
|
||||||
domain = Domain{Main: domains[0]}
|
domain = Domain{Main: domains[0]}
|
||||||
|
@ -578,6 +583,29 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get provided certificate which check a domains list (Main and SANs)
|
||||||
|
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
||||||
|
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||||
|
providedCertMatch := false
|
||||||
|
log.Debugf("Look for provided certificate to validate %s...", domains)
|
||||||
|
for k := range a.TLSConfig.NameToCertificate {
|
||||||
|
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
|
||||||
|
for _, domainToCheck := range domains {
|
||||||
|
providedCertMatch, _ = regexp.MatchString(selector, domainToCheck)
|
||||||
|
if !providedCertMatch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if providedCertMatch {
|
||||||
|
log.Debugf("Got provided certificate for domains %s", domains)
|
||||||
|
return a.TLSConfig.NameToCertificate[k]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||||
log.Debugf("Loading ACME certificates %s...", domains)
|
log.Debugf("Loading ACME certificates %s...", domains)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -277,3 +279,18 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||||
t.Errorf("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
|
t.Errorf("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||||
|
mm := make(map[string]*tls.Certificate)
|
||||||
|
mm["*.containo.us"] = &tls.Certificate{}
|
||||||
|
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||||
|
|
||||||
|
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||||
|
|
||||||
|
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
||||||
|
certificate := a.getProvidedCertificate(domains)
|
||||||
|
assert.NotNil(t, certificate)
|
||||||
|
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||||
|
certificate = a.getProvidedCertificate(domains)
|
||||||
|
assert.Nil(t, certificate)
|
||||||
|
}
|
||||||
|
|
|
@ -2,32 +2,45 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-check/check"
|
|
||||||
|
|
||||||
"errors"
|
|
||||||
"github.com/containous/traefik/integration/utils"
|
"github.com/containous/traefik/integration/utils"
|
||||||
|
"github.com/go-check/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ACME test suites (using libcompose)
|
// ACME test suites (using libcompose)
|
||||||
type AcmeSuite struct {
|
type AcmeSuite struct {
|
||||||
BaseSuite
|
BaseSuite
|
||||||
|
boulderIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Acme tests configuration
|
||||||
|
type AcmeTestCase struct {
|
||||||
|
onDemand bool
|
||||||
|
traefikConfFilePath string
|
||||||
|
domainToCheck string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain to check
|
||||||
|
const acmeDomain = "traefik.acme.wtf"
|
||||||
|
|
||||||
|
// Wildcard domain to chekc
|
||||||
|
const wildcardDomain = "*.acme.wtf"
|
||||||
|
|
||||||
func (s *AcmeSuite) SetUpSuite(c *check.C) {
|
func (s *AcmeSuite) SetUpSuite(c *check.C) {
|
||||||
s.createComposeProject(c, "boulder")
|
s.createComposeProject(c, "boulder")
|
||||||
s.composeProject.Start(c)
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
boulderHost := s.composeProject.Container(c, "boulder").NetworkSettings.IPAddress
|
s.boulderIP = s.composeProject.Container(c, "boulder").NetworkSettings.IPAddress
|
||||||
|
|
||||||
// wait for boulder
|
// wait for boulder
|
||||||
err := utils.Try(120*time.Second, func() error {
|
err := utils.Try(120*time.Second, func() error {
|
||||||
resp, err := http.Get("http://" + boulderHost + ":4000/directory")
|
resp, err := http.Get("http://" + s.boulderIP + ":4000/directory")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -47,9 +60,48 @@ func (s *AcmeSuite) TearDownSuite(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AcmeSuite) TestRetrieveAcmeCertificate(c *check.C) {
|
// Test OnDemand option with none provided certificate
|
||||||
boulderHost := s.composeProject.Container(c, "boulder").NetworkSettings.IPAddress
|
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificate(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/acme/acme.toml", struct{ BoulderHost string }{boulderHost})
|
aTestCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme.toml",
|
||||||
|
onDemand: true,
|
||||||
|
domainToCheck: acmeDomain}
|
||||||
|
s.retrieveAcmeCertificate(c, aTestCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test OnHostRule option with none provided certificate
|
||||||
|
func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificate(c *check.C) {
|
||||||
|
aTestCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme.toml",
|
||||||
|
onDemand: false,
|
||||||
|
domainToCheck: acmeDomain}
|
||||||
|
s.retrieveAcmeCertificate(c, aTestCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test OnDemand option with a wildcard provided certificate
|
||||||
|
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
|
||||||
|
aTestCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme_provided.toml",
|
||||||
|
onDemand: true,
|
||||||
|
domainToCheck: wildcardDomain}
|
||||||
|
s.retrieveAcmeCertificate(c, aTestCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test onHostRule option with a wildcard provided certificate
|
||||||
|
func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateWithWildcard(c *check.C) {
|
||||||
|
aTestCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme_provided.toml",
|
||||||
|
onDemand: false,
|
||||||
|
domainToCheck: wildcardDomain}
|
||||||
|
s.retrieveAcmeCertificate(c, aTestCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doing an HTTPS request and test the response certificate
|
||||||
|
func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, a AcmeTestCase) {
|
||||||
|
file := s.adaptFile(c, a.traefikConfFilePath, struct {
|
||||||
|
BoulderHost string
|
||||||
|
OnDemand, OnHostRule bool
|
||||||
|
}{s.boulderIP, a.onDemand, !a.onDemand})
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
|
@ -77,16 +129,32 @@ func (s *AcmeSuite) TestRetrieveAcmeCertificate(c *check.C) {
|
||||||
tr = &http.Transport{
|
tr = &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
ServerName: "traefik.acme.wtf",
|
ServerName: acmeDomain,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
client = &http.Client{Transport: tr}
|
client = &http.Client{Transport: tr}
|
||||||
req, _ := http.NewRequest("GET", "https://127.0.0.1:5001/", nil)
|
req, _ := http.NewRequest("GET", "https://127.0.0.1:5001/", nil)
|
||||||
req.Host = "traefik.acme.wtf"
|
req.Host = acmeDomain
|
||||||
req.Header.Set("Host", "traefik.acme.wtf")
|
req.Header.Set("Host", acmeDomain)
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
var resp *http.Response
|
||||||
|
// Retry to send a Request which uses the LE generated certificate
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
||||||
|
req.Close = true
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if resp.TLS.PeerCertificates[0].Subject.CommonName != a.domainToCheck {
|
||||||
|
return errors.New("Domain " + resp.TLS.PeerCertificates[0].Subject.CommonName + " found in place of " + a.domainToCheck)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
// Check Domain into response certificate
|
||||||
|
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, a.domainToCheck)
|
||||||
// Expected a 200
|
// Expected a 200
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
37
integration/fixtures/acme/README.md
Normal file
37
integration/fixtures/acme/README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# How to generate the self-signed wildcard certificate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Specify where we will install
|
||||||
|
# the wildcard certificate
|
||||||
|
SSL_DIR="./ssl"
|
||||||
|
|
||||||
|
# Set the wildcarded domain
|
||||||
|
# we want to use
|
||||||
|
DOMAIN="*.acme.wtf"
|
||||||
|
|
||||||
|
# A blank passphrase
|
||||||
|
PASSPHRASE=""
|
||||||
|
|
||||||
|
# Set our CSR variables
|
||||||
|
SUBJ="
|
||||||
|
C=FR
|
||||||
|
ST=MP
|
||||||
|
O=
|
||||||
|
localityName=Toulouse
|
||||||
|
commonName=$DOMAIN
|
||||||
|
organizationalUnitName=Traefik
|
||||||
|
emailAddress=
|
||||||
|
"
|
||||||
|
|
||||||
|
# Create our SSL directory
|
||||||
|
# in case it doesn't exist
|
||||||
|
sudo mkdir -p "$SSL_DIR"
|
||||||
|
|
||||||
|
# Generate our Private Key, CSR and Certificate
|
||||||
|
sudo openssl genrsa -out "$SSL_DIR/wildcard.key" 2048
|
||||||
|
sudo openssl req -new -subj "$(echo -n "$SUBJ" | tr "\n" "/")" -key "$SSL_DIR/wildcard.key" -out "$SSL_DIR/wildcard.csr" -passin pass:$PASSPHRASE
|
||||||
|
sudo openssl x509 -req -days 3650 -in "$SSL_DIR/wildcard.csr" -signkey "$SSL_DIR/wildcard.key" -out "$SSL_DIR/wildcard.crt"
|
||||||
|
sudo rm -f "$SSL_DIR/wildcard.csr"
|
||||||
|
```
|
|
@ -14,7 +14,8 @@ defaultEntryPoints = ["http", "https"]
|
||||||
email = "test@traefik.io"
|
email = "test@traefik.io"
|
||||||
storage = "/dev/null"
|
storage = "/dev/null"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
onDemand = true
|
onDemand = {{.OnDemand}}
|
||||||
|
OnHostRule = {{.OnHostRule}}
|
||||||
caServer = "http://{{.BoulderHost}}:4000/directory"
|
caServer = "http://{{.BoulderHost}}:4000/directory"
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
35
integration/fixtures/acme/acme_provided.toml
Normal file
35
integration/fixtures/acme/acme_provided.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":8080"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":5001"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "fixtures/acme/ssl/wildcard.crt"
|
||||||
|
KeyFile = "fixtures/acme/ssl/wildcard.key"
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "/dev/null"
|
||||||
|
entryPoint = "https"
|
||||||
|
onDemand = {{.OnDemand}}
|
||||||
|
OnHostRule = {{.OnHostRule}}
|
||||||
|
caServer = "http://{{.BoulderHost}}:4000/directory"
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.backend]
|
||||||
|
[backends.backend.servers.server1]
|
||||||
|
url = "http://127.0.0.1:9010"
|
||||||
|
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend]
|
||||||
|
backend = "backend"
|
||||||
|
[frontends.frontend.routes.test]
|
||||||
|
rule = "Host:traefik.acme.wtf"
|
19
integration/fixtures/acme/ssl/wildcard.crt
Normal file
19
integration/fixtures/acme/ssl/wildcard.crt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDJDCCAgwCCQCS90TE7NuTqzANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJG
|
||||||
|
UjELMAkGA1UECAwCTVAxETAPBgNVBAcMCFRvdWxvdXNlMRMwEQYDVQQDDAoqLmFj
|
||||||
|
bWUud3RmMRAwDgYDVQQLDAdUcmFlZmlrMB4XDTE3MDYyMzE0NTE0MVoXDTI3MDYy
|
||||||
|
MTE0NTE0MVowVDELMAkGA1UEBhMCRlIxCzAJBgNVBAgMAk1QMREwDwYDVQQHDAhU
|
||||||
|
b3Vsb3VzZTETMBEGA1UEAwwKKi5hY21lLnd0ZjEQMA4GA1UECwwHVHJhZWZpazCC
|
||||||
|
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAODqsVCLhauFZPhPXqZDIKST
|
||||||
|
wqoJST+jO5O/WmA7oC4S6JlecRoNsHAXyddd3cQW3yZqB0ryOHrMOpMX0PPXf3jS
|
||||||
|
OOXoXA6xsq+RXlR4hDrBkOrj/LR/g62Eiuj2JVO2uy6tKJIetSB/Wzl6OgRkY/um
|
||||||
|
EXIc7zQS81/QKg+pg7Z4AYJht5J88nOFHJ3RspUMaH1vJ6LhH3MOUkgFj+I1OiqX
|
||||||
|
Tnkd7EDWbkYxAJa0xI2qbmY5VYv8dsIUN+IlPFDtBt87Fc2qv5dQkOz11FDYxWnz
|
||||||
|
+kxX6+MESLBaTvJjXvG+bzTfh9xCExFQFiN+Us0JuLX8HKQ4MqWL2IiVLsko2osC
|
||||||
|
AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAl2jTX2yzUpiufrJ6WtZjKIAH8GF817hS
|
||||||
|
dWvt2eyLrBPvllMUj8zqCE5uNVUDVuXQvOhOyx+3zZzfcgfYqbTD8G8amNWcSiRA
|
||||||
|
vonoOn1p1pW2OonSi32h3qv5i4gCyh/6cBneYi03lkQ7uLCsJK9+dXTAvoKL6s23
|
||||||
|
IXhZGS0Qkvs4vkORA2MX9tyJdyfCCaCx3GpPCGkKrKJ8ePTEvq1ZE2xdhERnV5pz
|
||||||
|
L1PRY2QthXXVjMz7AXw0gkHvAbtrKVKR1Tv4ZK34bFBh/kyGAjkcn0zdeFKITqTF
|
||||||
|
tCoXWEArmiRqGuXwbqU3mEA9Cv6aMM+0YX89K2InhOnBU80OWs0uMQ==
|
||||||
|
-----END CERTIFICATE-----
|
27
integration/fixtures/acme/ssl/wildcard.key
Normal file
27
integration/fixtures/acme/ssl/wildcard.key
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA4OqxUIuFq4Vk+E9epkMgpJPCqglJP6M7k79aYDugLhLomV5x
|
||||||
|
Gg2wcBfJ113dxBbfJmoHSvI4esw6kxfQ89d/eNI45ehcDrGyr5FeVHiEOsGQ6uP8
|
||||||
|
tH+DrYSK6PYlU7a7Lq0okh61IH9bOXo6BGRj+6YRchzvNBLzX9AqD6mDtngBgmG3
|
||||||
|
knzyc4UcndGylQxofW8nouEfcw5SSAWP4jU6KpdOeR3sQNZuRjEAlrTEjapuZjlV
|
||||||
|
i/x2whQ34iU8UO0G3zsVzaq/l1CQ7PXUUNjFafP6TFfr4wRIsFpO8mNe8b5vNN+H
|
||||||
|
3EITEVAWI35SzQm4tfwcpDgypYvYiJUuySjaiwIDAQABAoIBAQCs9Ex9v4x+pQlL
|
||||||
|
2NzTxXLom6dp0dI92WwK5W696Zv3UhsDNRiMDFLNH73amxfZnizjAU2yWCkOZNX2
|
||||||
|
Hq5TlDc11ZJjWRbRRdw+He8HzdUAybCCr+a3dgbv+6hGFGIHydCOyCEWm/50ivq/
|
||||||
|
bDoI/pnT/ZQUyCM5TAlSeGSfvp7GRHi9v3HOl85H1Pn2Dvyk9gj4y3BIFrKuv8fJ
|
||||||
|
o6aEzlfgWGROCzshU2m8fB9P0B4hWDlJsc1D01sW60zhjLo9+XoWznmw5mczz7sc
|
||||||
|
S5sdDh47rSJsNRuFd7YDjeLzJWPqLrKVB5nn6nRbvrnBqhfsknkO4VIXhmEMSs1u
|
||||||
|
RMYOJ9ShAoGBAPinA6ktIeez1t5IsfxGwbCeZzFI1suZqZeX6ezNKaMpeykyAPuh
|
||||||
|
CqN7H+a4NCKsinsgHJowU98ckHeAsQ22s7R8dFZhyxEXkcBawY2soK29eq2aJHnY
|
||||||
|
lqKOwjOA7wgElRHwLkNFniQ5lKFPMly8a9NVAqg+Th/J3uR+7wE2t+b1AoGBAOeQ
|
||||||
|
H/vVkdaNB2ovnCxMh+OfxpcjkfF6KnD2jpn/TKsbR5BtnrtyRLc5+qt52D0CEgSy
|
||||||
|
qU3zrsZebShej3OIBPrEwIcPN+LezaxnLMf9RXdOde+wWrQLWLkShJaSTwSoGqZB
|
||||||
|
fcO0/sc1lzhGxm++ByP5mWbHr/VM9IdTQQH5Bct/AoGBAMhmOrIXeNL4Az2FU0Vi
|
||||||
|
dWp2T+7NqKfRAXj264Z5V4xzuxpZfadPhHZ7nhth7Erhyn4vRD4UoxQXPmvB4XCP
|
||||||
|
Bkh5YX3ZNUNiPorL2mDnd1xvcLcHm0xEfisnaWb/DCbnIomhjHeVXT4O1jYn0Qwi
|
||||||
|
o7hgNFMKXAaMuUJo9xGAWzkdAoGASxC4nY2tOiz7k1udt+qTPqHj4cjhHbOpoHb8
|
||||||
|
4UUWmH0+ZL50b3Vqey8raH0WMSjDqIw2QBPXu2yO3EBTJnOYkaZIdz/isQPjDplf
|
||||||
|
tfEPnM5tgubbcHQhLdWn75u8S9km0nB2kYPR98gSnmarGzwx2mKmbOAc1Vs+BcRi
|
||||||
|
VX5hd4cCgYAubBq0VsFT0KVU3Rva3dgPR1K5bp4r4hE5cGXm4HvLiOgv995CwPy1
|
||||||
|
27eONF9GN7hvjI6C17jA1Gyx5sN0QrsMv/1BZqiGaragMOPXFD+tVecWuKH4lZQi
|
||||||
|
VbKTOWHlGkrDCpiYWpfetQAjouj+0c6d+wigcoC8e5dwxBPI2f3rGw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
Loading…
Add table
Add a link
Reference in a new issue