Skip to content

Commit b72d56f

Browse files
neildgopherbot
authored andcommitted
[release-branch.go1.22] net/http: persist header stripping across repeated redirects
When an HTTP redirect changes the host of a request, we drop sensitive headers such as Authorization from the redirected request. Fix a bug where a chain of redirects could result in sensitive headers being sent to the wrong host: 1. request to a.tld with Authorization header 2. a.tld redirects to b.tld 3. request to b.tld with no Authorization header 4. b.tld redirects to b.tld 3. request to b.tld with Authorization header restored Thanks to Kyle Seely for reporting this issue. Fixes #70530 For #71210 Fixes CVE-2024-45336 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1641 Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Tatiana Bradley <[email protected]> Commit-Queue: Roland Shoemaker <[email protected]> Change-Id: Id7b1e3c90345566b8ee1a51f65dbb179da6eb427 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1765 Reviewed-on: https://go-review.googlesource.com/c/go/+/643106 Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Michael Knyszek <[email protected]>
1 parent 19d2103 commit b72d56f

File tree

3 files changed

+154
-106
lines changed

3 files changed

+154
-106
lines changed

src/net/http/client.go

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,9 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
612612
reqBodyClosed = false // have we closed the current req.Body?
613613

614614
// Redirect behavior:
615-
redirectMethod string
616-
includeBody bool
615+
redirectMethod string
616+
includeBody = true
617+
stripSensitiveHeaders = false
617618
)
618619
uerr := func(err error) error {
619620
// the body may have been closed already by c.send()
@@ -680,7 +681,12 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
680681
// in case the user set Referer on their first request.
681682
// If they really want to override, they can do it in
682683
// their CheckRedirect func.
683-
copyHeaders(req)
684+
if !stripSensitiveHeaders && reqs[0].URL.Host != req.URL.Host {
685+
if !shouldCopyHeaderOnRedirect(reqs[0].URL, req.URL) {
686+
stripSensitiveHeaders = true
687+
}
688+
}
689+
copyHeaders(req, stripSensitiveHeaders)
684690

685691
// Add the Referer header from the most recent
686692
// request URL to the new one, if it's not https->http:
@@ -746,7 +752,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
746752
// makeHeadersCopier makes a function that copies headers from the
747753
// initial Request, ireq. For every redirect, this function must be called
748754
// so that it can copy headers into the upcoming Request.
749-
func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
755+
func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders bool) {
750756
// The headers to copy are from the very initial request.
751757
// We use a closured callback to keep a reference to these original headers.
752758
var (
@@ -760,8 +766,7 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
760766
}
761767
}
762768

763-
preq := ireq // The previous request
764-
return func(req *Request) {
769+
return func(req *Request, stripSensitiveHeaders bool) {
765770
// If Jar is present and there was some initial cookies provided
766771
// via the request header, then we may need to alter the initial
767772
// cookies as we follow redirects since each redirect may end up
@@ -798,12 +803,15 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
798803
// Copy the initial request's Header values
799804
// (at least the safe ones).
800805
for k, vv := range ireqhdr {
801-
if shouldCopyHeaderOnRedirect(k, preq.URL, req.URL) {
806+
sensitive := false
807+
switch CanonicalHeaderKey(k) {
808+
case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":
809+
sensitive = true
810+
}
811+
if !(sensitive && stripSensitiveHeaders) {
802812
req.Header[k] = vv
803813
}
804814
}
805-
806-
preq = req // Update previous Request with the current request
807815
}
808816
}
809817

@@ -982,28 +990,23 @@ func (b *cancelTimerBody) Close() error {
982990
return err
983991
}
984992

985-
func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool {
986-
switch CanonicalHeaderKey(headerKey) {
987-
case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":
988-
// Permit sending auth/cookie headers from "foo.com"
989-
// to "sub.foo.com".
990-
991-
// Note that we don't send all cookies to subdomains
992-
// automatically. This function is only used for
993-
// Cookies set explicitly on the initial outgoing
994-
// client request. Cookies automatically added via the
995-
// CookieJar mechanism continue to follow each
996-
// cookie's scope as set by Set-Cookie. But for
997-
// outgoing requests with the Cookie header set
998-
// directly, we don't know their scope, so we assume
999-
// it's for *.domain.com.
1000-
1001-
ihost := idnaASCIIFromURL(initial)
1002-
dhost := idnaASCIIFromURL(dest)
1003-
return isDomainOrSubdomain(dhost, ihost)
1004-
}
1005-
// All other headers are copied:
1006-
return true
993+
func shouldCopyHeaderOnRedirect(initial, dest *url.URL) bool {
994+
// Permit sending auth/cookie headers from "foo.com"
995+
// to "sub.foo.com".
996+
997+
// Note that we don't send all cookies to subdomains
998+
// automatically. This function is only used for
999+
// Cookies set explicitly on the initial outgoing
1000+
// client request. Cookies automatically added via the
1001+
// CookieJar mechanism continue to follow each
1002+
// cookie's scope as set by Set-Cookie. But for
1003+
// outgoing requests with the Cookie header set
1004+
// directly, we don't know their scope, so we assume
1005+
// it's for *.domain.com.
1006+
1007+
ihost := idnaASCIIFromURL(initial)
1008+
dhost := idnaASCIIFromURL(dest)
1009+
return isDomainOrSubdomain(dhost, ihost)
10071010
}
10081011

10091012
// isDomainOrSubdomain reports whether sub is a subdomain (or exact

src/net/http/client_test.go

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,6 +1530,55 @@ func testClientCopyHeadersOnRedirect(t *testing.T, mode testMode) {
15301530
}
15311531
}
15321532

1533+
// Issue #70530: Once we strip a header on a redirect to a different host,
1534+
// the header should stay stripped across any further redirects.
1535+
func TestClientStripHeadersOnRepeatedRedirect(t *testing.T) {
1536+
run(t, testClientStripHeadersOnRepeatedRedirect)
1537+
}
1538+
func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode testMode) {
1539+
var proto string
1540+
ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
1541+
if r.Host+r.URL.Path != "a.example.com/" {
1542+
if h := r.Header.Get("Authorization"); h != "" {
1543+
t.Errorf("on request to %v%v, Authorization=%q, want no header", r.Host, r.URL.Path, h)
1544+
}
1545+
}
1546+
// Follow a chain of redirects from a to b and back to a.
1547+
// The Authorization header is stripped on the first redirect to b,
1548+
// and stays stripped even if we're sent back to a.
1549+
switch r.Host + r.URL.Path {
1550+
case "a.example.com/":
1551+
Redirect(w, r, proto+"://b.example.com/", StatusFound)
1552+
case "b.example.com/":
1553+
Redirect(w, r, proto+"://b.example.com/redirect", StatusFound)
1554+
case "b.example.com/redirect":
1555+
Redirect(w, r, proto+"://a.example.com/redirect", StatusFound)
1556+
case "a.example.com/redirect":
1557+
w.Header().Set("X-Done", "true")
1558+
default:
1559+
t.Errorf("unexpected request to %v", r.URL)
1560+
}
1561+
})).ts
1562+
proto, _, _ = strings.Cut(ts.URL, ":")
1563+
1564+
c := ts.Client()
1565+
c.Transport.(*Transport).Dial = func(_ string, _ string) (net.Conn, error) {
1566+
return net.Dial("tcp", ts.Listener.Addr().String())
1567+
}
1568+
1569+
req, _ := NewRequest("GET", proto+"://a.example.com/", nil)
1570+
req.Header.Add("Cookie", "foo=bar")
1571+
req.Header.Add("Authorization", "secretpassword")
1572+
res, err := c.Do(req)
1573+
if err != nil {
1574+
t.Fatal(err)
1575+
}
1576+
defer res.Body.Close()
1577+
if res.Header.Get("X-Done") != "true" {
1578+
t.Fatalf("response missing expected header: X-Done=true")
1579+
}
1580+
}
1581+
15331582
// Issue 22233: copy host when Client follows a relative redirect.
15341583
func TestClientCopyHostOnRedirect(t *testing.T) { run(t, testClientCopyHostOnRedirect) }
15351584
func testClientCopyHostOnRedirect(t *testing.T, mode testMode) {
@@ -1696,43 +1745,39 @@ func testClientAltersCookiesOnRedirect(t *testing.T, mode testMode) {
16961745
// Part of Issue 4800
16971746
func TestShouldCopyHeaderOnRedirect(t *testing.T) {
16981747
tests := []struct {
1699-
header string
17001748
initialURL string
17011749
destURL string
17021750
want bool
17031751
}{
1704-
{"User-Agent", "http://foo.com/", "http://bar.com/", true},
1705-
{"X-Foo", "http://foo.com/", "http://bar.com/", true},
1706-
17071752
// Sensitive headers:
1708-
{"cookie", "http://foo.com/", "http://bar.com/", false},
1709-
{"cookie2", "http://foo.com/", "http://bar.com/", false},
1710-
{"authorization", "http://foo.com/", "http://bar.com/", false},
1711-
{"authorization", "http://foo.com/", "https://foo.com/", true},
1712-
{"authorization", "http://foo.com:1234/", "http://foo.com:4321/", true},
1713-
{"www-authenticate", "http://foo.com/", "http://bar.com/", false},
1714-
{"authorization", "http://foo.com/", "http://[::1%25.foo.com]/", false},
1753+
{"http://foo.com/", "http://bar.com/", false},
1754+
{"http://foo.com/", "http://bar.com/", false},
1755+
{"http://foo.com/", "http://bar.com/", false},
1756+
{"http://foo.com/", "https://foo.com/", true},
1757+
{"http://foo.com:1234/", "http://foo.com:4321/", true},
1758+
{"http://foo.com/", "http://bar.com/", false},
1759+
{"http://foo.com/", "http://[::1%25.foo.com]/", false},
17151760

17161761
// But subdomains should work:
1717-
{"www-authenticate", "http://foo.com/", "http://foo.com/", true},
1718-
{"www-authenticate", "http://foo.com/", "http://sub.foo.com/", true},
1719-
{"www-authenticate", "http://foo.com/", "http://notfoo.com/", false},
1720-
{"www-authenticate", "http://foo.com/", "https://foo.com/", true},
1721-
{"www-authenticate", "http://foo.com:80/", "http://foo.com/", true},
1722-
{"www-authenticate", "http://foo.com:80/", "http://sub.foo.com/", true},
1723-
{"www-authenticate", "http://foo.com:443/", "https://foo.com/", true},
1724-
{"www-authenticate", "http://foo.com:443/", "https://sub.foo.com/", true},
1725-
{"www-authenticate", "http://foo.com:1234/", "http://foo.com/", true},
1726-
1727-
{"authorization", "http://foo.com/", "http://foo.com/", true},
1728-
{"authorization", "http://foo.com/", "http://sub.foo.com/", true},
1729-
{"authorization", "http://foo.com/", "http://notfoo.com/", false},
1730-
{"authorization", "http://foo.com/", "https://foo.com/", true},
1731-
{"authorization", "http://foo.com:80/", "http://foo.com/", true},
1732-
{"authorization", "http://foo.com:80/", "http://sub.foo.com/", true},
1733-
{"authorization", "http://foo.com:443/", "https://foo.com/", true},
1734-
{"authorization", "http://foo.com:443/", "https://sub.foo.com/", true},
1735-
{"authorization", "http://foo.com:1234/", "http://foo.com/", true},
1762+
{"http://foo.com/", "http://foo.com/", true},
1763+
{"http://foo.com/", "http://sub.foo.com/", true},
1764+
{"http://foo.com/", "http://notfoo.com/", false},
1765+
{"http://foo.com/", "https://foo.com/", true},
1766+
{"http://foo.com:80/", "http://foo.com/", true},
1767+
{"http://foo.com:80/", "http://sub.foo.com/", true},
1768+
{"http://foo.com:443/", "https://foo.com/", true},
1769+
{"http://foo.com:443/", "https://sub.foo.com/", true},
1770+
{"http://foo.com:1234/", "http://foo.com/", true},
1771+
1772+
{"http://foo.com/", "http://foo.com/", true},
1773+
{"http://foo.com/", "http://sub.foo.com/", true},
1774+
{"http://foo.com/", "http://notfoo.com/", false},
1775+
{"http://foo.com/", "https://foo.com/", true},
1776+
{"http://foo.com:80/", "http://foo.com/", true},
1777+
{"http://foo.com:80/", "http://sub.foo.com/", true},
1778+
{"http://foo.com:443/", "https://foo.com/", true},
1779+
{"http://foo.com:443/", "https://sub.foo.com/", true},
1780+
{"http://foo.com:1234/", "http://foo.com/", true},
17361781
}
17371782
for i, tt := range tests {
17381783
u0, err := url.Parse(tt.initialURL)
@@ -1745,10 +1790,10 @@ func TestShouldCopyHeaderOnRedirect(t *testing.T) {
17451790
t.Errorf("%d. dest URL %q parse error: %v", i, tt.destURL, err)
17461791
continue
17471792
}
1748-
got := Export_shouldCopyHeaderOnRedirect(tt.header, u0, u1)
1793+
got := Export_shouldCopyHeaderOnRedirect(u0, u1)
17491794
if got != tt.want {
1750-
t.Errorf("%d. shouldCopyHeaderOnRedirect(%q, %q => %q) = %v; want %v",
1751-
i, tt.header, tt.initialURL, tt.destURL, got, tt.want)
1795+
t.Errorf("%d. shouldCopyHeaderOnRedirect(%q => %q) = %v; want %v",
1796+
i, tt.initialURL, tt.destURL, got, tt.want)
17521797
}
17531798
}
17541799
}

src/net/http/internal/testcert/testcert.go

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,56 +10,56 @@ import "strings"
1010
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
1111
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
1212
// generated from src/crypto/tls:
13-
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
13+
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com,*.example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
1414
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
15-
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
15+
MIIDSDCCAjCgAwIBAgIQEP/md970HysdBTpuzDOf0DANBgkqhkiG9w0BAQsFADAS
1616
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
1717
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
18-
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
19-
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
20-
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
21-
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
22-
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
23-
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
18+
MIIBCgKCAQEAxcl69ROJdxjN+MJZnbFrYxyQooADCsJ6VDkuMyNQIix/Hk15Nk/u
19+
FyBX1Me++aEpGmY3RIY4fUvELqT/srvAHsTXwVVSttMcY8pcAFmXSqo3x4MuUTG/
20+
jCX3Vftj0r3EM5M8ImY1rzA/jqTTLJg00rD+DmuDABcqQvoXw/RV8w1yTRi5BPoH
21+
DFD/AWTt/YgMvk1l2Yq/xI8VbMUIpjBoGXxWsSevQ5i2s1mk9/yZzu0Ysp1tTlzD
22+
qOPa4ysFjBitdXiwfxjxtv5nXqOCP5rheKO0sWLk0fetMp1OV5JSJMAJw6c2ZMkl
23+
U2WMqAEpRjdE/vHfIuNg+yGaRRqI07NZRQIDAQABo4GXMIGUMA4GA1UdDwEB/wQE
2424
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
25-
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
26-
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
27-
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
28-
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
29-
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
30-
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
31-
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
32-
WkBKOclmOV2xlTVuPw==
25+
DgQWBBQR5QIzmacmw78ZI1C4MXw7Q0wJ1jA9BgNVHREENjA0ggtleGFtcGxlLmNv
26+
bYINKi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
27+
9w0BAQsFAAOCAQEACrRNgiioUDzxQftd0fwOa6iRRcPampZRDtuaF68yNHoNWbOu
28+
LUwc05eOWxRq3iABGSk2xg+FXM3DDeW4HhAhCFptq7jbVZ+4Jj6HeJG9mYRatAxR
29+
Y/dEpa0D0EHhDxxVg6UzKOXB355n0IetGE/aWvyTV9SiDs6QsaC57Q9qq1/mitx5
30+
2GFBoapol9L5FxCc77bztzK8CpLujkBi25Vk6GAFbl27opLfpyxkM+rX/T6MXCPO
31+
6/YBacNZ7ff1/57Etg4i5mNA6ubCpuc4Gi9oYqCNNohftr2lkJr7REdDR6OW0lsL
32+
rF7r4gUnKeC7mYIH1zypY7laskopiLFAfe96Kg==
3333
-----END CERTIFICATE-----`)
3434

3535
// LocalhostKey is the private key for LocalhostCert.
3636
var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY-----
37-
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi
38-
4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS
39-
gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW
40-
URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX
41-
AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy
42-
VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK
43-
x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk
44-
lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL
45-
dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89
46-
EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq
47-
XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki
48-
6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O
49-
3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s
50-
uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ
51-
Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ
52-
w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo
53-
+bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP
54-
OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA
55-
brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv
56-
m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y
57-
LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN
58-
/3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN
59-
s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ
60-
Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0
61-
xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/
62-
ZboOWVe3icTy64BT3OQhmg==
37+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFyXr1E4l3GM34
38+
wlmdsWtjHJCigAMKwnpUOS4zI1AiLH8eTXk2T+4XIFfUx775oSkaZjdEhjh9S8Qu
39+
pP+yu8AexNfBVVK20xxjylwAWZdKqjfHgy5RMb+MJfdV+2PSvcQzkzwiZjWvMD+O
40+
pNMsmDTSsP4Oa4MAFypC+hfD9FXzDXJNGLkE+gcMUP8BZO39iAy+TWXZir/EjxVs
41+
xQimMGgZfFaxJ69DmLazWaT3/JnO7RiynW1OXMOo49rjKwWMGK11eLB/GPG2/mde
42+
o4I/muF4o7SxYuTR960ynU5XklIkwAnDpzZkySVTZYyoASlGN0T+8d8i42D7IZpF
43+
GojTs1lFAgMBAAECggEAIYthUi1lFBDd5gG4Rzlu+BlBIn5JhcqkCqLEBiJIFfOr
44+
/4yuMRrvS3bNzqWt6xJ9MSAC4ZlN/VobRLnxL/QNymoiGYUKCT3Ww8nvPpPzR9OE
45+
sE68TUL9tJw/zZJcRMKwgvrGqSLimfq53MxxkE+kLdOc0v9C8YH8Re26mB5ZcWYa
46+
7YFyZQpKsQYnsmu/05cMbpOQrQWhtmIqRoyn8mG/par2s3NzjtpSE9NINyz26uFc
47+
k/3ovFJQIHkUmTS7KHD3BgY5vuCqP98HramYnOysJ0WoYgvSDNCWw3037s5CCwJT
48+
gCKuM+Ow6liFrj83RrdKBpm5QUGjfNpYP31o+QNP4QKBgQDSrUQ2XdgtAnibAV7u
49+
7kbxOxro0EhIKso0Y/6LbDQgcXgxLqltkmeqZgG8nC3Z793lhlSasz2snhzzooV5
50+
5fTy1y8ikXqjhG0nNkInFyOhsI0auE28CFoDowaQd+5cmCatpN4Grqo5PNRXxm1w
51+
HktfPEgoP11NNCFHvvN5fEKbbQKBgQDwVlOaV20IvW3IPq7cXZyiyabouFF9eTRo
52+
VJka1Uv+JtyvL2P0NKkjYHOdN8gRblWqxQtJoTNk020rVA4UP1heiXALy50gvj/p
53+
hMcybPTLYSPOhAGx838KIcvGR5oskP1aUCmFbFQzGELxhJ9diVVjxUtbG2DuwPKd
54+
tD9TLxT2OQKBgQCcdlHSjp+dzdgERmBa0ludjGfPv9/uuNizUBAbO6D690psPFtY
55+
JQMYaemgSd1DngEOFVWADt4e9M5Lose+YCoqr+UxpxmNlyv5kzJOFcFAs/4XeglB
56+
PHKdgNW/NVKxMc6H54l9LPr+x05sYdGlEtqnP/3W5jhEvhJ5Vjc8YiyVgQKBgQCl
57+
zwjyrGo+42GACy7cPYE5FeIfIDqoVByB9guC5bD98JXEDu/opQQjsgFRcBCJZhOY
58+
M0UsURiB8ROaFu13rpQq9KrmmF0ZH+g8FSzQbzcbsTLg4VXCDXmR5esOKowFPypr
59+
Sm667BfTAGP++D5ya7MLmCv6+RKQ5XD8uEQQAaV2kQKBgAD8qeJuWIXZT0VKkQrn
60+
nIhgtzGERF/6sZdQGW2LxTbUDWG74AfFkkEbeBfwEkCZXY/xmnYqYABhvlSex8jU
61+
supU6Eea21esIxIub2zv/Np0ojUb6rlqTPS4Ox1E27D787EJ3VOXpriSD10vyNnZ
62+
jel6uj2FOP9g54s+GzlSVg/T
6363
-----END RSA TESTING KEY-----`))
6464

6565
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }

0 commit comments

Comments
 (0)