diff options
Diffstat (limited to 'ipn/ipnlocal')
| -rw-r--r-- | ipn/ipnlocal/breaktcp_darwin.go | 60 | ||||
| -rw-r--r-- | ipn/ipnlocal/breaktcp_linux.go | 60 | ||||
| -rw-r--r-- | ipn/ipnlocal/expiry_test.go | 602 | ||||
| -rw-r--r-- | ipn/ipnlocal/peerapi_h2c.go | 40 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/example.com-key.pem | 54 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/example.com.pem | 50 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/rootCA.pem | 58 |
7 files changed, 462 insertions, 462 deletions
diff --git a/ipn/ipnlocal/breaktcp_darwin.go b/ipn/ipnlocal/breaktcp_darwin.go index 13566198c..289e760e1 100644 --- a/ipn/ipnlocal/breaktcp_darwin.go +++ b/ipn/ipnlocal/breaktcp_darwin.go @@ -1,30 +1,30 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "log" - - "golang.org/x/sys/unix" -) - -func init() { - breakTCPConns = breakTCPConnsDarwin -} - -func breakTCPConnsDarwin() error { - var matched int - for fd := 0; fd < 1000; fd++ { - _, err := unix.GetsockoptTCPConnectionInfo(fd, unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO) - if err == nil { - matched++ - err = unix.Close(fd) - log.Printf("debug: closed TCP fd %v: %v", fd, err) - } - } - if matched == 0 { - log.Printf("debug: no TCP connections found") - } - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "log"
+
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ breakTCPConns = breakTCPConnsDarwin
+}
+
+func breakTCPConnsDarwin() error {
+ var matched int
+ for fd := 0; fd < 1000; fd++ {
+ _, err := unix.GetsockoptTCPConnectionInfo(fd, unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO)
+ if err == nil {
+ matched++
+ err = unix.Close(fd)
+ log.Printf("debug: closed TCP fd %v: %v", fd, err)
+ }
+ }
+ if matched == 0 {
+ log.Printf("debug: no TCP connections found")
+ }
+ return nil
+}
diff --git a/ipn/ipnlocal/breaktcp_linux.go b/ipn/ipnlocal/breaktcp_linux.go index b82f65212..d078103cf 100644 --- a/ipn/ipnlocal/breaktcp_linux.go +++ b/ipn/ipnlocal/breaktcp_linux.go @@ -1,30 +1,30 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "log" - - "golang.org/x/sys/unix" -) - -func init() { - breakTCPConns = breakTCPConnsLinux -} - -func breakTCPConnsLinux() error { - var matched int - for fd := 0; fd < 1000; fd++ { - _, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO) - if err == nil { - matched++ - err = unix.Close(fd) - log.Printf("debug: closed TCP fd %v: %v", fd, err) - } - } - if matched == 0 { - log.Printf("debug: no TCP connections found") - } - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "log"
+
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ breakTCPConns = breakTCPConnsLinux
+}
+
+func breakTCPConnsLinux() error {
+ var matched int
+ for fd := 0; fd < 1000; fd++ {
+ _, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO)
+ if err == nil {
+ matched++
+ err = unix.Close(fd)
+ log.Printf("debug: closed TCP fd %v: %v", fd, err)
+ }
+ }
+ if matched == 0 {
+ log.Printf("debug: no TCP connections found")
+ }
+ return nil
+}
diff --git a/ipn/ipnlocal/expiry_test.go b/ipn/ipnlocal/expiry_test.go index af1aa337b..efc18133f 100644 --- a/ipn/ipnlocal/expiry_test.go +++ b/ipn/ipnlocal/expiry_test.go @@ -1,301 +1,301 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "fmt" - "reflect" - "strings" - "testing" - "time" - - "tailscale.com/tailcfg" - "tailscale.com/tstest" - "tailscale.com/types/key" - "tailscale.com/types/netmap" -) - -func TestFlagExpiredPeers(t *testing.T) { - n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node { - n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry} - for _, f := range mod { - f(n) - } - return n - } - - now := time.Unix(1673373129, 0) - - timeInPast := now.Add(-1 * time.Hour) - timeInFuture := now.Add(1 * time.Hour) - - timeBeforeEpoch := flagExpiredPeersEpoch.Add(-1 * time.Second) - if now.Before(timeBeforeEpoch) { - panic("current time in test cannot be before epoch") - } - - var expiredKey key.NodePublic - if err := expiredKey.UnmarshalText([]byte("nodekey:6da774d5d7740000000000000000000000000000000000000000000000000000")); err != nil { - panic(err) - } - - tests := []struct { - name string - controlTime *time.Time - netmap *netmap.NetworkMap - want []tailcfg.NodeView - }{ - { - name: "no_expiry", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInFuture), - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInFuture), - }), - }, - { - name: "expiry", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInPast), - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInPast, func(n *tailcfg.Node) { - n.Expired = true - n.Key = expiredKey - }), - }), - }, - { - name: "bad_ControlTime", - // controlTime here is intentionally before our hardcoded epoch - controlTime: &timeBeforeEpoch, - - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // before ControlTime - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // should have expired, but ControlTime is before epoch - }), - }, - { - name: "tagged_node", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", time.Time{}), // tagged node; zero expiry - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", time.Time{}), // not expired - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - if tt.controlTime != nil { - em.onControlTime(*tt.controlTime) - } - em.flagExpiredPeers(tt.netmap, now) - if !reflect.DeepEqual(tt.netmap.Peers, tt.want) { - t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.netmap.Peers), formatNodes(tt.want)) - } - }) - } -} - -func TestNextPeerExpiry(t *testing.T) { - n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node { - n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry} - for _, f := range mod { - f(n) - } - return n - } - - now := time.Unix(1675725516, 0) - - noExpiry := time.Time{} - timeInPast := now.Add(-1 * time.Hour) - timeInFuture := now.Add(1 * time.Hour) - timeInMoreFuture := now.Add(2 * time.Hour) - - tests := []struct { - name string - netmap *netmap.NetworkMap - want time.Time - }{ - { - name: "no_expiry", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", noExpiry), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: noExpiry, - }, - { - name: "future_expiry_from_peer", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", timeInFuture), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", noExpiry), - }), - SelfNode: n(3, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_multiple_peers", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInMoreFuture), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_peer_and_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInMoreFuture), - }), - SelfNode: n(2, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "only_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{}), - SelfNode: n(1, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "peer_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - SelfNode: n(2, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "self_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - }), - SelfNode: n(2, "self", timeInPast).View(), - }, - want: timeInFuture, - }, - { - name: "all_nodes_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - SelfNode: n(2, "self", timeInPast).View(), - }, - want: noExpiry, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - got := em.nextPeerExpiry(tt.netmap, now) - if !got.Equal(tt.want) { - t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339)) - } else if !got.IsZero() && got.Before(now) { - t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339)) - } - }) - } - - t.Run("ClockSkew", func(t *testing.T) { - t.Logf("local time: %q", now.Format(time.RFC3339)) - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - - // The local clock is "running fast"; our clock skew is -2h - em.clockDelta.Store(-2 * time.Hour) - t.Logf("'real' time: %q", now.Add(-2*time.Hour).Format(time.RFC3339)) - - // If we don't adjust for the local time, this would return a - // time in the past. - nm := &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - } - got := em.nextPeerExpiry(nm, now) - want := now.Add(30 * time.Second) - if !got.Equal(want) { - t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339)) - } - }) -} - -func formatNodes(nodes []tailcfg.NodeView) string { - var sb strings.Builder - for i, n := range nodes { - if i > 0 { - sb.WriteString(", ") - } - fmt.Fprintf(&sb, "(%d, %q", n.ID(), n.Name()) - - if n.Online() != nil { - fmt.Fprintf(&sb, ", online=%v", *n.Online()) - } - if n.LastSeen() != nil { - fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen().Unix()) - } - if n.Key() != (key.NodePublic{}) { - fmt.Fprintf(&sb, ", key=%v", n.Key().String()) - } - if n.Expired() { - fmt.Fprintf(&sb, ", expired=true") - } - sb.WriteString(")") - } - return sb.String() -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "tailscale.com/tailcfg"
+ "tailscale.com/tstest"
+ "tailscale.com/types/key"
+ "tailscale.com/types/netmap"
+)
+
+func TestFlagExpiredPeers(t *testing.T) {
+ n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node {
+ n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry}
+ for _, f := range mod {
+ f(n)
+ }
+ return n
+ }
+
+ now := time.Unix(1673373129, 0)
+
+ timeInPast := now.Add(-1 * time.Hour)
+ timeInFuture := now.Add(1 * time.Hour)
+
+ timeBeforeEpoch := flagExpiredPeersEpoch.Add(-1 * time.Second)
+ if now.Before(timeBeforeEpoch) {
+ panic("current time in test cannot be before epoch")
+ }
+
+ var expiredKey key.NodePublic
+ if err := expiredKey.UnmarshalText([]byte("nodekey:6da774d5d7740000000000000000000000000000000000000000000000000000")); err != nil {
+ panic(err)
+ }
+
+ tests := []struct {
+ name string
+ controlTime *time.Time
+ netmap *netmap.NetworkMap
+ want []tailcfg.NodeView
+ }{
+ {
+ name: "no_expiry",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInFuture),
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInFuture),
+ }),
+ },
+ {
+ name: "expiry",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInPast),
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInPast, func(n *tailcfg.Node) {
+ n.Expired = true
+ n.Key = expiredKey
+ }),
+ }),
+ },
+ {
+ name: "bad_ControlTime",
+ // controlTime here is intentionally before our hardcoded epoch
+ controlTime: &timeBeforeEpoch,
+
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // before ControlTime
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // should have expired, but ControlTime is before epoch
+ }),
+ },
+ {
+ name: "tagged_node",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", time.Time{}), // tagged node; zero expiry
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", time.Time{}), // not expired
+ }),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+ if tt.controlTime != nil {
+ em.onControlTime(*tt.controlTime)
+ }
+ em.flagExpiredPeers(tt.netmap, now)
+ if !reflect.DeepEqual(tt.netmap.Peers, tt.want) {
+ t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.netmap.Peers), formatNodes(tt.want))
+ }
+ })
+ }
+}
+
+func TestNextPeerExpiry(t *testing.T) {
+ n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node {
+ n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry}
+ for _, f := range mod {
+ f(n)
+ }
+ return n
+ }
+
+ now := time.Unix(1675725516, 0)
+
+ noExpiry := time.Time{}
+ timeInPast := now.Add(-1 * time.Hour)
+ timeInFuture := now.Add(1 * time.Hour)
+ timeInMoreFuture := now.Add(2 * time.Hour)
+
+ tests := []struct {
+ name string
+ netmap *netmap.NetworkMap
+ want time.Time
+ }{
+ {
+ name: "no_expiry",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", noExpiry),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: noExpiry,
+ },
+ {
+ name: "future_expiry_from_peer",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", timeInFuture),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", noExpiry),
+ }),
+ SelfNode: n(3, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_multiple_peers",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInMoreFuture),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_peer_and_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInMoreFuture),
+ }),
+ SelfNode: n(2, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "only_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{}),
+ SelfNode: n(1, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "peer_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ SelfNode: n(2, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "self_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ }),
+ SelfNode: n(2, "self", timeInPast).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "all_nodes_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ SelfNode: n(2, "self", timeInPast).View(),
+ },
+ want: noExpiry,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+ got := em.nextPeerExpiry(tt.netmap, now)
+ if !got.Equal(tt.want) {
+ t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339))
+ } else if !got.IsZero() && got.Before(now) {
+ t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339))
+ }
+ })
+ }
+
+ t.Run("ClockSkew", func(t *testing.T) {
+ t.Logf("local time: %q", now.Format(time.RFC3339))
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+
+ // The local clock is "running fast"; our clock skew is -2h
+ em.clockDelta.Store(-2 * time.Hour)
+ t.Logf("'real' time: %q", now.Add(-2*time.Hour).Format(time.RFC3339))
+
+ // If we don't adjust for the local time, this would return a
+ // time in the past.
+ nm := &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ }
+ got := em.nextPeerExpiry(nm, now)
+ want := now.Add(30 * time.Second)
+ if !got.Equal(want) {
+ t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339))
+ }
+ })
+}
+
+func formatNodes(nodes []tailcfg.NodeView) string {
+ var sb strings.Builder
+ for i, n := range nodes {
+ if i > 0 {
+ sb.WriteString(", ")
+ }
+ fmt.Fprintf(&sb, "(%d, %q", n.ID(), n.Name())
+
+ if n.Online() != nil {
+ fmt.Fprintf(&sb, ", online=%v", *n.Online())
+ }
+ if n.LastSeen() != nil {
+ fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen().Unix())
+ }
+ if n.Key() != (key.NodePublic{}) {
+ fmt.Fprintf(&sb, ", key=%v", n.Key().String())
+ }
+ if n.Expired() {
+ fmt.Fprintf(&sb, ", expired=true")
+ }
+ sb.WriteString(")")
+ }
+ return sb.String()
+}
diff --git a/ipn/ipnlocal/peerapi_h2c.go b/ipn/ipnlocal/peerapi_h2c.go index fbfa86398..e6335fe2b 100644 --- a/ipn/ipnlocal/peerapi_h2c.go +++ b/ipn/ipnlocal/peerapi_h2c.go @@ -1,20 +1,20 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !ios && !android && !js - -package ipnlocal - -import ( - "net/http" - - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" -) - -func init() { - addH2C = func(s *http.Server) { - h2s := &http2.Server{} - s.Handler = h2c.NewHandler(s.Handler, h2s) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ios && !android && !js
+
+package ipnlocal
+
+import (
+ "net/http"
+
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+)
+
+func init() {
+ addH2C = func(s *http.Server) {
+ h2s := &http2.Server{}
+ s.Handler = h2c.NewHandler(s.Handler, h2s)
+ }
+}
diff --git a/ipn/ipnlocal/testdata/example.com-key.pem b/ipn/ipnlocal/testdata/example.com-key.pem index 06902f4c9..9020553f1 100644 --- a/ipn/ipnlocal/testdata/example.com-key.pem +++ b/ipn/ipnlocal/testdata/example.com-key.pem @@ -1,28 +1,28 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCejQaJrntrJSgE -QtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TVYZOX -xH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmLpXbn -ui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0GM1n9 -Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdpMVOg -w/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zNNivE -K1qaPS5RAgMBAAECggEAV9dAGQWPISR70CiKjLa5A60nbRHFQjackTE0c32daC6W -7dOYGsh/DxOMm8fyJqhp9nhEYJa3MbUWxU27ER3NbA6wrhM6gvqeKG8zYRhPNrGq -0o3vMdDPozb6cldZ0Fimz1jMO6h373NjtiyjxibWqkrLpRbaDtCq5EQKbMEcVa2D -Xt5hxCOaCA3OZ/mAcGUNFmDNgNsGP/r6eXdI5pbqnUNMPkv/JsHl8h2HuyKUm4hf -TRnXPAak6DkUod9QXYFKVBVPa5pjiO09e0aiMUvJ8vYd/6bNIsAKWLPa1PYuUE2l -kg8Nik+P/XLzffKsLxiFKY0nCqrorM9K5q7baofGdQKBgQDPujjebFg6OKw6MS3S -PESopvL//C/XgtgifcSSZCWzIZRVBVTbbJCGRtqFzF0XO4YRX3EOAyD/L7wYUPzO -+W3AU2W3/DVJYdcm2CASABbHNy0kk52LI0HHAssbFDgyB9XuuWP+vVZk7B5OmCAD -Bppuj6Mnu03i282nKNJzvRiVnwKBgQDDZUXv22K8y7GkKw/ZW/wQP2zBNtFc15he -1EOyUGHlXuQixnDSaqonkwec6IOlo7Sx/vwO/7+v4Jzc24Wq3DFAmMu/EYJgvI+m -m3kpB4H7Xus4JqnhxqN7GB7zOdguCWZF1HLemZNZlVrUjG5mQ9cizzvvYptnQDLq -FEJ1hddWDwKBgB+vy276Xfb7oCH8UH4KXXrQhK7RvEaGmgug3bRq/Gk3zRWvC4Ox -KtagxkK0qtqZZNkPkwJNLeJfWLTo3beAyuIUlqabHVHFT/mH7FRymQbofsVekyCf -TzBZV7wYuH3BPjv9IajBHwWkEvdwMyni/vmwhXXRF49schF2o6uuA6sHAoGBAL1J -Xnb+EKjUq0JedPwcIBOdXb3PXQKT2QgEmZAkTrHlOxx1INa2fh/YT4ext9a+wE2u -tn/RQeEfttY90z+yEASEAN0YGTWddYvxEW6t1z2stjGvQuN1ium0dEcrwkDW2jzL -knwSSqx+A3/kiw6GqeMO3wEIhYOArdIVzkwLXJABAoGAOXLGhz5u5FWjF3zAeYme -uHTU/3Z3jeI80PvShGrgAakPOBt3cIFpUaiOEslcqqgDUSGE3EnmkRqaEch+UapF -ty6Zz7cKjXhQSWOjew1uUW2ANNEpsnYbmZOOnfvosd7jfHSVbL6KIhWmIdC6h0NP -c/bJnTXEEVsWjLZTwYaq0Us= +-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCejQaJrntrJSgE
+QtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TVYZOX
+xH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmLpXbn
+ui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0GM1n9
+Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdpMVOg
+w/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zNNivE
+K1qaPS5RAgMBAAECggEAV9dAGQWPISR70CiKjLa5A60nbRHFQjackTE0c32daC6W
+7dOYGsh/DxOMm8fyJqhp9nhEYJa3MbUWxU27ER3NbA6wrhM6gvqeKG8zYRhPNrGq
+0o3vMdDPozb6cldZ0Fimz1jMO6h373NjtiyjxibWqkrLpRbaDtCq5EQKbMEcVa2D
+Xt5hxCOaCA3OZ/mAcGUNFmDNgNsGP/r6eXdI5pbqnUNMPkv/JsHl8h2HuyKUm4hf
+TRnXPAak6DkUod9QXYFKVBVPa5pjiO09e0aiMUvJ8vYd/6bNIsAKWLPa1PYuUE2l
+kg8Nik+P/XLzffKsLxiFKY0nCqrorM9K5q7baofGdQKBgQDPujjebFg6OKw6MS3S
+PESopvL//C/XgtgifcSSZCWzIZRVBVTbbJCGRtqFzF0XO4YRX3EOAyD/L7wYUPzO
++W3AU2W3/DVJYdcm2CASABbHNy0kk52LI0HHAssbFDgyB9XuuWP+vVZk7B5OmCAD
+Bppuj6Mnu03i282nKNJzvRiVnwKBgQDDZUXv22K8y7GkKw/ZW/wQP2zBNtFc15he
+1EOyUGHlXuQixnDSaqonkwec6IOlo7Sx/vwO/7+v4Jzc24Wq3DFAmMu/EYJgvI+m
+m3kpB4H7Xus4JqnhxqN7GB7zOdguCWZF1HLemZNZlVrUjG5mQ9cizzvvYptnQDLq
+FEJ1hddWDwKBgB+vy276Xfb7oCH8UH4KXXrQhK7RvEaGmgug3bRq/Gk3zRWvC4Ox
+KtagxkK0qtqZZNkPkwJNLeJfWLTo3beAyuIUlqabHVHFT/mH7FRymQbofsVekyCf
+TzBZV7wYuH3BPjv9IajBHwWkEvdwMyni/vmwhXXRF49schF2o6uuA6sHAoGBAL1J
+Xnb+EKjUq0JedPwcIBOdXb3PXQKT2QgEmZAkTrHlOxx1INa2fh/YT4ext9a+wE2u
+tn/RQeEfttY90z+yEASEAN0YGTWddYvxEW6t1z2stjGvQuN1ium0dEcrwkDW2jzL
+knwSSqx+A3/kiw6GqeMO3wEIhYOArdIVzkwLXJABAoGAOXLGhz5u5FWjF3zAeYme
+uHTU/3Z3jeI80PvShGrgAakPOBt3cIFpUaiOEslcqqgDUSGE3EnmkRqaEch+UapF
+ty6Zz7cKjXhQSWOjew1uUW2ANNEpsnYbmZOOnfvosd7jfHSVbL6KIhWmIdC6h0NP
+c/bJnTXEEVsWjLZTwYaq0Us=
-----END PRIVATE KEY-----
\ No newline at end of file diff --git a/ipn/ipnlocal/testdata/example.com.pem b/ipn/ipnlocal/testdata/example.com.pem index 588850813..65e7110a8 100644 --- a/ipn/ipnlocal/testdata/example.com.pem +++ b/ipn/ipnlocal/testdata/example.com.pem @@ -1,26 +1,26 @@ ------BEGIN CERTIFICATE----- -MIIEcDCCAtigAwIBAgIRAPmUKRkyFAkVVxFblB/233cwDQYJKoZIhvcNAQELBQAw -gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv -bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB -MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh -ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMjUwNTA3MTkzNDE4 -WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4 -BgNVBAsMMWZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hhZWwgSi4gRnJv -bWJlcmdlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCejQaJrntr -JSgEQtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TV -YZOXxH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmL -pXbnui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0G -M1n9Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdp -MVOgw/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zN -NivEK1qaPS5RAgMBAAGjYDBeMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr -BgEFBQcDATAfBgNVHSMEGDAWgBTXyq2jQVrnqQKL8fB9C4L0QJftwDAWBgNVHREE -DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAQWzpOaBkRR4M+WqB -CsT4ARyM6WpZ+jpeSblCzPdlDRW+50G1HV7K930zayq4DwncPY/SqSn0Q31WuzZv -bTWHkWa+MLPGYANHsusOmMR8Eh16G4+5+GGf8psWa0npAYO35cuNkyyCCc1LEB4M -NrzCB2+KZ+SyOdfCCA5VzEKN3I8wvVLaYovi24Zjwv+0uETG92TlZmLQRhj8uPxN -deeLM45aBkQZSYCbGMDVDK/XYKBkNLn3kxD/eZeXxxr41v4pH44+46FkYcYJzdn8 -ccAg5LRGieqTozhLiXARNK1vTy6kR1l/Az8DIx6GN4sP2/LMFYFijiiOCDKS1wWA -xQgZeHt4GIuBym+Kd+Z5KXcP0AT+47Cby3+B10Kq8vHwjTELiF0UFeEYYMdynPAW -pbEwVLhsfMsBqFtj3dsxHr8Kz3rnarOYzkaw7EMZnLAthb2CN7y5uGV9imQC5RMI -/qZdRSuCYZ3A1E/WJkGbPY/YdPql/IE+LIAgKGFHZZNftBCo +-----BEGIN CERTIFICATE-----
+MIIEcDCCAtigAwIBAgIRAPmUKRkyFAkVVxFblB/233cwDQYJKoZIhvcNAQELBQAw
+gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv
+bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB
+MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh
+ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMjUwNTA3MTkzNDE4
+WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4
+BgNVBAsMMWZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hhZWwgSi4gRnJv
+bWJlcmdlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCejQaJrntr
+JSgEQtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TV
+YZOXxH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmL
+pXbnui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0G
+M1n9Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdp
+MVOgw/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zN
+NivEK1qaPS5RAgMBAAGjYDBeMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
+BgEFBQcDATAfBgNVHSMEGDAWgBTXyq2jQVrnqQKL8fB9C4L0QJftwDAWBgNVHREE
+DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAQWzpOaBkRR4M+WqB
+CsT4ARyM6WpZ+jpeSblCzPdlDRW+50G1HV7K930zayq4DwncPY/SqSn0Q31WuzZv
+bTWHkWa+MLPGYANHsusOmMR8Eh16G4+5+GGf8psWa0npAYO35cuNkyyCCc1LEB4M
+NrzCB2+KZ+SyOdfCCA5VzEKN3I8wvVLaYovi24Zjwv+0uETG92TlZmLQRhj8uPxN
+deeLM45aBkQZSYCbGMDVDK/XYKBkNLn3kxD/eZeXxxr41v4pH44+46FkYcYJzdn8
+ccAg5LRGieqTozhLiXARNK1vTy6kR1l/Az8DIx6GN4sP2/LMFYFijiiOCDKS1wWA
+xQgZeHt4GIuBym+Kd+Z5KXcP0AT+47Cby3+B10Kq8vHwjTELiF0UFeEYYMdynPAW
+pbEwVLhsfMsBqFtj3dsxHr8Kz3rnarOYzkaw7EMZnLAthb2CN7y5uGV9imQC5RMI
+/qZdRSuCYZ3A1E/WJkGbPY/YdPql/IE+LIAgKGFHZZNftBCo
-----END CERTIFICATE-----
\ No newline at end of file diff --git a/ipn/ipnlocal/testdata/rootCA.pem b/ipn/ipnlocal/testdata/rootCA.pem index 88a16f47a..28bd25467 100644 --- a/ipn/ipnlocal/testdata/rootCA.pem +++ b/ipn/ipnlocal/testdata/rootCA.pem @@ -1,30 +1,30 @@ ------BEGIN CERTIFICATE----- -MIIFEDCCA3igAwIBAgIRANf5NdPojIfj70wMfJVYUg8wDQYJKoZIhvcNAQELBQAw -gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv -bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB -MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh -ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMzMwMjA3MjAzNDE4 -WjCBnzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTowOAYDVQQLDDFm -cm9tYmVyZ2VyQHN0YXJkdXN0LmxvY2FsIChNaWNoYWVsIEouIEZyb21iZXJnZXIp -MUEwPwYDVQQDDDhta2NlcnQgZnJvbWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWlj -aGFlbCBKLiBGcm9tYmVyZ2VyKTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC -ggGBAL5uXNnrZ6dgjcvK0Hc7ZNUIRYEWst9qbO0P9H7le08pJ6d9T2BUWruZtVjk -Q12msv5/bVWHhVk8dZclI9FLXuMsIrocH8bsoP4wruPMyRyp6EedSKODN51fFSRv -/jHbS5vzUVAWTYy9qYmd6qL0uhsHCZCCT6gfigamHPUFKM3sHDn5ZHWvySMwcyGl -AicmPAIkBWqiCZAkB5+WM7+oyRLjmrIalfWIZYxW/rojGLwTfneHv6J5WjVQnpJB -ayWCzCzaiXukK9MeBWeTOe8UfVN0Engd74/rjLWvjbfC+uZSr6RVkZvs2jANLwPF -zgzBPHgRPfAhszU1NNAMjnNQ47+OMOTKRt7e6jYzhO5fyO1qVAAvGBqcfpj+JfDk -cccaUMhUvdiGrhGf1V1tN/PislxvALirzcFipjD01isBKwn0fxRugzvJNrjEo8RA -RvbcdeKcwex7M0o/Cd0+G2B13gZNOFvR33PmG7iTpp7IUrUKfQg28I83Sp8tMY3s -ljJSawIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIB -ADAdBgNVHQ4EFgQU18qto0Fa56kCi/HwfQuC9ECX7cAwDQYJKoZIhvcNAQELBQAD -ggGBAAzs96LwZVOsRSlBdQqMo8oMAvs7HgnYbXt8SqaACLX3+kJ3cV/vrCE3iJrW -ma4CiQbxS/HqsiZjota5m4lYeEevRnUDpXhp+7ugZTiz33Flm1RU99c9UYfQ+919 -ANPAKeqNpoPco/HF5Bz0ocepjcfKQrVZZNTj6noLs8o12FHBLO5976AcF9mqlNfh -8/F0gDJXq6+x7VT5y8u0rY004XKPRe3CklRt8kpeMiP6mhRyyUehOaHeIbNx8ubi -Pi44ByN/ueAnuRhF9zYtyZVZZOaSLysJge01tuPXF8rBXGruoJIv35xTTBa9BzaP -YDOGbGn1ZnajdNagHqCba8vjTLDSpqMvgRj3TFrGHdETA2LDQat38uVxX8gxm68K -va5Tyv7n+6BQ5YTpJjTPnmSJKaXZrrhdLPvG0OU2TxeEsvbcm5LFQofirOOw86Se -vzF2cQ94mmHRZiEk0Av3NO0jF93ELDrBCuiccVyEKq6TknuvPQlutCXKDOYSEb8I -MHctBg== +-----BEGIN CERTIFICATE-----
+MIIFEDCCA3igAwIBAgIRANf5NdPojIfj70wMfJVYUg8wDQYJKoZIhvcNAQELBQAw
+gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv
+bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB
+MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh
+ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMzMwMjA3MjAzNDE4
+WjCBnzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTowOAYDVQQLDDFm
+cm9tYmVyZ2VyQHN0YXJkdXN0LmxvY2FsIChNaWNoYWVsIEouIEZyb21iZXJnZXIp
+MUEwPwYDVQQDDDhta2NlcnQgZnJvbWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWlj
+aGFlbCBKLiBGcm9tYmVyZ2VyKTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
+ggGBAL5uXNnrZ6dgjcvK0Hc7ZNUIRYEWst9qbO0P9H7le08pJ6d9T2BUWruZtVjk
+Q12msv5/bVWHhVk8dZclI9FLXuMsIrocH8bsoP4wruPMyRyp6EedSKODN51fFSRv
+/jHbS5vzUVAWTYy9qYmd6qL0uhsHCZCCT6gfigamHPUFKM3sHDn5ZHWvySMwcyGl
+AicmPAIkBWqiCZAkB5+WM7+oyRLjmrIalfWIZYxW/rojGLwTfneHv6J5WjVQnpJB
+ayWCzCzaiXukK9MeBWeTOe8UfVN0Engd74/rjLWvjbfC+uZSr6RVkZvs2jANLwPF
+zgzBPHgRPfAhszU1NNAMjnNQ47+OMOTKRt7e6jYzhO5fyO1qVAAvGBqcfpj+JfDk
+cccaUMhUvdiGrhGf1V1tN/PislxvALirzcFipjD01isBKwn0fxRugzvJNrjEo8RA
+RvbcdeKcwex7M0o/Cd0+G2B13gZNOFvR33PmG7iTpp7IUrUKfQg28I83Sp8tMY3s
+ljJSawIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIB
+ADAdBgNVHQ4EFgQU18qto0Fa56kCi/HwfQuC9ECX7cAwDQYJKoZIhvcNAQELBQAD
+ggGBAAzs96LwZVOsRSlBdQqMo8oMAvs7HgnYbXt8SqaACLX3+kJ3cV/vrCE3iJrW
+ma4CiQbxS/HqsiZjota5m4lYeEevRnUDpXhp+7ugZTiz33Flm1RU99c9UYfQ+919
+ANPAKeqNpoPco/HF5Bz0ocepjcfKQrVZZNTj6noLs8o12FHBLO5976AcF9mqlNfh
+8/F0gDJXq6+x7VT5y8u0rY004XKPRe3CklRt8kpeMiP6mhRyyUehOaHeIbNx8ubi
+Pi44ByN/ueAnuRhF9zYtyZVZZOaSLysJge01tuPXF8rBXGruoJIv35xTTBa9BzaP
+YDOGbGn1ZnajdNagHqCba8vjTLDSpqMvgRj3TFrGHdETA2LDQat38uVxX8gxm68K
+va5Tyv7n+6BQ5YTpJjTPnmSJKaXZrrhdLPvG0OU2TxeEsvbcm5LFQofirOOw86Se
+vzF2cQ94mmHRZiEk0Av3NO0jF93ELDrBCuiccVyEKq6TknuvPQlutCXKDOYSEb8I
+MHctBg==
-----END CERTIFICATE-----
\ No newline at end of file |
