Skip to content

Commit

Permalink
update ssh module
Browse files Browse the repository at this point in the history
  • Loading branch information
hugefiver committed Dec 7, 2021
1 parent 720bb85 commit 3bc2d42
Show file tree
Hide file tree
Showing 15 changed files with 322 additions and 69 deletions.
24 changes: 22 additions & 2 deletions third/ssh/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"time"
)

// These constants from [PROTOCOL.certkeys] represent the algorithm names
// These constants from [PROTOCOL.certkeys] represent the key algorithm names
// for certificate types supported by this package.
const (
CertAlgoRSAv01 = "[email protected]"
Expand All @@ -27,6 +27,14 @@ const (
CertAlgoSKED25519v01 = "[email protected]"
)

// These constants from [PROTOCOL.certkeys] represent additional signature
// algorithm names for certificate types supported by this package.
const (
CertSigAlgoRSAv01 = "[email protected]"
CertSigAlgoRSASHA2256v01 = "[email protected]"
CertSigAlgoRSASHA2512v01 = "[email protected]"
)

// Certificate types distinguish between host and user
// certificates. The values can be set in the CertType field of
// Certificate.
Expand Down Expand Up @@ -423,6 +431,12 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
}
c.SignatureKey = authority.PublicKey()

if v, ok := authority.(AlgorithmSigner); ok {
if v.PublicKey().Type() == KeyAlgoRSA {
authority = &rsaSigner{v, SigAlgoRSASHA2512}
}
}

sig, err := authority.Sign(rand, c.bytesForSigning())
if err != nil {
return err
Expand All @@ -431,8 +445,14 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
return nil
}

// certAlgoNames includes a mapping from signature algorithms to the
// corresponding certificate signature algorithm. When a key type (such
// as ED25516) is associated with only one algorithm, the KeyAlgo
// constant is used instead of the SigAlgo.
var certAlgoNames = map[string]string{
KeyAlgoRSA: CertAlgoRSAv01,
SigAlgoRSA: CertSigAlgoRSAv01,
SigAlgoRSASHA2256: CertSigAlgoRSASHA2256v01,
SigAlgoRSASHA2512: CertSigAlgoRSASHA2512v01,
KeyAlgoDSA: CertAlgoDSAv01,
KeyAlgoECDSA256: CertAlgoECDSA256v01,
KeyAlgoECDSA384: CertAlgoECDSA384v01,
Expand Down
78 changes: 31 additions & 47 deletions third/ssh/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"io"
"net"
"reflect"
"testing"
"time"

"github.com/hugefiver/fakessh/third/ssh/testdata"
)

// Cert generated by ssh-keygen 6.0p1 Debian-4.
Expand Down Expand Up @@ -226,53 +226,33 @@ func TestHostKeyCert(t *testing.T) {
}
}

type legacyRSASigner struct {
Signer
}

func (s *legacyRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
v, ok := s.Signer.(AlgorithmSigner)
if !ok {
return nil, fmt.Errorf("invalid signer")
}
return v.SignWithAlgorithm(rand, data, SigAlgoRSA)
}

func TestCertTypes(t *testing.T) {
var testVars = []struct {
name string
keys func() Signer
name string
signer Signer
algo string
}{
{
name: CertAlgoECDSA256v01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap256"])
return s
},
},
{
name: CertAlgoECDSA384v01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap384"])
return s
},
},
{
name: CertAlgoECDSA521v01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap521"])
return s
},
},
{
name: CertAlgoED25519v01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["ed25519"])
return s
},
},
{
name: CertAlgoRSAv01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["rsa"])
return s
},
},
{
name: CertAlgoDSAv01,
keys: func() Signer {
s, _ := ParsePrivateKey(testdata.PEMBytes["dsa"])
return s
},
},
{CertAlgoECDSA256v01, testSigners["ecdsap256"], ""},
{CertAlgoECDSA384v01, testSigners["ecdsap384"], ""},
{CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
{CertAlgoED25519v01, testSigners["ed25519"], ""},
{CertAlgoRSAv01, testSigners["rsa"], SigAlgoRSASHA2512},
{CertAlgoRSAv01, &legacyRSASigner{testSigners["rsa"]}, SigAlgoRSA},
{CertAlgoRSAv01, testSigners["rsa-sha2-256"], SigAlgoRSASHA2512},
{CertAlgoRSAv01, testSigners["rsa-sha2-512"], SigAlgoRSASHA2512},
{CertAlgoDSAv01, testSigners["dsa"], ""},
}

k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand Down Expand Up @@ -304,7 +284,7 @@ func TestCertTypes(t *testing.T) {

go NewServerConn(c1, conf)

priv := m.keys()
priv := m.signer
if err != nil {
t.Fatalf("error generating ssh pubkey: %v", err)
}
Expand All @@ -320,6 +300,10 @@ func TestCertTypes(t *testing.T) {
t.Fatalf("error generating cert signer: %v", err)
}

if m.algo != "" && cert.Signature.Format != m.algo {
t.Errorf("expected %q signature format, got %q", m.algo, cert.Signature.Format)
}

config := &ClientConfig{
User: "user",
HostKeyCallback: func(h string, r net.Addr, k PublicKey) error { return nil },
Expand Down
8 changes: 8 additions & 0 deletions third/ssh/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
}
c.incIV()

if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}

padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
Expand Down Expand Up @@ -710,6 +714,10 @@ func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([
plain := c.buf[4:contentEnd]
s.XORKeyStream(plain, plain)

if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}

padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
Expand Down
100 changes: 100 additions & 0 deletions third/ssh/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import (
"bytes"
"crypto"
"crypto/rand"
"encoding/binary"
"io"
"testing"

"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/poly1305"
)

func TestDefaultCiphersExist(t *testing.T) {
Expand Down Expand Up @@ -129,3 +134,98 @@ func TestCBCOracleCounterMeasure(t *testing.T) {
lastRead = bytesRead
}
}

func TestCVE202143565(t *testing.T) {
tests := []struct {
cipher string
constructPacket func(packetCipher) io.Reader
}{
{
cipher: gcmCipherID,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*gcmCipher)
b := &bytes.Buffer{}
prefix := [4]byte{}
if _, err := b.Write(prefix[:]); err != nil {
t.Fatal(err)
}
internalCipher.buf = internalCipher.aead.Seal(internalCipher.buf[:0], internalCipher.iv, []byte{}, prefix[:])
if _, err := b.Write(internalCipher.buf); err != nil {
t.Fatal(err)
}
internalCipher.incIV()

return b
},
},
{
cipher: chacha20Poly1305ID,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*chacha20Poly1305Cipher)
b := &bytes.Buffer{}

nonce := make([]byte, 12)
s, err := chacha20.NewUnauthenticatedCipher(internalCipher.contentKey[:], nonce)
if err != nil {
t.Fatal(err)
}
var polyKey, discardBuf [32]byte
s.XORKeyStream(polyKey[:], polyKey[:])
s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes

internalCipher.buf = make([]byte, 4+poly1305.TagSize)
binary.BigEndian.PutUint32(internalCipher.buf, 0)
ls, err := chacha20.NewUnauthenticatedCipher(internalCipher.lengthKey[:], nonce)
if err != nil {
t.Fatal(err)
}
ls.XORKeyStream(internalCipher.buf, internalCipher.buf[:4])
if _, err := io.ReadFull(rand.Reader, internalCipher.buf[4:4]); err != nil {
t.Fatal(err)
}

s.XORKeyStream(internalCipher.buf[4:], internalCipher.buf[4:4])

var tag [poly1305.TagSize]byte
poly1305.Sum(&tag, internalCipher.buf[:4], &polyKey)

copy(internalCipher.buf[4:], tag[:])

if _, err := b.Write(internalCipher.buf); err != nil {
t.Fatal(err)
}

return b
},
},
}

for _, tc := range tests {
mac := "hmac-sha2-256"

kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: tc.cipher,
MAC: mac,
Compression: "none",
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
}
server, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
}

b := tc.constructPacket(client)

wantErr := "ssh: empty packet"
_, err = server.readCipherPacket(0, b)
if err == nil {
t.Fatalf("readCipherPacket(%q, %q): didn't fail with empty packet", tc.cipher, mac)
} else if err.Error() != wantErr {
t.Fatalf("readCipherPacket(%q, %q): unexpected error, got %q, want %q", tc.cipher, mac, err, wantErr)
}
}
}
15 changes: 14 additions & 1 deletion third/ssh/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,25 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e

// verifyHostKeySignature verifies the host key obtained in the key
// exchange.
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error {
sig, rest, ok := parseSignatureBody(result.Signature)
if len(rest) > 0 || !ok {
return errors.New("ssh: signature parse error")
}

// For keys, underlyingAlgo is exactly algo. For certificates,
// we have to look up the underlying key algorithm that SSH
// uses to evaluate signatures.
underlyingAlgo := algo
for sigAlgo, certAlgo := range certAlgoNames {
if certAlgo == algo {
underlyingAlgo = sigAlgo
}
}
if sig.Format != underlyingAlgo {
return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, underlyingAlgo)
}

return hostKey.Verify(result.H, sig)
}

Expand Down
41 changes: 41 additions & 0 deletions third/ssh/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package ssh

import (
"bytes"
"crypto/rand"
"strings"
"testing"
)
Expand Down Expand Up @@ -116,6 +118,45 @@ func TestHostKeyCheck(t *testing.T) {
}
}

func TestVerifyHostKeySignature(t *testing.T) {
for _, tt := range []struct {
key string
signAlgo string
verifyAlgo string
wantError string
}{
{"rsa", SigAlgoRSA, SigAlgoRSA, ""},
{"rsa", SigAlgoRSASHA2256, SigAlgoRSASHA2256, ""},
{"rsa", SigAlgoRSA, SigAlgoRSASHA2512, `ssh: invalid signature algorithm "ssh-rsa", expected "rsa-sha2-512"`},
{"ed25519", KeyAlgoED25519, KeyAlgoED25519, ""},
} {
key := testSigners[tt.key].PublicKey()
s, ok := testSigners[tt.key].(AlgorithmSigner)
if !ok {
t.Fatalf("needed an AlgorithmSigner")
}
sig, err := s.SignWithAlgorithm(rand.Reader, []byte("test"), tt.signAlgo)
if err != nil {
t.Fatalf("couldn't sign: %q", err)
}

b := bytes.Buffer{}
writeString(&b, []byte(sig.Format))
writeString(&b, sig.Blob)

result := kexResult{Signature: b.Bytes(), H: []byte("test")}

err = verifyHostKeySignature(key, tt.verifyAlgo, &result)
if err != nil {
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
t.Errorf("got error %q, expecting %q", err.Error(), tt.wantError)
}
} else if tt.wantError != "" {
t.Errorf("succeeded, but want error string %q", tt.wantError)
}
}
}

func TestBannerCallback(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions third/ssh/commit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5770296d904e90f15f38f77dfc2e43fdf5efc083
Loading

0 comments on commit 3bc2d42

Please sign in to comment.