Skip to content

Commit 6b60550

Browse files
neildgopherbot
authored andcommitted
[release-branch.go1.24] 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. For #70530 Fixes #71212 Fixes CVE-2024-45336 Change-Id: Ia58a2e10d33d6b0cc7220935e771450e5c34de72 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]> (cherry picked from commit 2889169b87a61f1218a02994feb80fd3d8bfa87c) Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1766 Reviewed-on: https://go-review.googlesource.com/c/go/+/643100 Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 468fad4 commit 6b60550

File tree

3 files changed

+154
-106
lines changed

3 files changed

+154
-106
lines changed

src/net/http/client.go

+34-31
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,9 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
610610
reqBodyClosed = false // have we closed the current req.Body?
611611

612612
// Redirect behavior:
613-
redirectMethod string
614-
includeBody = true
613+
redirectMethod string
614+
includeBody = true
615+
stripSensitiveHeaders = false
615616
)
616617
uerr := func(err error) error {
617618
// the body may have been closed already by c.send()
@@ -678,7 +679,12 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
678679
// in case the user set Referer on their first request.
679680
// If they really want to override, they can do it in
680681
// their CheckRedirect func.
681-
copyHeaders(req)
682+
if !stripSensitiveHeaders && reqs[0].URL.Host != req.URL.Host {
683+
if !shouldCopyHeaderOnRedirect(reqs[0].URL, req.URL) {
684+
stripSensitiveHeaders = true
685+
}
686+
}
687+
copyHeaders(req, stripSensitiveHeaders)
682688

683689
// Add the Referer header from the most recent
684690
// 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

@@ -979,28 +987,23 @@ func (b *cancelTimerBody) Close() error {
979987
return err
980988
}
981989

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

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

src/net/http/client_test.go

+78-33
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,55 @@ func testClientCopyHeadersOnRedirect(t *testing.T, mode testMode) {
15361536
}
15371537
}
15381538

1539+
// Issue #70530: Once we strip a header on a redirect to a different host,
1540+
// the header should stay stripped across any further redirects.
1541+
func TestClientStripHeadersOnRepeatedRedirect(t *testing.T) {
1542+
run(t, testClientStripHeadersOnRepeatedRedirect)
1543+
}
1544+
func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode testMode) {
1545+
var proto string
1546+
ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
1547+
if r.Host+r.URL.Path != "a.example.com/" {
1548+
if h := r.Header.Get("Authorization"); h != "" {
1549+
t.Errorf("on request to %v%v, Authorization=%q, want no header", r.Host, r.URL.Path, h)
1550+
}
1551+
}
1552+
// Follow a chain of redirects from a to b and back to a.
1553+
// The Authorization header is stripped on the first redirect to b,
1554+
// and stays stripped even if we're sent back to a.
1555+
switch r.Host + r.URL.Path {
1556+
case "a.example.com/":
1557+
Redirect(w, r, proto+"://b.example.com/", StatusFound)
1558+
case "b.example.com/":
1559+
Redirect(w, r, proto+"://b.example.com/redirect", StatusFound)
1560+
case "b.example.com/redirect":
1561+
Redirect(w, r, proto+"://a.example.com/redirect", StatusFound)
1562+
case "a.example.com/redirect":
1563+
w.Header().Set("X-Done", "true")
1564+
default:
1565+
t.Errorf("unexpected request to %v", r.URL)
1566+
}
1567+
})).ts
1568+
proto, _, _ = strings.Cut(ts.URL, ":")
1569+
1570+
c := ts.Client()
1571+
c.Transport.(*Transport).Dial = func(_ string, _ string) (net.Conn, error) {
1572+
return net.Dial("tcp", ts.Listener.Addr().String())
1573+
}
1574+
1575+
req, _ := NewRequest("GET", proto+"://a.example.com/", nil)
1576+
req.Header.Add("Cookie", "foo=bar")
1577+
req.Header.Add("Authorization", "secretpassword")
1578+
res, err := c.Do(req)
1579+
if err != nil {
1580+
t.Fatal(err)
1581+
}
1582+
defer res.Body.Close()
1583+
if res.Header.Get("X-Done") != "true" {
1584+
t.Fatalf("response missing expected header: X-Done=true")
1585+
}
1586+
}
1587+
15391588
// Issue 22233: copy host when Client follows a relative redirect.
15401589
func TestClientCopyHostOnRedirect(t *testing.T) { run(t, testClientCopyHostOnRedirect) }
15411590
func testClientCopyHostOnRedirect(t *testing.T, mode testMode) {
@@ -1702,43 +1751,39 @@ func testClientAltersCookiesOnRedirect(t *testing.T, mode testMode) {
17021751
// Part of Issue 4800
17031752
func TestShouldCopyHeaderOnRedirect(t *testing.T) {
17041753
tests := []struct {
1705-
header string
17061754
initialURL string
17071755
destURL string
17081756
want bool
17091757
}{
1710-
{"User-Agent", "http://foo.com/", "http://bar.com/", true},
1711-
{"X-Foo", "http://foo.com/", "http://bar.com/", true},
1712-
17131758
// Sensitive headers:
1714-
{"cookie", "http://foo.com/", "http://bar.com/", false},
1715-
{"cookie2", "http://foo.com/", "http://bar.com/", false},
1716-
{"authorization", "http://foo.com/", "http://bar.com/", false},
1717-
{"authorization", "http://foo.com/", "https://foo.com/", true},
1718-
{"authorization", "http://foo.com:1234/", "http://foo.com:4321/", true},
1719-
{"www-authenticate", "http://foo.com/", "http://bar.com/", false},
1720-
{"authorization", "http://foo.com/", "http://[::1%25.foo.com]/", false},
1759+
{"http://foo.com/", "http://bar.com/", false},
1760+
{"http://foo.com/", "http://bar.com/", false},
1761+
{"http://foo.com/", "http://bar.com/", false},
1762+
{"http://foo.com/", "https://foo.com/", true},
1763+
{"http://foo.com:1234/", "http://foo.com:4321/", true},
1764+
{"http://foo.com/", "http://bar.com/", false},
1765+
{"http://foo.com/", "http://[::1%25.foo.com]/", false},
17211766

17221767
// But subdomains should work:
1723-
{"www-authenticate", "http://foo.com/", "http://foo.com/", true},
1724-
{"www-authenticate", "http://foo.com/", "http://sub.foo.com/", true},
1725-
{"www-authenticate", "http://foo.com/", "http://notfoo.com/", false},
1726-
{"www-authenticate", "http://foo.com/", "https://foo.com/", true},
1727-
{"www-authenticate", "http://foo.com:80/", "http://foo.com/", true},
1728-
{"www-authenticate", "http://foo.com:80/", "http://sub.foo.com/", true},
1729-
{"www-authenticate", "http://foo.com:443/", "https://foo.com/", true},
1730-
{"www-authenticate", "http://foo.com:443/", "https://sub.foo.com/", true},
1731-
{"www-authenticate", "http://foo.com:1234/", "http://foo.com/", true},
1732-
1733-
{"authorization", "http://foo.com/", "http://foo.com/", true},
1734-
{"authorization", "http://foo.com/", "http://sub.foo.com/", true},
1735-
{"authorization", "http://foo.com/", "http://notfoo.com/", false},
1736-
{"authorization", "http://foo.com/", "https://foo.com/", true},
1737-
{"authorization", "http://foo.com:80/", "http://foo.com/", true},
1738-
{"authorization", "http://foo.com:80/", "http://sub.foo.com/", true},
1739-
{"authorization", "http://foo.com:443/", "https://foo.com/", true},
1740-
{"authorization", "http://foo.com:443/", "https://sub.foo.com/", true},
1741-
{"authorization", "http://foo.com:1234/", "http://foo.com/", true},
1768+
{"http://foo.com/", "http://foo.com/", true},
1769+
{"http://foo.com/", "http://sub.foo.com/", true},
1770+
{"http://foo.com/", "http://notfoo.com/", false},
1771+
{"http://foo.com/", "https://foo.com/", true},
1772+
{"http://foo.com:80/", "http://foo.com/", true},
1773+
{"http://foo.com:80/", "http://sub.foo.com/", true},
1774+
{"http://foo.com:443/", "https://foo.com/", true},
1775+
{"http://foo.com:443/", "https://sub.foo.com/", true},
1776+
{"http://foo.com:1234/", "http://foo.com/", true},
1777+
1778+
{"http://foo.com/", "http://foo.com/", true},
1779+
{"http://foo.com/", "http://sub.foo.com/", true},
1780+
{"http://foo.com/", "http://notfoo.com/", false},
1781+
{"http://foo.com/", "https://foo.com/", true},
1782+
{"http://foo.com:80/", "http://foo.com/", true},
1783+
{"http://foo.com:80/", "http://sub.foo.com/", true},
1784+
{"http://foo.com:443/", "https://foo.com/", true},
1785+
{"http://foo.com:443/", "https://sub.foo.com/", true},
1786+
{"http://foo.com:1234/", "http://foo.com/", true},
17421787
}
17431788
for i, tt := range tests {
17441789
u0, err := url.Parse(tt.initialURL)
@@ -1751,10 +1796,10 @@ func TestShouldCopyHeaderOnRedirect(t *testing.T) {
17511796
t.Errorf("%d. dest URL %q parse error: %v", i, tt.destURL, err)
17521797
continue
17531798
}
1754-
got := Export_shouldCopyHeaderOnRedirect(tt.header, u0, u1)
1799+
got := Export_shouldCopyHeaderOnRedirect(u0, u1)
17551800
if got != tt.want {
1756-
t.Errorf("%d. shouldCopyHeaderOnRedirect(%q, %q => %q) = %v; want %v",
1757-
i, tt.header, tt.initialURL, tt.destURL, got, tt.want)
1801+
t.Errorf("%d. shouldCopyHeaderOnRedirect(%q => %q) = %v; want %v",
1802+
i, tt.initialURL, tt.destURL, got, tt.want)
17581803
}
17591804
}
17601805
}

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

+42-42
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)