Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d375be9b6 | ||
|
|
b4ddeeda5b | ||
|
|
edc325d13a | ||
|
|
eab9315960 | ||
|
|
ddfa82138e | ||
|
|
d08e19beac | ||
|
|
12e1fcd8c0 | ||
|
|
3f8f0645c2 | ||
|
|
5df3b59941 | ||
|
|
2e82bdd171 | ||
|
|
64e0e99383 | ||
|
|
23b1b90df2 | ||
|
|
e98487292d | ||
|
|
183630ada7 | ||
|
|
a9e447dde7 | ||
|
|
64c3993f5c | ||
|
|
5fe8145aca | ||
|
|
043e94c17a | ||
|
|
0d502d7cd6 | ||
|
|
0ff60057bb | ||
|
|
8e447d8cc5 | ||
|
|
22c9997952 | ||
|
|
d0b316056b | ||
|
|
16222386f4 | ||
|
|
a4e984136a | ||
|
|
3ef8056490 | ||
|
|
a8cc953517 | ||
|
|
7d6d3f5d4a | ||
|
|
1faeef9713 |
@@ -431,6 +431,14 @@ func (s *String) readBase128Int(out *int) bool {
|
||||
}
|
||||
ret <<= 7
|
||||
b := s.read(1)[0]
|
||||
|
||||
// ITU-T X.690, section 8.19.2:
|
||||
// The subidentifier shall be encoded in the fewest possible octets,
|
||||
// that is, the leading octet of the subidentifier shall not have the value 0x80.
|
||||
if i == 0 && b == 0x80 {
|
||||
return false
|
||||
}
|
||||
|
||||
ret |= int(b & 0x7f)
|
||||
if b&0x80 == 0 {
|
||||
*out = ret
|
||||
|
||||
@@ -276,6 +276,7 @@ func TestASN1ObjectIdentifier(t *testing.T) {
|
||||
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
|
||||
{[]byte{6, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
|
||||
}
|
||||
|
||||
for i, test := range testData {
|
||||
|
||||
@@ -3,9 +3,9 @@ module golang.org/x/crypto
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/term v0.7.0
|
||||
golang.org/x/net v0.10.0 // tagx:ignore
|
||||
golang.org/x/sys v0.12.0
|
||||
golang.org/x/term v0.12.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.9.0 // indirect
|
||||
require golang.org/x/text v0.13.0 // indirect
|
||||
|
||||
@@ -7,8 +7,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -18,19 +18,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CommandContext is like exec.CommandContext, but:
|
||||
// - skips t if the platform does not support os/exec,
|
||||
// - sends SIGQUIT (if supported by the platform) instead of SIGKILL
|
||||
// in its Cancel function
|
||||
// - if the test has a deadline, adds a Context timeout and WaitDelay
|
||||
// for an arbitrary grace period before the test's deadline expires,
|
||||
// - fails the test if the command does not complete before the test's deadline, and
|
||||
// - sets a Cleanup function that verifies that the test did not leak a subprocess.
|
||||
func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
cancelCtx context.CancelFunc
|
||||
gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging)
|
||||
)
|
||||
|
||||
if t, ok := t.(interface {
|
||||
testing.TB
|
||||
Deadline() (time.Time, bool)
|
||||
}); ok {
|
||||
if td, ok := t.Deadline(); ok {
|
||||
// Start with a minimum grace period, just long enough to consume the
|
||||
// output of a reasonable program after it terminates.
|
||||
gracePeriod = 100 * time.Millisecond
|
||||
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
|
||||
scale, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err)
|
||||
}
|
||||
gracePeriod *= time.Duration(scale)
|
||||
}
|
||||
|
||||
// If time allows, increase the termination grace period to 5% of the
|
||||
// test's remaining time.
|
||||
testTimeout := time.Until(td)
|
||||
if gp := testTimeout / 20; gp > gracePeriod {
|
||||
gracePeriod = gp
|
||||
}
|
||||
|
||||
// When we run commands that execute subprocesses, we want to reserve two
|
||||
// grace periods to clean up: one for the delay between the first
|
||||
// termination signal being sent (via the Cancel callback when the Context
|
||||
// expires) and the process being forcibly terminated (via the WaitDelay
|
||||
// field), and a second one for the delay becween the process being
|
||||
// terminated and and the test logging its output for debugging.
|
||||
//
|
||||
// (We want to ensure that the test process itself has enough time to
|
||||
// log the output before it is also terminated.)
|
||||
cmdTimeout := testTimeout - 2*gracePeriod
|
||||
|
||||
if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout {
|
||||
// Either ctx doesn't have a deadline, or its deadline would expire
|
||||
// after (or too close before) the test has already timed out.
|
||||
// Add a shorter timeout so that the test will produce useful output.
|
||||
ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
// Set the Cancel and WaitDelay fields only if present (go 1.20 and later).
|
||||
// TODO: When Go 1.19 is no longer supported, remove this use of reflection
|
||||
// and instead set the fields directly.
|
||||
if cmdCancel := reflect.ValueOf(cmd).Elem().FieldByName("Cancel"); cmdCancel.IsValid() {
|
||||
cmdCancel.Set(reflect.ValueOf(func() error {
|
||||
if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded {
|
||||
// The command timed out due to running too close to the test's deadline.
|
||||
// There is no way the test did that intentionally — it's too close to the
|
||||
// wire! — so mark it as a test failure. That way, if the test expects the
|
||||
// command to fail for some other reason, it doesn't have to distinguish
|
||||
// between that reason and a timeout.
|
||||
t.Errorf("test timed out while running command: %v", cmd)
|
||||
} else {
|
||||
// The command is being terminated due to ctx being canceled, but
|
||||
// apparently not due to an explicit test deadline that we added.
|
||||
// Log that information in case it is useful for diagnosing a failure,
|
||||
// but don't actually fail the test because of it.
|
||||
t.Logf("%v: terminating command: %v", ctx.Err(), cmd)
|
||||
}
|
||||
return cmd.Process.Signal(Sigquit)
|
||||
}))
|
||||
}
|
||||
if cmdWaitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); cmdWaitDelay.IsValid() {
|
||||
cmdWaitDelay.Set(reflect.ValueOf(gracePeriod))
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if cancelCtx != nil {
|
||||
cancelCtx()
|
||||
}
|
||||
if cmd.Process != nil && cmd.ProcessState == nil {
|
||||
t.Errorf("command was started, but test did not wait for it to complete: %v", cmd)
|
||||
}
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Command is like exec.Command, but applies the same changes as
|
||||
// testenv.CommandContext (with a default Context).
|
||||
func Command(t testing.TB, name string, args ...string) *exec.Cmd {
|
||||
t.Helper()
|
||||
return CommandContext(t, context.Background(), name, args...)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows || plan9 || (js && wasm) || wasip1
|
||||
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||
// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
|
||||
var Sigquit = os.Kill
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix
|
||||
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||
// Send SIGQUIT to get a stack trace.
|
||||
var Sigquit = syscall.SIGQUIT
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -28,6 +29,11 @@ const wycheproofModVer = "v0.0.0-20191219022705-2196000605e4"
|
||||
var wycheproofTestVectorsDir string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if flag.Lookup("test.short").Value.(flag.Getter).Get().(bool) {
|
||||
log.Println("skipping test that downloads testdata via 'go mod download' in short mode")
|
||||
os.Exit(0)
|
||||
}
|
||||
if _, err := exec.LookPath("go"); err != nil {
|
||||
log.Printf("skipping test because 'go' command is unavailable: %v", err)
|
||||
os.Exit(0)
|
||||
@@ -42,8 +48,6 @@ func TestMain(m *testing.M) {
|
||||
// can be used in the following tests.
|
||||
path := "github.com/google/wycheproof@" + wycheproofModVer
|
||||
cmd := exec.Command("go", "mod", "download", "-json", path)
|
||||
// TODO: enable the sumdb once the Trybots proxy supports it.
|
||||
cmd.Env = append(os.Environ(), "GONOSUMDB=*")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
|
||||
|
||||
@@ -29,6 +29,9 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
||||
// types supported vary by platform.
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping on windows, we don't support connecting to the ssh-agent via a named pipe")
|
||||
}
|
||||
|
||||
bin, err := exec.LookPath("ssh-agent")
|
||||
if err != nil {
|
||||
@@ -366,7 +369,8 @@ func TestAuth(t *testing.T) {
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
t.Errorf("NewServerConn error: %v", err)
|
||||
return
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
@@ -53,10 +53,11 @@ func TestSetupForwardAgent(t *testing.T) {
|
||||
incoming := make(chan *ssh.ServerConn, 1)
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
incoming <- conn
|
||||
if err != nil {
|
||||
t.Errorf("NewServerConn error: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{
|
||||
@@ -71,8 +72,10 @@ func TestSetupForwardAgent(t *testing.T) {
|
||||
if err := ForwardToRemote(client, socket); err != nil {
|
||||
t.Fatalf("SetupForwardAgent: %v", err)
|
||||
}
|
||||
|
||||
server := <-incoming
|
||||
if server == nil {
|
||||
t.Fatal("Unable to get server")
|
||||
}
|
||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||
|
||||
@@ -6,6 +6,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -90,16 +91,16 @@ func BenchmarkEndToEnd(b *testing.B) {
|
||||
go func() {
|
||||
newCh, err := server.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Client: %v", err)
|
||||
panic(fmt.Sprintf("Client: %v", err))
|
||||
}
|
||||
ch, incoming, err := newCh.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Accept: %v", err)
|
||||
panic(fmt.Sprintf("Accept: %v", err))
|
||||
}
|
||||
go DiscardRequests(incoming)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := io.ReadFull(ch, output); err != nil {
|
||||
b.Fatalf("ReadFull: %v", err)
|
||||
panic(fmt.Sprintf("ReadFull: %v", err))
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
|
||||
@@ -955,3 +955,124 @@ func TestAuthMethodGSSAPIWithMIC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompatibleAlgoAndSignatures(t *testing.T) {
|
||||
type testcase struct {
|
||||
algo string
|
||||
sigFormat string
|
||||
compatible bool
|
||||
}
|
||||
testcases := []*testcase{
|
||||
{
|
||||
KeyAlgoRSA,
|
||||
KeyAlgoRSA,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSA,
|
||||
KeyAlgoRSASHA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSA,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSASHA256,
|
||||
KeyAlgoRSA,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSASHA512,
|
||||
KeyAlgoRSA,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSASHA512,
|
||||
KeyAlgoRSASHA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSASHA256,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoRSASHA512,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSAv01,
|
||||
KeyAlgoRSA,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSAv01,
|
||||
KeyAlgoRSASHA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSAv01,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSASHA256v01,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSASHA512v01,
|
||||
KeyAlgoRSASHA512,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSASHA512v01,
|
||||
KeyAlgoRSASHA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSASHA256v01,
|
||||
CertAlgoRSAv01,
|
||||
true,
|
||||
},
|
||||
{
|
||||
CertAlgoRSAv01,
|
||||
CertAlgoRSASHA512v01,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoECDSA256,
|
||||
KeyAlgoRSA,
|
||||
false,
|
||||
},
|
||||
{
|
||||
KeyAlgoECDSA256,
|
||||
KeyAlgoECDSA521,
|
||||
false,
|
||||
},
|
||||
{
|
||||
KeyAlgoECDSA256,
|
||||
KeyAlgoECDSA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
KeyAlgoECDSA256,
|
||||
KeyAlgoED25519,
|
||||
false,
|
||||
},
|
||||
{
|
||||
KeyAlgoED25519,
|
||||
KeyAlgoED25519,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
if isAlgoCompatible(c.algo, c.sigFormat) != c.compatible {
|
||||
t.Errorf("algorithm %q, signature format %q, expected compatible to be %t", c.algo, c.sigFormat, c.compatible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,3 +254,93 @@ func TestNewClientConn(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedAlgorithm(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
config Config
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
"unsupported KEX",
|
||||
Config{
|
||||
KeyExchanges: []string{"unsupported"},
|
||||
},
|
||||
"no common algorithm",
|
||||
},
|
||||
{
|
||||
"unsupported and supported KEXs",
|
||||
Config{
|
||||
KeyExchanges: []string{"unsupported", kexAlgoCurve25519SHA256},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"unsupported cipher",
|
||||
Config{
|
||||
Ciphers: []string{"unsupported"},
|
||||
},
|
||||
"no common algorithm",
|
||||
},
|
||||
{
|
||||
"unsupported and supported ciphers",
|
||||
Config{
|
||||
Ciphers: []string{"unsupported", chacha20Poly1305ID},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"unsupported MAC",
|
||||
Config{
|
||||
MACs: []string{"unsupported"},
|
||||
// MAC is used for non AAED ciphers.
|
||||
Ciphers: []string{"aes256-ctr"},
|
||||
},
|
||||
"no common algorithm",
|
||||
},
|
||||
{
|
||||
"unsupported and supported MACs",
|
||||
Config{
|
||||
MACs: []string{"unsupported", "hmac-sha2-256-etm@openssh.com"},
|
||||
// MAC is used for non AAED ciphers.
|
||||
Ciphers: []string{"aes256-ctr"},
|
||||
},
|
||||
"",
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
Config: tt.config,
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
return &Permissions{}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
clientConf := &ClientConfig{
|
||||
User: "testuser",
|
||||
Config: tt.config,
|
||||
Auth: []AuthMethod{
|
||||
Password("testpw"),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||
if err != nil {
|
||||
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
|
||||
t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
|
||||
}
|
||||
} else if tt.wantError != "" {
|
||||
t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+38
-13
@@ -49,7 +49,8 @@ var supportedKexAlgos = []string{
|
||||
// P384 and P521 are not constant-time yet, but since we don't
|
||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
kexAlgoDH14SHA256, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
kexAlgoDH14SHA256, kexAlgoDH16SHA512, kexAlgoDH14SHA1,
|
||||
kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden
|
||||
@@ -59,8 +60,9 @@ var serverForbiddenKexAlgos = map[string]struct{}{
|
||||
kexAlgoDHGEXSHA256: {}, // server half implementation is only minimal to satisfy the automated tests
|
||||
}
|
||||
|
||||
// preferredKexAlgos specifies the default preference for key-exchange algorithms
|
||||
// in preference order.
|
||||
// preferredKexAlgos specifies the default preference for key-exchange
|
||||
// algorithms in preference order. The diffie-hellman-group16-sha512 algorithm
|
||||
// is disabled by default because it is a bit slower than the others.
|
||||
var preferredKexAlgos = []string{
|
||||
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
@@ -70,12 +72,12 @@ var preferredKexAlgos = []string{
|
||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSASHA512v01, CertAlgoRSASHA256v01,
|
||||
CertAlgoRSASHA256v01, CertAlgoRSASHA512v01,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
||||
|
||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
KeyAlgoRSASHA512, KeyAlgoRSASHA256,
|
||||
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
|
||||
KeyAlgoRSA, KeyAlgoDSA,
|
||||
|
||||
KeyAlgoED25519,
|
||||
@@ -85,7 +87,7 @@ var supportedHostKeyAlgos = []string{
|
||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||
// because they have reached the end of their useful life.
|
||||
var supportedMACs = []string{
|
||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
@@ -119,6 +121,13 @@ func algorithmsForKeyFormat(keyFormat string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// isRSA returns whether algo is a supported RSA algorithm, including certificate
|
||||
// algorithms.
|
||||
func isRSA(algo string) bool {
|
||||
algos := algorithmsForKeyFormat(KeyAlgoRSA)
|
||||
return contains(algos, underlyingAlgo(algo))
|
||||
}
|
||||
|
||||
// supportedPubKeyAuthAlgos specifies the supported client public key
|
||||
// authentication algorithms. Note that this doesn't include certificate types
|
||||
// since those use the underlying algorithm. This list is sent to the client if
|
||||
@@ -262,16 +271,16 @@ type Config struct {
|
||||
// unspecified, a size suitable for the chosen cipher is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
// default set of algorithms is used.
|
||||
// The allowed key exchanges algorithms. If unspecified then a default set
|
||||
// of algorithms is used. Unsupported values are silently ignored.
|
||||
KeyExchanges []string
|
||||
|
||||
// The allowed cipher algorithms. If unspecified then a sensible
|
||||
// default is used.
|
||||
// The allowed cipher algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
Ciphers []string
|
||||
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
||||
// is used.
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
MACs []string
|
||||
}
|
||||
|
||||
@@ -288,7 +297,7 @@ func (c *Config) SetDefaults() {
|
||||
var ciphers []string
|
||||
for _, c := range c.Ciphers {
|
||||
if cipherModes[c] != nil {
|
||||
// reject the cipher if we have no cipherModes definition
|
||||
// Ignore the cipher if we have no cipherModes definition.
|
||||
ciphers = append(ciphers, c)
|
||||
}
|
||||
}
|
||||
@@ -297,10 +306,26 @@ func (c *Config) SetDefaults() {
|
||||
if c.KeyExchanges == nil {
|
||||
c.KeyExchanges = preferredKexAlgos
|
||||
}
|
||||
var kexs []string
|
||||
for _, k := range c.KeyExchanges {
|
||||
if kexAlgoMap[k] != nil {
|
||||
// Ignore the KEX if we have no kexAlgoMap definition.
|
||||
kexs = append(kexs, k)
|
||||
}
|
||||
}
|
||||
c.KeyExchanges = kexs
|
||||
|
||||
if c.MACs == nil {
|
||||
c.MACs = supportedMACs
|
||||
}
|
||||
var macs []string
|
||||
for _, m := range c.MACs {
|
||||
if macModes[m] != nil {
|
||||
// Ignore the MAC if we have no macModes definition.
|
||||
macs = append(macs, m)
|
||||
}
|
||||
}
|
||||
c.MACs = macs
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// cipher specific default
|
||||
|
||||
+5
-5
@@ -82,11 +82,11 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []testcase{
|
||||
testcase{
|
||||
{
|
||||
name: "standard",
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common hostkey",
|
||||
serverIn: kexInitMsg{
|
||||
ServerHostKeyAlgos: []string{"hostkey2"},
|
||||
@@ -94,7 +94,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common kex",
|
||||
serverIn: kexInitMsg{
|
||||
KexAlgos: []string{"kex2"},
|
||||
@@ -102,7 +102,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common cipher",
|
||||
serverIn: kexInitMsg{
|
||||
CiphersClientServer: []string{"cipher2"},
|
||||
@@ -110,7 +110,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "client decides cipher",
|
||||
serverIn: kexInitMsg{
|
||||
CiphersClientServer: []string{"cipher1", "cipher2"},
|
||||
|
||||
+19
-17
@@ -148,6 +148,7 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
clientDone := make(chan int, 0)
|
||||
gotHalf := make(chan int, 0)
|
||||
const N = 20
|
||||
errorCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(clientDone)
|
||||
@@ -158,7 +159,9 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
for i := 0; i < N; i++ {
|
||||
p := []byte{msgRequestSuccess, byte(i)}
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
errorCh <- err
|
||||
trC.Close()
|
||||
return
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
<-gotHalf
|
||||
@@ -177,16 +180,15 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
checker.waitCall <- 1
|
||||
}
|
||||
}
|
||||
errorCh <- nil
|
||||
}()
|
||||
|
||||
// Server checks that client messages come in cleanly
|
||||
i := 0
|
||||
err = nil
|
||||
for ; i < N; i++ {
|
||||
var p []byte
|
||||
p, err = trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
p, err := trS.readPacket()
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
gotHalf <- 1
|
||||
@@ -198,8 +200,8 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
<-clientDone
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
if err := <-errorCh; err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
}
|
||||
if i != N {
|
||||
t.Errorf("received %d messages, want 10.", i)
|
||||
@@ -345,16 +347,16 @@ func TestHandshakeAutoRekeyRead(t *testing.T) {
|
||||
|
||||
// While we read out the packet, a key change will be
|
||||
// initiated.
|
||||
done := make(chan int, 1)
|
||||
errorCh := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
_, err := trC.readPacket()
|
||||
errorCh <- err
|
||||
}()
|
||||
|
||||
<-done
|
||||
if err := <-errorCh; err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
<-sync.called
|
||||
}
|
||||
|
||||
@@ -421,8 +423,8 @@ func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||
// panic.
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
||||
if runtime.GOOS == "js" && runtime.GOARCH == "wasm" {
|
||||
t.Skip("skipping on js/wasm; see golang.org/issue/32840")
|
||||
if (runtime.GOOS == "js" || runtime.GOOS == "wasip1") && runtime.GOARCH == "wasm" {
|
||||
t.Skipf("skipping on %s/wasm; see golang.org/issue/32840", runtime.GOOS)
|
||||
}
|
||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||
|
||||
|
||||
+12
@@ -23,6 +23,7 @@ const (
|
||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
|
||||
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
|
||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||
@@ -430,6 +431,17 @@ func init() {
|
||||
hashFunc: crypto.SHA256,
|
||||
}
|
||||
|
||||
// This is the group called diffie-hellman-group16-sha512 in RFC
|
||||
// 8268 and Oakley Group 16 in RFC 3526.
|
||||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", 16)
|
||||
|
||||
kexAlgoMap[kexAlgoDH16SHA512] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
hashFunc: crypto.SHA512,
|
||||
}
|
||||
|
||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||
|
||||
@@ -8,6 +8,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -63,3 +64,43 @@ func TestKexes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKexes(b *testing.B) {
|
||||
type kexResultErr struct {
|
||||
result *kexResult
|
||||
err error
|
||||
}
|
||||
|
||||
for name, kex := range kexAlgoMap {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
t1, t2 := memPipe()
|
||||
|
||||
s := make(chan kexResultErr, 1)
|
||||
c := make(chan kexResultErr, 1)
|
||||
var magics handshakeMagics
|
||||
|
||||
go func() {
|
||||
r, e := kex.Client(t1, rand.Reader, &magics)
|
||||
t1.Close()
|
||||
c <- kexResultErr{r, e}
|
||||
}()
|
||||
go func() {
|
||||
r, e := kex.Server(t2, rand.Reader, &magics, testSigners["ecdsa"].(AlgorithmSigner), testSigners["ecdsa"].PublicKey().Type())
|
||||
t2.Close()
|
||||
s <- kexResultErr{r, e}
|
||||
}()
|
||||
|
||||
clientRes := <-c
|
||||
serverRes := <-s
|
||||
|
||||
if clientRes.err != nil {
|
||||
panic(fmt.Sprintf("client: %v", clientRes.err))
|
||||
}
|
||||
if serverRes.err != nil {
|
||||
panic(fmt.Sprintf("server: %v", serverRes.err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
)
|
||||
|
||||
@@ -46,9 +47,15 @@ func (t truncatingMAC) Size() int {
|
||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
var macModes = map[string]*macMode{
|
||||
"hmac-sha2-512-etm@openssh.com": {64, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha512.New, key)
|
||||
}},
|
||||
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha2-512": {64, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha512.New, key)
|
||||
}},
|
||||
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
|
||||
+85
-38
@@ -5,6 +5,8 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -29,14 +31,21 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
go func() {
|
||||
newCh, ok := <-s.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("No incoming channel")
|
||||
t.Error("no incoming channel")
|
||||
close(res)
|
||||
return
|
||||
}
|
||||
if newCh.ChannelType() != "chan" {
|
||||
t.Fatalf("got type %q want chan", newCh.ChannelType())
|
||||
t.Errorf("got type %q want chan", newCh.ChannelType())
|
||||
newCh.Reject(Prohibited, fmt.Sprintf("got type %q want chan", newCh.ChannelType()))
|
||||
close(res)
|
||||
return
|
||||
}
|
||||
ch, _, err := newCh.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Accept %v", err)
|
||||
t.Errorf("accept: %v", err)
|
||||
close(res)
|
||||
return
|
||||
}
|
||||
res <- ch.(*channel)
|
||||
}()
|
||||
@@ -45,8 +54,12 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
w := <-res
|
||||
if w == nil {
|
||||
t.Fatal("unable to get write channel")
|
||||
}
|
||||
|
||||
return <-res, ch, c
|
||||
return w, ch, c
|
||||
}
|
||||
|
||||
// Test that stderr and stdout can be addressed from different
|
||||
@@ -74,14 +87,14 @@ func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
||||
go func() {
|
||||
c, err := io.ReadAll(reader)
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err)
|
||||
t.Errorf("stdout read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
go func() {
|
||||
c, err := io.ReadAll(reader.Stderr())
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err)
|
||||
t.Errorf("stderr read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
@@ -102,11 +115,13 @@ func TestMuxReadWrite(t *testing.T) {
|
||||
go func() {
|
||||
_, err := s.Write([]byte(magic))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
t.Errorf("Write: %v", err)
|
||||
return
|
||||
}
|
||||
_, err = s.Extended(1).Write([]byte(magicExt))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
t.Errorf("Write: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -215,10 +230,13 @@ func TestMuxReject(t *testing.T) {
|
||||
go func() {
|
||||
ch, ok := <-server.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("Accept")
|
||||
t.Error("cannot accept channel")
|
||||
return
|
||||
}
|
||||
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
||||
t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
||||
t.Errorf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
||||
ch.Reject(RejectionReason(UnknownChannelType), UnknownChannelType.String())
|
||||
return
|
||||
}
|
||||
ch.Reject(RejectionReason(42), "message")
|
||||
}()
|
||||
@@ -294,7 +312,7 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
defer serverPipe.Close()
|
||||
defer client.Close()
|
||||
|
||||
kDone := make(chan struct{})
|
||||
kDone := make(chan error, 1)
|
||||
go func() {
|
||||
// Ignore unknown channel messages that don't want a reply.
|
||||
err := serverPipe.writePacket(Marshal(channelRequestMsg{
|
||||
@@ -304,7 +322,8 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send a keepalive, which should get a channel failure message
|
||||
@@ -316,44 +335,53 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
packet, err := serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
t.Fatalf("decode failed: %v", err)
|
||||
kDone <- fmt.Errorf("decode failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelRequestFailureMsg:
|
||||
if msg.PeersID != 2 {
|
||||
t.Fatalf("received response to wrong message: %v", msg)
|
||||
kDone <- fmt.Errorf("received response to wrong message: %v", msg)
|
||||
return
|
||||
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected channel message: %v", msg)
|
||||
kDone <- fmt.Errorf("unexpected channel message: %v", msg)
|
||||
return
|
||||
}
|
||||
|
||||
kDone <- struct{}{}
|
||||
kDone <- nil
|
||||
|
||||
// Receive and respond to the keepalive to confirm the mux is
|
||||
// still processing requests.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgGlobalRequest {
|
||||
t.Fatalf("expected global request")
|
||||
kDone <- errors.New("expected global request")
|
||||
return
|
||||
}
|
||||
|
||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||
Data: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to send failure msg: %v", err)
|
||||
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
close(kDone)
|
||||
@@ -362,7 +390,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
// Wait for the server to send the keepalive message and receive back a
|
||||
// response.
|
||||
select {
|
||||
case <-kDone:
|
||||
case err := <-kDone:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("server never received ack")
|
||||
}
|
||||
@@ -373,7 +404,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
}
|
||||
|
||||
select {
|
||||
case <-kDone:
|
||||
case err := <-kDone:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("server never shut down")
|
||||
}
|
||||
@@ -385,20 +419,23 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
defer serverPipe.Close()
|
||||
defer client.Close()
|
||||
|
||||
kDone := make(chan struct{})
|
||||
kDone := make(chan error, 1)
|
||||
go func() {
|
||||
// Open the channel.
|
||||
packet, err := serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgChannelOpen {
|
||||
t.Fatalf("expected chan open")
|
||||
kDone <- errors.New("expected chan open")
|
||||
return
|
||||
}
|
||||
|
||||
var openMsg channelOpenMsg
|
||||
if err := Unmarshal(packet, &openMsg); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
kDone <- fmt.Errorf("unmarshal: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send back the opened channel confirmation.
|
||||
@@ -409,7 +446,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
MaxPacketSize: channelMaxPacket,
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Close the channel.
|
||||
@@ -417,7 +455,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
PeersID: openMsg.PeersID,
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send a keepalive message on the channel we just closed.
|
||||
@@ -428,43 +467,51 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Receive the channel closed response.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgChannelClose {
|
||||
t.Fatalf("expected channel close")
|
||||
kDone <- errors.New("expected channel close")
|
||||
return
|
||||
}
|
||||
|
||||
// Receive the keepalive response failure.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgChannelFailure {
|
||||
t.Fatalf("expected channel close")
|
||||
kDone <- errors.New("expected channel failure")
|
||||
return
|
||||
}
|
||||
kDone <- struct{}{}
|
||||
kDone <- nil
|
||||
|
||||
// Receive and respond to the keepalive to confirm the mux is
|
||||
// still processing requests.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgGlobalRequest {
|
||||
t.Fatalf("expected global request")
|
||||
kDone <- errors.New("expected global request")
|
||||
return
|
||||
}
|
||||
|
||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||
Data: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to send failure msg: %v", err)
|
||||
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
close(kDone)
|
||||
|
||||
+20
-1
@@ -370,6 +370,25 @@ func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *c
|
||||
return authErr, perms, nil
|
||||
}
|
||||
|
||||
// isAlgoCompatible checks if the signature format is compatible with the
|
||||
// selected algorithm taking into account edge cases that occur with old
|
||||
// clients.
|
||||
func isAlgoCompatible(algo, sigFormat string) bool {
|
||||
// Compatibility for old clients.
|
||||
//
|
||||
// For certificate authentication with OpenSSH 7.2-7.7 signature format can
|
||||
// be rsa-sha2-256 or rsa-sha2-512 for the algorithm
|
||||
// ssh-rsa-cert-v01@openssh.com.
|
||||
//
|
||||
// With gpg-agent < 2.2.6 the algorithm can be rsa-sha2-256 or rsa-sha2-512
|
||||
// for signature format ssh-rsa.
|
||||
if isRSA(algo) && isRSA(sigFormat) {
|
||||
return true
|
||||
}
|
||||
// Standard case: the underlying algorithm must match the signature format.
|
||||
return underlyingAlgo(algo) == sigFormat
|
||||
}
|
||||
|
||||
// ServerAuthError represents server authentication errors and is
|
||||
// sometimes returned by NewServerConn. It appends any authentication
|
||||
// errors that may occur, and is returned if all of the authentication
|
||||
@@ -567,7 +586,7 @@ userAuthLoop:
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
||||
break
|
||||
}
|
||||
if underlyingAlgo(algo) != sig.Format {
|
||||
if !isAlgoCompatible(algo, sig.Format) {
|
||||
authErr = fmt.Errorf("ssh: signature %q not compatible with selected algorithm %q", sig.Format, algo)
|
||||
break
|
||||
}
|
||||
|
||||
+16
-3
@@ -36,7 +36,8 @@ func dial(handler serverType, t *testing.T) *Client {
|
||||
|
||||
conn, chans, reqs, err := NewServerConn(c1, &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to handshake: %v", err)
|
||||
t.Errorf("Unable to handshake: %v", err)
|
||||
return
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
@@ -647,10 +648,12 @@ func TestSessionID(t *testing.T) {
|
||||
User: "user",
|
||||
}
|
||||
|
||||
srvErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewServerConn(c1, serverConf)
|
||||
srvErrCh <- err
|
||||
if err != nil {
|
||||
t.Fatalf("server handshake: %v", err)
|
||||
return
|
||||
}
|
||||
serverID <- conn.SessionID()
|
||||
go DiscardRequests(reqs)
|
||||
@@ -659,10 +662,12 @@ func TestSessionID(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
cliErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
|
||||
cliErrCh <- err
|
||||
if err != nil {
|
||||
t.Fatalf("client handshake: %v", err)
|
||||
return
|
||||
}
|
||||
clientID <- conn.SessionID()
|
||||
go DiscardRequests(reqs)
|
||||
@@ -671,6 +676,14 @@ func TestSessionID(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := <-srvErrCh; err != nil {
|
||||
t.Fatalf("server handshake: %v", err)
|
||||
}
|
||||
|
||||
if err := <-cliErrCh; err != nil {
|
||||
t.Fatalf("client handshake: %v", err)
|
||||
}
|
||||
|
||||
s := <-serverID
|
||||
c := <-clientID
|
||||
if bytes.Compare(s, c) != 0 {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
func TestAgentForward(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
func TestBannerCallbackAgainstOpenSSH(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
|
||||
clientConf := clientConfig()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
|
||||
func TestCertLogin(t *testing.T) {
|
||||
s := newServer(t)
|
||||
defer s.Shutdown()
|
||||
|
||||
// Use a key different from the default.
|
||||
clientKey := testSigners["dsa"]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && !solaris && !js
|
||||
// +build !windows,!solaris,!js
|
||||
//go:build !windows && !js && !wasip1
|
||||
// +build !windows,!js,!wasip1
|
||||
|
||||
package test
|
||||
|
||||
@@ -24,7 +24,6 @@ type dialTester interface {
|
||||
|
||||
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
sshConn := server.Dial(clientConfig())
|
||||
defer sshConn.Close()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -23,7 +23,6 @@ type closeWriter interface {
|
||||
|
||||
func testPortForward(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -120,7 +119,6 @@ func TestPortForwardUnix(t *testing.T) {
|
||||
|
||||
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
@@ -162,10 +160,9 @@ func TestAcceptCloseUnix(t *testing.T) {
|
||||
// Check that listeners exit if the underlying client transport dies.
|
||||
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
client := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
sshListener, err := client.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -184,14 +181,10 @@ func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||
|
||||
// It would be even nicer if we closed the server side, but it
|
||||
// is more involved as the fd for that side is dup()ed.
|
||||
server.clientConn.Close()
|
||||
server.lastDialConn.Close()
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Errorf("timeout: listener did not close.")
|
||||
case err := <-quit:
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
err = <-quit
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
||||
|
||||
@@ -77,27 +77,27 @@ func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []stri
|
||||
func TestMultiAuth(t *testing.T) {
|
||||
testCases := []multiAuthTestCase{
|
||||
// Test password,publickey authentication, assert that password callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"password", "publickey"},
|
||||
expectedPasswordCbs: 1,
|
||||
},
|
||||
// Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"keyboard-interactive", "publickey"},
|
||||
expectedKbdIntCbs: 1,
|
||||
},
|
||||
// Test publickey,password authentication, assert that password callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"publickey", "password"},
|
||||
expectedPasswordCbs: 1,
|
||||
},
|
||||
// Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"publickey", "keyboard-interactive"},
|
||||
expectedKbdIntCbs: 1,
|
||||
},
|
||||
// Test password,password authentication, assert that password callback is called 2 times
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"password", "password"},
|
||||
expectedPasswordCbs: 2,
|
||||
},
|
||||
@@ -108,7 +108,6 @@ func TestMultiAuth(t *testing.T) {
|
||||
ctx := newMultiAuthTestCtx(t)
|
||||
|
||||
server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
|
||||
defer server.Shutdown()
|
||||
|
||||
clientConfig := clientConfig()
|
||||
server.setTestPassword(clientConfig.User, ctx.password)
|
||||
|
||||
+27
-21
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && !solaris && !js
|
||||
// +build !windows,!solaris,!js
|
||||
//go:build !windows && !js && !wasip1
|
||||
// +build !windows,!js,!wasip1
|
||||
|
||||
package test
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -23,7 +25,6 @@ import (
|
||||
|
||||
func TestRunCommandSuccess(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -40,7 +41,6 @@ func TestRunCommandSuccess(t *testing.T) {
|
||||
|
||||
func TestHostKeyCheck(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
@@ -62,7 +62,6 @@ func TestHostKeyCheck(t *testing.T) {
|
||||
|
||||
func TestRunCommandStdin(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -85,7 +84,6 @@ func TestRunCommandStdin(t *testing.T) {
|
||||
|
||||
func TestRunCommandStdinError(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -109,7 +107,6 @@ func TestRunCommandStdinError(t *testing.T) {
|
||||
|
||||
func TestRunCommandFailed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -126,7 +123,6 @@ func TestRunCommandFailed(t *testing.T) {
|
||||
|
||||
func TestRunCommandWeClosed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -146,7 +142,6 @@ func TestRunCommandWeClosed(t *testing.T) {
|
||||
|
||||
func TestFuncLargeRead(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -178,7 +173,6 @@ func TestFuncLargeRead(t *testing.T) {
|
||||
|
||||
func TestKeyChange(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
conf.HostKeyCallback = hostDB.Check
|
||||
@@ -225,7 +219,6 @@ func TestValidTerminalMode(t *testing.T) {
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -255,15 +248,31 @@ func TestValidTerminalMode(t *testing.T) {
|
||||
t.Fatalf("session failed: %s", err)
|
||||
}
|
||||
|
||||
stdin.Write([]byte("stty -a && exit\n"))
|
||||
if _, err := io.WriteString(stdin, "echo && echo SHELL $SHELL && stty -a && exit\n"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
||||
buf := new(strings.Builder)
|
||||
if _, err := io.Copy(buf, stdout); err != nil {
|
||||
t.Fatalf("reading failed: %s", err)
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
t.Logf("echo && echo SHELL $SHELL && stty -a && exit:\n%s", buf)
|
||||
}
|
||||
|
||||
shellLine := regexp.MustCompile("(?m)^SHELL (.*)$").FindStringSubmatch(buf.String())
|
||||
if len(shellLine) != 2 {
|
||||
t.Fatalf("missing output from echo SHELL $SHELL")
|
||||
}
|
||||
switch shell := filepath.Base(strings.TrimSpace(shellLine[1])); shell {
|
||||
case "sh", "bash":
|
||||
default:
|
||||
t.Skipf("skipping test on non-Bourne shell %q", shell)
|
||||
}
|
||||
|
||||
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
|
||||
t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput)
|
||||
t.Fatal("terminal mode failure: expected -echo in stty output")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +283,6 @@ func TestWindowChange(t *testing.T) {
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -322,7 +330,6 @@ func TestWindowChange(t *testing.T) {
|
||||
|
||||
func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.Ciphers = []string{cipher}
|
||||
// Don't fail if sshd doesn't have the cipher.
|
||||
@@ -381,7 +388,6 @@ func TestMACs(t *testing.T) {
|
||||
for _, mac := range macOrder {
|
||||
t.Run(mac, func(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.MACs = []string{mac}
|
||||
// Don't fail if sshd doesn't have the MAC.
|
||||
@@ -404,10 +410,12 @@ func TestKeyExchanges(t *testing.T) {
|
||||
// are not included in the default list of supported kex so we have to add them
|
||||
// here manually.
|
||||
kexOrder = append(kexOrder, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
|
||||
// The key exchange algorithms diffie-hellman-group16-sha512 is disabled by
|
||||
// default so we add it here manually.
|
||||
kexOrder = append(kexOrder, "diffie-hellman-group16-sha512")
|
||||
for _, kex := range kexOrder {
|
||||
t.Run(kex, func(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
// Don't fail if sshd doesn't have the kex.
|
||||
conf.KeyExchanges = append([]string{kex}, kexOrder...)
|
||||
@@ -442,8 +450,6 @@ func TestClientAuthAlgorithms(t *testing.T) {
|
||||
} else {
|
||||
t.Errorf("failed for key %q", key)
|
||||
}
|
||||
|
||||
server.Shutdown()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+29
-39
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd plan9
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd plan9 solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/crypto/internal/testenv"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
@@ -67,17 +68,13 @@ var configTmpl = map[string]*template.Template{
|
||||
|
||||
type server struct {
|
||||
t *testing.T
|
||||
cleanup func() // executed during Shutdown
|
||||
configfile string
|
||||
cmd *exec.Cmd
|
||||
output bytes.Buffer // holds stderr from sshd process
|
||||
|
||||
testUser string // test username for sshd
|
||||
testPasswd string // test password for sshd
|
||||
sshdTestPwSo string // dynamic library to inject a custom password into sshd
|
||||
|
||||
// Client half of the network connection.
|
||||
clientConn net.Conn
|
||||
lastDialConn net.Conn
|
||||
}
|
||||
|
||||
func username() string {
|
||||
@@ -193,15 +190,15 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
||||
s.t.Fatalf("unixConnection: %v", err)
|
||||
}
|
||||
|
||||
s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
|
||||
cmd := testenv.Command(s.t, sshd, "-f", s.configfile, "-i", "-e")
|
||||
f, err := c2.File()
|
||||
if err != nil {
|
||||
s.t.Fatalf("UnixConn.File: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
s.cmd.Stdin = f
|
||||
s.cmd.Stdout = f
|
||||
s.cmd.Stderr = &s.output
|
||||
cmd.Stdin = f
|
||||
cmd.Stdout = f
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
|
||||
if s.sshdTestPwSo != "" {
|
||||
if s.testUser == "" {
|
||||
@@ -210,18 +207,29 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
||||
if s.testPasswd == "" {
|
||||
s.t.Fatal("password missing from sshd_test_pw.so config")
|
||||
}
|
||||
s.cmd.Env = append(os.Environ(),
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
|
||||
fmt.Sprintf("TEST_USER=%s", s.testUser),
|
||||
fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
|
||||
}
|
||||
|
||||
if err := s.cmd.Start(); err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
if err := cmd.Start(); err != nil {
|
||||
s.t.Fatalf("s.cmd.Start: %v", err)
|
||||
}
|
||||
s.clientConn = c1
|
||||
s.lastDialConn = c1
|
||||
s.t.Cleanup(func() {
|
||||
// Don't check for errors; if it fails it's most
|
||||
// likely "os: process already finished", and we don't
|
||||
// care about that. Use os.Interrupt, so child
|
||||
// processes are killed too.
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
cmd.Wait()
|
||||
if s.t.Failed() {
|
||||
// log any output from sshd process
|
||||
s.t.Logf("sshd:\n%s", cmd.Stderr)
|
||||
}
|
||||
})
|
||||
|
||||
conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -232,29 +240,11 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
||||
func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
|
||||
conn, err := s.TryDial(config)
|
||||
if err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
s.t.Fatalf("ssh.Client: %v", err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *server) Shutdown() {
|
||||
if s.cmd != nil && s.cmd.Process != nil {
|
||||
// Don't check for errors; if it fails it's most
|
||||
// likely "os: process already finished", and we don't
|
||||
// care about that. Use os.Interrupt, so child
|
||||
// processes are killed too.
|
||||
s.cmd.Process.Signal(os.Interrupt)
|
||||
s.cmd.Wait()
|
||||
}
|
||||
if s.t.Failed() {
|
||||
// log any output from sshd process
|
||||
s.t.Logf("sshd: %s", s.output.String())
|
||||
}
|
||||
s.cleanup()
|
||||
}
|
||||
|
||||
func writeFile(path string, contents []byte) {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
@@ -351,15 +341,15 @@ func newServerForConfig(t *testing.T, config string, configVars map[string]strin
|
||||
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
}
|
||||
writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
|
||||
t.Cleanup(func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
return &server{
|
||||
t: t,
|
||||
configfile: f.Name(),
|
||||
cleanup: func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
// Package fallback embeds a set of fallback X.509 trusted roots in the
|
||||
// application by automatically invoking [x509.SetFallbackRoots]. This allows
|
||||
// the application to work correctly even if the operating system does not
|
||||
// provide a verifier or system roots pool.
|
||||
//
|
||||
// To use it, import the package like
|
||||
//
|
||||
// import _ "golang.org/x/crypto/x509roots/fallback"
|
||||
//
|
||||
// It's recommended that only binaries, and not libraries, import this package.
|
||||
//
|
||||
// This package must be kept up to date for security and compatibility reasons.
|
||||
// Use govulncheck to be notified of when new versions of the package are
|
||||
// available.
|
||||
package fallback
|
||||
|
||||
import "crypto/x509"
|
||||
|
||||
func init() {
|
||||
p := x509.NewCertPool()
|
||||
for _, c := range bundle {
|
||||
p.AddCert(c)
|
||||
}
|
||||
x509.SetFallbackRoots(p)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module golang.org/x/crypto/x509roots/fallback
|
||||
|
||||
go 1.20
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package goissue52287 is an empty internal package.
|
||||
// It exists only to work around go.dev/issue/52287 and
|
||||
// can be removed after Go 1.19 stops being supported.
|
||||
package goissue52287
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build generate
|
||||
// +build generate
|
||||
|
||||
//go:generate go run gen_fallback_bundle.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/crypto/x509roots/nss"
|
||||
)
|
||||
|
||||
const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package fallback
|
||||
|
||||
import "crypto/x509"
|
||||
import "encoding/pem"
|
||||
|
||||
func mustParse(b []byte) []*x509.Certificate {
|
||||
var roots []*x509.Certificate
|
||||
for len(b) > 0 {
|
||||
var block *pem.Block
|
||||
block, b = pem.Decode(b)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
panic("unexpected PEM block type: " + block.Type)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
roots = append(roots, cert)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
var bundle = mustParse([]byte(pemRoots))
|
||||
|
||||
// Format of the PEM list is:
|
||||
// * Subject common name
|
||||
// * SHA256 hash
|
||||
// * PEM block
|
||||
|
||||
`
|
||||
|
||||
var (
|
||||
certDataURL = flag.String("certdata-url", "https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt", "URL to the raw certdata.txt file to parse (certdata-path overrides this, if provided)")
|
||||
certDataPath = flag.String("certdata-path", "", "Path to the NSS certdata.txt file to parse (this overrides certdata-url, if provided)")
|
||||
output = flag.String("output", "fallback/bundle.go", "Path to file to write output to")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var certdata io.Reader
|
||||
|
||||
if *certDataPath != "" {
|
||||
f, err := os.Open(*certDataPath)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to open %q: %s", *certDataPath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
certdata = f
|
||||
} else {
|
||||
resp, err := http.Get(*certDataURL)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to request %q: %s", *certDataURL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
certdata = resp.Body
|
||||
}
|
||||
|
||||
certs, err := nss.Parse(certdata)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse %q: %s", *certDataPath, err)
|
||||
}
|
||||
|
||||
sort.Slice(certs, func(i, j int) bool {
|
||||
// Sort based on the stringified subject (which may not be unique), and
|
||||
// break any ties by just sorting on the raw DER (which will be unique,
|
||||
// but is expensive). This should produce a stable sorting, which should
|
||||
// be mostly readable by a human looking for a specific root or set of
|
||||
// roots.
|
||||
subjI, subjJ := certs[i].X509.Subject.String(), certs[j].X509.Subject.String()
|
||||
if subjI == subjJ {
|
||||
return string(certs[i].X509.Raw) < string(certs[j].X509.Raw)
|
||||
}
|
||||
return subjI < subjJ
|
||||
})
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString(tmpl)
|
||||
fmt.Fprintln(b, "const pemRoots = `")
|
||||
for _, c := range certs {
|
||||
if len(c.Constraints) > 0 {
|
||||
// Until the constrained roots API lands, skip anything that has any
|
||||
// additional constraints. Once that API is available, we can add
|
||||
// build constraints that support both the current version and the
|
||||
// new version.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "# %s\n# %x\n", c.X509.Subject.String(), sha256.Sum256(c.X509.Raw))
|
||||
pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})
|
||||
}
|
||||
fmt.Fprintln(b, "`")
|
||||
|
||||
formatted, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to format source: %s", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*output, formatted, 0644); err != nil {
|
||||
log.Fatalf("failed to write to %q: %s", *output, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package nss provides functionality for parsing NSS certdata.txt
|
||||
// formatted certificate lists and extracting serverAuth roots. Most
|
||||
// users should not use this package themselves, and should instead
|
||||
// rely on the golang.org/x/crypto/x509roots/fallback package which
|
||||
// calls x509.SetFallbackRoots on a pre-parsed set of roots.
|
||||
package nss
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constraint is a constraint to be applied to a certificate or
|
||||
// certificate chain.
|
||||
type Constraint interface {
|
||||
Kind() Kind
|
||||
}
|
||||
|
||||
// Kind is the constraint kind, using the NSS enumeration.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
CKA_NSS_SERVER_DISTRUST_AFTER Kind = iota
|
||||
)
|
||||
|
||||
// DistrustAfter is a Constraint that indicates a certificate has a
|
||||
// CKA_NSS_SERVER_DISTRUST_AFTER constraint. This constraint defines a date
|
||||
// after which any certificate issued which is rooted by the constrained
|
||||
// certificate should be distrusted.
|
||||
type DistrustAfter time.Time
|
||||
|
||||
func (DistrustAfter) Kind() Kind {
|
||||
return CKA_NSS_SERVER_DISTRUST_AFTER
|
||||
}
|
||||
|
||||
// A Certificate represents a single trusted serverAuth certificate in the NSS
|
||||
// certdata.txt list and any constraints that should be applied to chains
|
||||
// rooted by it.
|
||||
type Certificate struct {
|
||||
// Certificate is the parsed certificate
|
||||
X509 *x509.Certificate
|
||||
// Constraints contains a list of additional constraints that should be
|
||||
// applied to any certificates that chain to Certificate. If there are
|
||||
// any unknown constraints in the slice, Certificate should not be
|
||||
// trusted.
|
||||
Constraints []Constraint
|
||||
}
|
||||
|
||||
func parseMulitLineOctal(s *bufio.Scanner) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for s.Scan() {
|
||||
if s.Text() == "END" {
|
||||
break
|
||||
}
|
||||
b, err := strconv.Unquote(fmt.Sprintf("\"%s\"", s.Text()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write([]byte(b))
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type certObj struct {
|
||||
c *x509.Certificate
|
||||
DistrustAfter *time.Time
|
||||
}
|
||||
|
||||
func parseCertClass(s *bufio.Scanner) ([sha1.Size]byte, *certObj, error) {
|
||||
var h [sha1.Size]byte
|
||||
co := &certObj{}
|
||||
for s.Scan() {
|
||||
l := s.Text()
|
||||
if l == "" {
|
||||
// assume an empty newline indicates the end of a block
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(l, "CKA_VALUE") {
|
||||
b, err := parseMulitLineOctal(s)
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
co.c, err = x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
h = sha1.Sum(b)
|
||||
} else if strings.HasPrefix(l, "CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE") {
|
||||
// we don't want it
|
||||
return h, nil, nil
|
||||
} else if l == "CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL" {
|
||||
dateStr, err := parseMulitLineOctal(s)
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
t, err := time.Parse("060102150405Z0700", string(dateStr))
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
co.DistrustAfter = &t
|
||||
}
|
||||
}
|
||||
if co.c == nil {
|
||||
return h, nil, errors.New("malformed CKO_CERTIFICATE object")
|
||||
}
|
||||
return h, co, nil
|
||||
}
|
||||
|
||||
type trustObj struct {
|
||||
trusted bool
|
||||
}
|
||||
|
||||
func parseTrustClass(s *bufio.Scanner) ([sha1.Size]byte, *trustObj, error) {
|
||||
var h [sha1.Size]byte
|
||||
to := &trustObj{trusted: false} // default to untrusted
|
||||
|
||||
for s.Scan() {
|
||||
l := s.Text()
|
||||
if l == "" {
|
||||
// assume an empty newline indicates the end of a block
|
||||
break
|
||||
}
|
||||
if l == "CKA_CERT_SHA1_HASH MULTILINE_OCTAL" {
|
||||
hash, err := parseMulitLineOctal(s)
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
copy(h[:], hash)
|
||||
} else if l == "CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR" {
|
||||
// we only care about server auth
|
||||
to.trusted = true
|
||||
}
|
||||
}
|
||||
|
||||
return h, to, nil
|
||||
}
|
||||
|
||||
// Parse parses a NSS certdata.txt formatted file, returning only
|
||||
// trusted serverAuth roots, as well as any additional constraints. This parser
|
||||
// is very opinionated, only returning roots that are currently trusted for
|
||||
// serverAuth. As such roots returned by this package should only be used for
|
||||
// making trust decisions about serverAuth certificates, as the trust status for
|
||||
// other uses is not considered. Using the roots returned by this package for
|
||||
// trust decisions should be done carefully.
|
||||
//
|
||||
// Some roots returned by the parser may include additional constraints
|
||||
// (currently only DistrustAfter) which need to be considered when verifying
|
||||
// certificates which chain to them.
|
||||
//
|
||||
// Parse is not intended to be a general purpose parser for certdata.txt.
|
||||
func Parse(r io.Reader) ([]*Certificate, error) {
|
||||
// certdata.txt is a rather strange format. It is essentially a list of
|
||||
// textual PKCS#11 objects, delimited by empty lines. There are two main
|
||||
// types of objects, certificates (CKO_CERTIFICATE) and trust definitions
|
||||
// (CKO_NSS_TRUST). These objects appear to alternate, but this ordering is
|
||||
// not defined anywhere, and should probably not be relied on. A single root
|
||||
// certificate requires both the certificate object and the trust definition
|
||||
// object in order to be properly understood.
|
||||
//
|
||||
// The list contains not just serverAuth certificates, so we need to be
|
||||
// careful to only extract certificates which have the serverAuth trust bit
|
||||
// set. Similarly there are a number of trust related bool fields that
|
||||
// appear to _always_ be CKA_TRUE, but it seems unsafe to assume this is the
|
||||
// case, so we should always double check.
|
||||
//
|
||||
// Since we only really care about a couple of fields, this parser throws
|
||||
// away a lot of information, essentially just consuming CKA_CLASS objects
|
||||
// and looking for the individual fields we care about. We could write a
|
||||
// siginificantly more complex parser, which handles the entire format, but
|
||||
// it feels like that would be over engineered for the little information
|
||||
// that we really care about.
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
type nssEntry struct {
|
||||
cert *certObj
|
||||
trust *trustObj
|
||||
}
|
||||
entries := map[[sha1.Size]byte]*nssEntry{}
|
||||
|
||||
for scanner.Scan() {
|
||||
// scan until we hit CKA_CLASS
|
||||
if !strings.HasPrefix(scanner.Text(), "CKA_CLASS") {
|
||||
continue
|
||||
}
|
||||
|
||||
f := strings.Fields(scanner.Text())
|
||||
if len(f) != 3 {
|
||||
return nil, errors.New("malformed CKA_CLASS")
|
||||
}
|
||||
switch f[2] {
|
||||
case "CKO_CERTIFICATE":
|
||||
h, co, err := parseCertClass(scanner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if co != nil {
|
||||
e, ok := entries[h]
|
||||
if !ok {
|
||||
e = &nssEntry{}
|
||||
entries[h] = e
|
||||
}
|
||||
e.cert = co
|
||||
}
|
||||
|
||||
case "CKO_NSS_TRUST":
|
||||
h, to, err := parseTrustClass(scanner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
e, ok := entries[h]
|
||||
if !ok {
|
||||
e = &nssEntry{}
|
||||
entries[h] = e
|
||||
}
|
||||
e.trust = to
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var certs []*Certificate
|
||||
for h, e := range entries {
|
||||
if e.cert == nil && e.trust != nil {
|
||||
// We may skip some certificates which are distrusted due to mozilla
|
||||
// policy (CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE), which means
|
||||
// we might get entries that appear to have a trust object, but no
|
||||
// certificate. We can just continue on here.
|
||||
continue
|
||||
} else if e.cert != nil && e.trust == nil {
|
||||
return nil, fmt.Errorf("missing trust object for certificate with SHA1 hash: %x", h)
|
||||
}
|
||||
if !e.trust.trusted {
|
||||
continue
|
||||
}
|
||||
nssCert := &Certificate{X509: e.cert.c}
|
||||
if e.cert.DistrustAfter != nil {
|
||||
nssCert.Constraints = append(nssCert.Constraints, DistrustAfter(*e.cert.DistrustAfter))
|
||||
}
|
||||
certs = append(certs, nssCert)
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user