The parseDSAKey function constructed a *dsa.PrivateKey directly from the add-identity request without validating the key parameters. Unlike DSA certificates, whose parameters are checked by the ssh package when the certificate's public key is parsed, raw DSA keys added to the agent were not validated at all. Align the raw DSA key parsing with the validation already performed by the main ssh package. Fixes golang/go#79725 Change-Id: I537cc2175d35c19848c90c68739cf94ba7b50e10 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795422 Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Junyang Shao <shaojunyang@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
467 lines
11 KiB
Go
467 lines
11 KiB
Go
// Copyright 2012 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 agent
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
pseudorand "math/rand"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func TestServer(t *testing.T) {
|
|
c1, c2, err := netPipe()
|
|
if err != nil {
|
|
t.Fatalf("netPipe: %v", err)
|
|
}
|
|
defer c1.Close()
|
|
defer c2.Close()
|
|
client := NewClient(c1)
|
|
|
|
go ServeAgent(NewKeyring(), c2)
|
|
|
|
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
|
|
}
|
|
|
|
func TestLockServer(t *testing.T) {
|
|
testLockAgent(NewKeyring(), t)
|
|
}
|
|
|
|
func TestSetupForwardAgent(t *testing.T) {
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
t.Fatalf("netPipe: %v", err)
|
|
}
|
|
|
|
defer a.Close()
|
|
defer b.Close()
|
|
|
|
_, socket, cleanup := startOpenSSHAgent(t)
|
|
defer cleanup()
|
|
|
|
serverConf := ssh.ServerConfig{
|
|
NoClientAuth: true,
|
|
}
|
|
serverConf.AddHostKey(testSigners["rsa"])
|
|
incoming := make(chan *ssh.ServerConn, 1)
|
|
go func() {
|
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
|
incoming <- conn
|
|
if err != nil {
|
|
t.Errorf("NewServerConn error: %v", err)
|
|
return
|
|
}
|
|
}()
|
|
|
|
conf := ssh.ClientConfig{
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
}
|
|
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
|
if err != nil {
|
|
t.Fatalf("NewClientConn: %v", err)
|
|
}
|
|
client := ssh.NewClient(conn, chans, reqs)
|
|
|
|
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)
|
|
}
|
|
go ssh.DiscardRequests(reqs)
|
|
|
|
agentClient := NewClient(ch)
|
|
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
|
|
conn.Close()
|
|
}
|
|
|
|
func TestForwardAgentDiscardsStderr(t *testing.T) {
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
t.Fatalf("netPipe: %v", err)
|
|
}
|
|
defer a.Close()
|
|
defer b.Close()
|
|
|
|
serverConf := ssh.ServerConfig{
|
|
NoClientAuth: true,
|
|
}
|
|
serverConf.AddHostKey(testSigners["rsa"])
|
|
incoming := make(chan *ssh.ServerConn, 1)
|
|
go func() {
|
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
|
incoming <- conn
|
|
if err != nil {
|
|
t.Errorf("NewServerConn error: %v", err)
|
|
return
|
|
}
|
|
}()
|
|
|
|
conf := ssh.ClientConfig{
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
}
|
|
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
|
if err != nil {
|
|
t.Fatalf("NewClientConn: %v", err)
|
|
}
|
|
client := ssh.NewClient(conn, chans, reqs)
|
|
defer client.Close()
|
|
|
|
if err := ForwardToAgent(client, NewKeyring()); err != nil {
|
|
t.Fatalf("ForwardToAgent: %v", err)
|
|
}
|
|
|
|
server := <-incoming
|
|
if server == nil {
|
|
t.Fatal("Unable to get server")
|
|
}
|
|
defer server.Close()
|
|
|
|
ch, chanReqs, err := server.OpenChannel(channelType, nil)
|
|
if err != nil {
|
|
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
|
}
|
|
go ssh.DiscardRequests(chanReqs)
|
|
defer ch.Close()
|
|
|
|
// Write more than the default channel receive window (2 MiB) on the
|
|
// stderr stream. If the forwarder did not drain it the remote window
|
|
// would be exhausted and this write would block forever, because no
|
|
// window-adjust messages are emitted for an extended stream that is
|
|
// never read.
|
|
const payload = 4 * 1024 * 1024
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
buf := make([]byte, 32*1024)
|
|
remaining := payload
|
|
for remaining > 0 {
|
|
n := len(buf)
|
|
if remaining < n {
|
|
n = remaining
|
|
}
|
|
w, err := ch.Stderr().Write(buf[:n])
|
|
if err != nil {
|
|
done <- err
|
|
return
|
|
}
|
|
remaining -= w
|
|
}
|
|
done <- nil
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Fatalf("write to stderr: %v", err)
|
|
}
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("write to stderr blocked: ForwardToAgent is not draining channel.Stderr()")
|
|
}
|
|
|
|
// The agent must still be reachable on the main stream after the
|
|
// stderr traffic.
|
|
if _, err := NewClient(ch).List(); err != nil {
|
|
t.Fatalf("List after stderr flood: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestV1ProtocolMessages(t *testing.T) {
|
|
c1, c2, err := netPipe()
|
|
if err != nil {
|
|
t.Fatalf("netPipe: %v", err)
|
|
}
|
|
defer c1.Close()
|
|
defer c2.Close()
|
|
c := NewClient(c1)
|
|
|
|
go ServeAgent(NewKeyring(), c2)
|
|
|
|
testV1ProtocolMessages(t, c.(*client))
|
|
}
|
|
|
|
func testV1ProtocolMessages(t *testing.T, c *client) {
|
|
reply, err := c.call([]byte{agentRequestV1Identities})
|
|
if err != nil {
|
|
t.Fatalf("v1 request all failed: %v", err)
|
|
}
|
|
if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 {
|
|
t.Fatalf("invalid request all response: %#v", reply)
|
|
}
|
|
|
|
reply, err = c.call([]byte{agentRemoveAllV1Identities})
|
|
if err != nil {
|
|
t.Fatalf("v1 remove all failed: %v", err)
|
|
}
|
|
if _, ok := reply.(*successAgentMsg); !ok {
|
|
t.Fatalf("invalid remove all response: %#v", reply)
|
|
}
|
|
}
|
|
|
|
func verifyKey(sshAgent Agent) error {
|
|
keys, err := sshAgent.List()
|
|
if err != nil {
|
|
return fmt.Errorf("listing keys: %v", err)
|
|
}
|
|
|
|
if len(keys) != 1 {
|
|
return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys))
|
|
}
|
|
|
|
buf := make([]byte, 128)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return fmt.Errorf("rand: %v", err)
|
|
}
|
|
|
|
sig, err := sshAgent.Sign(keys[0], buf)
|
|
if err != nil {
|
|
return fmt.Errorf("sign: %v", err)
|
|
}
|
|
|
|
if err := keys[0].Verify(buf, sig); err != nil {
|
|
return fmt.Errorf("verify: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addKeyToAgent(key crypto.PrivateKey) error {
|
|
sshAgent := NewKeyring()
|
|
if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil {
|
|
return fmt.Errorf("add: %v", err)
|
|
}
|
|
return verifyKey(sshAgent)
|
|
}
|
|
|
|
func TestParseDSAKeyHugeQ(t *testing.T) {
|
|
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
|
Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
|
|
// G and Y: dummy values, just needs to be < P to pass that specific check.
|
|
G := big.NewInt(2)
|
|
Y := big.NewInt(5)
|
|
|
|
req := ssh.Marshal(dsaKeyMsg{
|
|
Type: ssh.InsecureKeyAlgoDSA,
|
|
P: P,
|
|
Q: Q,
|
|
G: G,
|
|
Y: Y,
|
|
X: big.NewInt(1),
|
|
})
|
|
|
|
_, err := parseDSAKey(req)
|
|
if err == nil {
|
|
t.Fatal("parseDSAKey accepted a DSA key with large Q")
|
|
}
|
|
|
|
expectedError := "unsupported DSA sub-prime size"
|
|
if !strings.Contains(err.Error(), expectedError) {
|
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
|
}
|
|
}
|
|
|
|
func TestParseDSAKeyYOutOfRange(t *testing.T) {
|
|
// Valid 1024/160 parameters (values don't need to be a real DSA group,
|
|
// they only need to pass the checkDSAParams bit-length checks and the
|
|
// G < P / G > 0 checks).
|
|
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
|
P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
|
|
Q := new(big.Int).Lsh(big.NewInt(1), 159)
|
|
Q.SetBit(Q, 0, 1)
|
|
G := big.NewInt(2)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
Y *big.Int
|
|
}{
|
|
{"Y_zero", big.NewInt(0)},
|
|
{"Y_negative", big.NewInt(-1)},
|
|
{"Y_equals_P", new(big.Int).Set(P)},
|
|
{"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
|
|
{"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := ssh.Marshal(dsaKeyMsg{
|
|
Type: ssh.InsecureKeyAlgoDSA,
|
|
P: P,
|
|
Q: Q,
|
|
G: G,
|
|
Y: tc.Y,
|
|
X: big.NewInt(1),
|
|
})
|
|
|
|
_, err := parseDSAKey(req)
|
|
if err == nil {
|
|
t.Fatalf("parseDSAKey accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
|
|
}
|
|
expectedError := "DSA public value Y out of range"
|
|
if !strings.Contains(err.Error(), expectedError) {
|
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeyTypes(t *testing.T) {
|
|
for k, v := range testPrivateKeys {
|
|
if err := addKeyToAgent(v); err != nil {
|
|
t.Errorf("error adding key type %s, %v", k, err)
|
|
}
|
|
if err := addCertToAgentSock(v, nil); err != nil {
|
|
t.Errorf("error adding key type %s, %v", k, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
agentServer := NewKeyring()
|
|
go ServeAgent(agentServer, a)
|
|
|
|
agentClient := NewClient(b)
|
|
if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
|
return fmt.Errorf("add: %v", err)
|
|
}
|
|
return verifyKey(agentClient)
|
|
}
|
|
|
|
func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
|
sshAgent := NewKeyring()
|
|
if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
|
return fmt.Errorf("add: %v", err)
|
|
}
|
|
return verifyKey(sshAgent)
|
|
}
|
|
|
|
func TestCertTypes(t *testing.T) {
|
|
for keyType, key := range testPublicKeys {
|
|
cert := &ssh.Certificate{
|
|
ValidPrincipals: []string{"gopher1"},
|
|
ValidAfter: 0,
|
|
ValidBefore: ssh.CertTimeInfinity,
|
|
Key: key,
|
|
Serial: 1,
|
|
CertType: ssh.UserCert,
|
|
SignatureKey: testPublicKeys["rsa"],
|
|
Permissions: ssh.Permissions{
|
|
CriticalOptions: map[string]string{},
|
|
Extensions: map[string]string{},
|
|
},
|
|
}
|
|
if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil {
|
|
t.Fatalf("signcert: %v", err)
|
|
}
|
|
if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseConstraints(t *testing.T) {
|
|
// Test LifetimeSecs
|
|
var msg = constrainLifetimeAgentMsg{pseudorand.Uint32()}
|
|
lifetimeSecs, _, _, err := parseConstraints(ssh.Marshal(msg))
|
|
if err != nil {
|
|
t.Fatalf("parseConstraints: %v", err)
|
|
}
|
|
if lifetimeSecs != msg.LifetimeSecs {
|
|
t.Errorf("got lifetime %v, want %v", lifetimeSecs, msg.LifetimeSecs)
|
|
}
|
|
|
|
// Test ConfirmBeforeUse
|
|
_, confirmBeforeUse, _, err := parseConstraints([]byte{agentConstrainConfirm})
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if !confirmBeforeUse {
|
|
t.Error("got comfirmBeforeUse == false")
|
|
}
|
|
|
|
// Test ConstraintExtensions
|
|
var data []byte
|
|
var expect []ConstraintExtension
|
|
for i := 0; i < 10; i++ {
|
|
var ext = ConstraintExtension{
|
|
ExtensionName: fmt.Sprintf("name%d", i),
|
|
ExtensionDetails: []byte(fmt.Sprintf("details: %d", i)),
|
|
}
|
|
expect = append(expect, ext)
|
|
if i%2 == 0 {
|
|
data = append(data, agentConstrainExtension)
|
|
} else {
|
|
data = append(data, agentConstrainExtensionV00)
|
|
}
|
|
data = append(data, ssh.Marshal(ext)...)
|
|
}
|
|
_, _, extensions, err := parseConstraints(data)
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if !reflect.DeepEqual(expect, extensions) {
|
|
t.Errorf("got extension %v, want %v", extensions, expect)
|
|
}
|
|
|
|
// Test Malformed Constraint
|
|
_, _, _, err = parseConstraints([]byte{1})
|
|
if err != io.ErrUnexpectedEOF {
|
|
t.Errorf("got %v, want %v", err, io.ErrUnexpectedEOF)
|
|
}
|
|
|
|
// Test Unknown Constraint
|
|
_, _, _, err = parseConstraints([]byte{128})
|
|
if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseEd25519KeyShortPanic(t *testing.T) {
|
|
msg := ssh.Marshal(ed25519KeyMsg{
|
|
Type: ssh.KeyAlgoED25519,
|
|
Pub: []byte{1, 2, 3},
|
|
Priv: []byte{1, 2, 3, 4, 5},
|
|
})
|
|
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer a.Close()
|
|
defer b.Close()
|
|
|
|
done := make(chan error, 1)
|
|
go func() { done <- ServeAgent(NewKeyring(), a) }()
|
|
|
|
c := NewClient(b)
|
|
_, err = c.(*client).call(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keys, err := c.List()
|
|
if err != nil {
|
|
t.Fatalf("agent died: %v", err)
|
|
}
|
|
if len(keys) != 0 {
|
|
t.Error("short ed25519 key was accepted into keyring")
|
|
}
|
|
}
|