Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
905d78a692 | ||
|
|
ebb717d630 | ||
|
|
0da2a6a1bb | ||
|
|
5defcc193a | ||
|
|
d042a396a6 | ||
|
|
b92bf9480d | ||
|
|
6f79b5a20c | ||
|
|
8d0d405eed | ||
|
|
b91329d961 | ||
|
|
7067223927 | ||
|
|
0d2316b267 |
+52
-58
@@ -33,6 +33,9 @@
|
||||
#define CONSTBASE R16
|
||||
#define BLOCKS R17
|
||||
|
||||
// for VPERMXOR
|
||||
#define MASK R18
|
||||
|
||||
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
|
||||
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
|
||||
DATA consts<>+0x10(SB)/8, $0x0000000000000001
|
||||
@@ -53,7 +56,11 @@ DATA consts<>+0x80(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x90(SB)/8, $0x0000000100000000
|
||||
DATA consts<>+0x98(SB)/8, $0x0000000300000002
|
||||
GLOBL consts<>(SB), RODATA, $0xa0
|
||||
DATA consts<>+0xa0(SB)/8, $0x5566774411223300
|
||||
DATA consts<>+0xa8(SB)/8, $0xddeeffcc99aabb88
|
||||
DATA consts<>+0xb0(SB)/8, $0x6677445522330011
|
||||
DATA consts<>+0xb8(SB)/8, $0xeeffccddaabb8899
|
||||
GLOBL consts<>(SB), RODATA, $0xc0
|
||||
|
||||
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
@@ -70,6 +77,9 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
MOVD $48, R10
|
||||
MOVD $64, R11
|
||||
SRD $6, LEN, BLOCKS
|
||||
// for VPERMXOR
|
||||
MOVD $consts<>+0xa0(SB), MASK
|
||||
MOVD $16, R20
|
||||
// V16
|
||||
LXVW4X (CONSTBASE)(R0), VS48
|
||||
ADD $80,CONSTBASE
|
||||
@@ -87,6 +97,10 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
// V28
|
||||
LXVW4X (CONSTBASE)(R11), VS60
|
||||
|
||||
// Load mask constants for VPERMXOR
|
||||
LXVW4X (MASK)(R0), V20
|
||||
LXVW4X (MASK)(R20), V21
|
||||
|
||||
// splat slot from V19 -> V26
|
||||
VSPLTW $0, V19, V26
|
||||
|
||||
@@ -97,7 +111,7 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
|
||||
MOVD $10, R14
|
||||
MOVD R14, CTR
|
||||
|
||||
PCALIGN $16
|
||||
loop_outer_vsx:
|
||||
// V0, V1, V2, V3
|
||||
LXVW4X (R0)(CONSTBASE), VS32
|
||||
@@ -128,22 +142,17 @@ loop_outer_vsx:
|
||||
VSPLTISW $12, V28
|
||||
VSPLTISW $8, V29
|
||||
VSPLTISW $7, V30
|
||||
|
||||
PCALIGN $16
|
||||
loop_vsx:
|
||||
VADDUWM V0, V4, V0
|
||||
VADDUWM V1, V5, V1
|
||||
VADDUWM V2, V6, V2
|
||||
VADDUWM V3, V7, V3
|
||||
|
||||
VXOR V12, V0, V12
|
||||
VXOR V13, V1, V13
|
||||
VXOR V14, V2, V14
|
||||
VXOR V15, V3, V15
|
||||
|
||||
VRLW V12, V27, V12
|
||||
VRLW V13, V27, V13
|
||||
VRLW V14, V27, V14
|
||||
VRLW V15, V27, V15
|
||||
VPERMXOR V12, V0, V21, V12
|
||||
VPERMXOR V13, V1, V21, V13
|
||||
VPERMXOR V14, V2, V21, V14
|
||||
VPERMXOR V15, V3, V21, V15
|
||||
|
||||
VADDUWM V8, V12, V8
|
||||
VADDUWM V9, V13, V9
|
||||
@@ -165,15 +174,10 @@ loop_vsx:
|
||||
VADDUWM V2, V6, V2
|
||||
VADDUWM V3, V7, V3
|
||||
|
||||
VXOR V12, V0, V12
|
||||
VXOR V13, V1, V13
|
||||
VXOR V14, V2, V14
|
||||
VXOR V15, V3, V15
|
||||
|
||||
VRLW V12, V29, V12
|
||||
VRLW V13, V29, V13
|
||||
VRLW V14, V29, V14
|
||||
VRLW V15, V29, V15
|
||||
VPERMXOR V12, V0, V20, V12
|
||||
VPERMXOR V13, V1, V20, V13
|
||||
VPERMXOR V14, V2, V20, V14
|
||||
VPERMXOR V15, V3, V20, V15
|
||||
|
||||
VADDUWM V8, V12, V8
|
||||
VADDUWM V9, V13, V9
|
||||
@@ -195,15 +199,10 @@ loop_vsx:
|
||||
VADDUWM V2, V7, V2
|
||||
VADDUWM V3, V4, V3
|
||||
|
||||
VXOR V15, V0, V15
|
||||
VXOR V12, V1, V12
|
||||
VXOR V13, V2, V13
|
||||
VXOR V14, V3, V14
|
||||
|
||||
VRLW V15, V27, V15
|
||||
VRLW V12, V27, V12
|
||||
VRLW V13, V27, V13
|
||||
VRLW V14, V27, V14
|
||||
VPERMXOR V15, V0, V21, V15
|
||||
VPERMXOR V12, V1, V21, V12
|
||||
VPERMXOR V13, V2, V21, V13
|
||||
VPERMXOR V14, V3, V21, V14
|
||||
|
||||
VADDUWM V10, V15, V10
|
||||
VADDUWM V11, V12, V11
|
||||
@@ -225,15 +224,10 @@ loop_vsx:
|
||||
VADDUWM V2, V7, V2
|
||||
VADDUWM V3, V4, V3
|
||||
|
||||
VXOR V15, V0, V15
|
||||
VXOR V12, V1, V12
|
||||
VXOR V13, V2, V13
|
||||
VXOR V14, V3, V14
|
||||
|
||||
VRLW V15, V29, V15
|
||||
VRLW V12, V29, V12
|
||||
VRLW V13, V29, V13
|
||||
VRLW V14, V29, V14
|
||||
VPERMXOR V15, V0, V20, V15
|
||||
VPERMXOR V12, V1, V20, V12
|
||||
VPERMXOR V13, V2, V20, V13
|
||||
VPERMXOR V14, V3, V20, V14
|
||||
|
||||
VADDUWM V10, V15, V10
|
||||
VADDUWM V11, V12, V11
|
||||
@@ -249,48 +243,48 @@ loop_vsx:
|
||||
VRLW V6, V30, V6
|
||||
VRLW V7, V30, V7
|
||||
VRLW V4, V30, V4
|
||||
BC 16, LT, loop_vsx
|
||||
BDNZ loop_vsx
|
||||
|
||||
VADDUWM V12, V26, V12
|
||||
|
||||
WORD $0x13600F8C // VMRGEW V0, V1, V27
|
||||
WORD $0x13821F8C // VMRGEW V2, V3, V28
|
||||
VMRGEW V0, V1, V27
|
||||
VMRGEW V2, V3, V28
|
||||
|
||||
WORD $0x10000E8C // VMRGOW V0, V1, V0
|
||||
WORD $0x10421E8C // VMRGOW V2, V3, V2
|
||||
VMRGOW V0, V1, V0
|
||||
VMRGOW V2, V3, V2
|
||||
|
||||
WORD $0x13A42F8C // VMRGEW V4, V5, V29
|
||||
WORD $0x13C63F8C // VMRGEW V6, V7, V30
|
||||
VMRGEW V4, V5, V29
|
||||
VMRGEW V6, V7, V30
|
||||
|
||||
XXPERMDI VS32, VS34, $0, VS33
|
||||
XXPERMDI VS32, VS34, $3, VS35
|
||||
XXPERMDI VS59, VS60, $0, VS32
|
||||
XXPERMDI VS59, VS60, $3, VS34
|
||||
|
||||
WORD $0x10842E8C // VMRGOW V4, V5, V4
|
||||
WORD $0x10C63E8C // VMRGOW V6, V7, V6
|
||||
VMRGOW V4, V5, V4
|
||||
VMRGOW V6, V7, V6
|
||||
|
||||
WORD $0x13684F8C // VMRGEW V8, V9, V27
|
||||
WORD $0x138A5F8C // VMRGEW V10, V11, V28
|
||||
VMRGEW V8, V9, V27
|
||||
VMRGEW V10, V11, V28
|
||||
|
||||
XXPERMDI VS36, VS38, $0, VS37
|
||||
XXPERMDI VS36, VS38, $3, VS39
|
||||
XXPERMDI VS61, VS62, $0, VS36
|
||||
XXPERMDI VS61, VS62, $3, VS38
|
||||
|
||||
WORD $0x11084E8C // VMRGOW V8, V9, V8
|
||||
WORD $0x114A5E8C // VMRGOW V10, V11, V10
|
||||
VMRGOW V8, V9, V8
|
||||
VMRGOW V10, V11, V10
|
||||
|
||||
WORD $0x13AC6F8C // VMRGEW V12, V13, V29
|
||||
WORD $0x13CE7F8C // VMRGEW V14, V15, V30
|
||||
VMRGEW V12, V13, V29
|
||||
VMRGEW V14, V15, V30
|
||||
|
||||
XXPERMDI VS40, VS42, $0, VS41
|
||||
XXPERMDI VS40, VS42, $3, VS43
|
||||
XXPERMDI VS59, VS60, $0, VS40
|
||||
XXPERMDI VS59, VS60, $3, VS42
|
||||
|
||||
WORD $0x118C6E8C // VMRGOW V12, V13, V12
|
||||
WORD $0x11CE7E8C // VMRGOW V14, V15, V14
|
||||
VMRGOW V12, V13, V12
|
||||
VMRGOW V14, V15, V14
|
||||
|
||||
VSPLTISW $4, V27
|
||||
VADDUWM V26, V27, V26
|
||||
@@ -431,7 +425,7 @@ tail_vsx:
|
||||
ADD $-1, R11, R12
|
||||
ADD $-1, INP
|
||||
ADD $-1, OUT
|
||||
|
||||
PCALIGN $16
|
||||
looptail_vsx:
|
||||
// Copying the result to OUT
|
||||
// in bytes.
|
||||
@@ -439,7 +433,7 @@ looptail_vsx:
|
||||
MOVBZU 1(INP), TMP
|
||||
XOR KEY, TMP, KEY
|
||||
MOVBU KEY, 1(OUT)
|
||||
BC 16, LT, looptail_vsx
|
||||
BDNZ looptail_vsx
|
||||
|
||||
// Clear the stack values
|
||||
STXVW4X VS48, (R11)(R0)
|
||||
|
||||
@@ -4,8 +4,8 @@ go 1.18
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.21.0 // tagx:ignore
|
||||
golang.org/x/sys v0.17.0
|
||||
golang.org/x/term v0.17.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.20.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.14.0 // indirect
|
||||
require golang.org/x/text v0.15.0 // indirect
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
|
||||
@@ -57,8 +57,8 @@ func CommandContext(t testing.TB, ctx context.Context, name string, args ...stri
|
||||
// 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.
|
||||
// field), and a second one for the delay between the process being
|
||||
// terminated 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.)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
|
||||
//
|
||||
// Deprecated: MD4 is cryptographically broken and should should only be used
|
||||
// Deprecated: MD4 is cryptographically broken and should only be used
|
||||
// where compatibility with legacy systems, not security, is the goal. Instead,
|
||||
// use a secure hash like SHA-256 (from crypto/sha256).
|
||||
package md4 // import "golang.org/x/crypto/md4"
|
||||
|
||||
@@ -132,7 +132,7 @@ func TestRevokedUserID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission.
|
||||
// TestExternallyRevocableKey attempts to load and parse a key with a third party revocation permission.
|
||||
func TestExternallyRevocableKey(t *testing.T) {
|
||||
kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||
if err != nil {
|
||||
|
||||
+17
-2
@@ -143,6 +143,12 @@ func (s *asmState) Write(b []byte) (int, error) {
|
||||
|
||||
// Read squeezes an arbitrary number of bytes from the sponge.
|
||||
func (s *asmState) Read(out []byte) (n int, err error) {
|
||||
// The 'compute last message digest' instruction only stores the digest
|
||||
// at the first operand (dst) for SHAKE functions.
|
||||
if s.function != shake_128 && s.function != shake_256 {
|
||||
panic("sha3: can only call Read for SHAKE functions")
|
||||
}
|
||||
|
||||
n = len(out)
|
||||
|
||||
// need to pad if we were absorbing
|
||||
@@ -202,8 +208,17 @@ func (s *asmState) Sum(b []byte) []byte {
|
||||
|
||||
// Hash the buffer. Note that we don't clear it because we
|
||||
// aren't updating the state.
|
||||
klmd(s.function, &a, nil, s.buf)
|
||||
return append(b, a[:s.outputLen]...)
|
||||
switch s.function {
|
||||
case sha3_224, sha3_256, sha3_384, sha3_512:
|
||||
klmd(s.function, &a, nil, s.buf)
|
||||
return append(b, a[:s.outputLen]...)
|
||||
case shake_128, shake_256:
|
||||
d := make([]byte, s.outputLen, 64)
|
||||
klmd(s.function, &a, d, s.buf)
|
||||
return append(b, d[:s.outputLen]...)
|
||||
default:
|
||||
panic("sha3: unknown function")
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the Hash to its initial state.
|
||||
|
||||
@@ -188,6 +188,34 @@ func TestKeccak(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestShakeSum tests that the output of Sum matches the output of Read.
|
||||
func TestShakeSum(t *testing.T) {
|
||||
tests := [...]struct {
|
||||
name string
|
||||
hash ShakeHash
|
||||
expectedLen int
|
||||
}{
|
||||
{"SHAKE128", NewShake128(), 32},
|
||||
{"SHAKE256", NewShake256(), 64},
|
||||
{"cSHAKE128", NewCShake128([]byte{'X'}, nil), 32},
|
||||
{"cSHAKE256", NewCShake256([]byte{'X'}, nil), 64},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
s := test.hash.Sum(nil)
|
||||
if len(s) != test.expectedLen {
|
||||
t.Errorf("Unexpected digest length: got %d, want %d", len(s), test.expectedLen)
|
||||
}
|
||||
r := make([]byte, test.expectedLen)
|
||||
test.hash.Read(r)
|
||||
if !bytes.Equal(s, r) {
|
||||
t.Errorf("Mismatch between Sum and Read:\nSum: %s\nRead: %s", hex.EncodeToString(s), hex.EncodeToString(r))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnalignedWrite tests that writing data in an arbitrary pattern with
|
||||
// small input buffers.
|
||||
func TestUnalignedWrite(t *testing.T) {
|
||||
|
||||
+9
-9
@@ -367,21 +367,21 @@ func TestCertTypes(t *testing.T) {
|
||||
|
||||
func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
||||
type testcase struct {
|
||||
sigAlgo string
|
||||
algoritms []string
|
||||
sigAlgo string
|
||||
algorithms []string
|
||||
}
|
||||
cases := []testcase{
|
||||
{
|
||||
sigAlgo: KeyAlgoRSA,
|
||||
algoritms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
sigAlgo: KeyAlgoRSA,
|
||||
algorithms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
},
|
||||
{
|
||||
sigAlgo: KeyAlgoRSASHA256,
|
||||
algoritms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
sigAlgo: KeyAlgoRSASHA256,
|
||||
algorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
},
|
||||
{
|
||||
sigAlgo: KeyAlgoRSASHA512,
|
||||
algoritms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||
sigAlgo: KeyAlgoRSASHA512,
|
||||
algorithms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.sigAlgo, func(t *testing.T) {
|
||||
signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), c.algoritms)
|
||||
signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), c.algorithms)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSignerWithAlgorithms error: %v", err)
|
||||
}
|
||||
|
||||
+11
-3
@@ -404,10 +404,10 @@ func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, e
|
||||
return false, err
|
||||
}
|
||||
|
||||
return confirmKeyAck(key, algo, c)
|
||||
return confirmKeyAck(key, c)
|
||||
}
|
||||
|
||||
func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) {
|
||||
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
|
||||
for {
|
||||
@@ -425,7 +425,15 @@ func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) {
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if msg.Algo != algo || !bytes.Equal(msg.PubKey, pubKey) {
|
||||
// According to RFC 4252 Section 7 the algorithm in
|
||||
// SSH_MSG_USERAUTH_PK_OK should match that of the request but some
|
||||
// servers send the key type instead. OpenSSH allows any algorithm
|
||||
// that matches the public key, so we do the same.
|
||||
// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
|
||||
if !contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
|
||||
return false, nil
|
||||
}
|
||||
if !bytes.Equal(msg.PubKey, pubKey) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
|
||||
+1
-1
@@ -384,7 +384,7 @@ func ExampleCertificate_SignCert() {
|
||||
}
|
||||
mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
log.Fatal("unable to create signer with algoritms: ", err)
|
||||
log.Fatal("unable to create signer with algorithms: ", err)
|
||||
}
|
||||
certificate := ssh.Certificate{
|
||||
Key: publicKey,
|
||||
|
||||
+120
-50
@@ -426,6 +426,35 @@ func (l ServerAuthError) Error() string {
|
||||
return "[" + strings.Join(errs, ", ") + "]"
|
||||
}
|
||||
|
||||
// ServerAuthCallbacks defines server-side authentication callbacks.
|
||||
type ServerAuthCallbacks struct {
|
||||
// PasswordCallback behaves like [ServerConfig.PasswordCallback].
|
||||
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
|
||||
|
||||
// PublicKeyCallback behaves like [ServerConfig.PublicKeyCallback].
|
||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// KeyboardInteractiveCallback behaves like [ServerConfig.KeyboardInteractiveCallback].
|
||||
KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
|
||||
|
||||
// GSSAPIWithMICConfig behaves like [ServerConfig.GSSAPIWithMICConfig].
|
||||
GSSAPIWithMICConfig *GSSAPIWithMICConfig
|
||||
}
|
||||
|
||||
// PartialSuccessError can be returned by any of the [ServerConfig]
|
||||
// authentication callbacks to indicate to the client that authentication has
|
||||
// partially succeeded, but further steps are required.
|
||||
type PartialSuccessError struct {
|
||||
// Next defines the authentication callbacks to apply to further steps. The
|
||||
// available methods communicated to the client are based on the non-nil
|
||||
// ServerAuthCallbacks fields.
|
||||
Next ServerAuthCallbacks
|
||||
}
|
||||
|
||||
func (p *PartialSuccessError) Error() string {
|
||||
return "ssh: authenticated with partial success"
|
||||
}
|
||||
|
||||
// ErrNoAuth is the error value returned if no
|
||||
// authentication method has been passed yet. This happens as a normal
|
||||
// part of the authentication loop, since the client first tries
|
||||
@@ -439,8 +468,18 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
|
||||
var perms *Permissions
|
||||
|
||||
authFailures := 0
|
||||
noneAuthCount := 0
|
||||
var authErrs []error
|
||||
var displayedBanner bool
|
||||
partialSuccessReturned := false
|
||||
// Set the initial authentication callbacks from the config. They can be
|
||||
// changed if a PartialSuccessError is returned.
|
||||
authConfig := ServerAuthCallbacks{
|
||||
PasswordCallback: config.PasswordCallback,
|
||||
PublicKeyCallback: config.PublicKeyCallback,
|
||||
KeyboardInteractiveCallback: config.KeyboardInteractiveCallback,
|
||||
GSSAPIWithMICConfig: config.GSSAPIWithMICConfig,
|
||||
}
|
||||
|
||||
userAuthLoop:
|
||||
for {
|
||||
@@ -471,6 +510,11 @@ userAuthLoop:
|
||||
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
||||
}
|
||||
|
||||
if s.user != userAuthReq.User && partialSuccessReturned {
|
||||
return nil, fmt.Errorf("ssh: client changed the user after a partial success authentication, previous user %q, current user %q",
|
||||
s.user, userAuthReq.User)
|
||||
}
|
||||
|
||||
s.user = userAuthReq.User
|
||||
|
||||
if !displayedBanner && config.BannerCallback != nil {
|
||||
@@ -491,20 +535,18 @@ userAuthLoop:
|
||||
|
||||
switch userAuthReq.Method {
|
||||
case "none":
|
||||
if config.NoClientAuth {
|
||||
noneAuthCount++
|
||||
// We don't allow none authentication after a partial success
|
||||
// response.
|
||||
if config.NoClientAuth && !partialSuccessReturned {
|
||||
if config.NoClientAuthCallback != nil {
|
||||
perms, authErr = config.NoClientAuthCallback(s)
|
||||
} else {
|
||||
authErr = nil
|
||||
}
|
||||
}
|
||||
|
||||
// allow initial attempt of 'none' without penalty
|
||||
if authFailures == 0 {
|
||||
authFailures--
|
||||
}
|
||||
case "password":
|
||||
if config.PasswordCallback == nil {
|
||||
if authConfig.PasswordCallback == nil {
|
||||
authErr = errors.New("ssh: password auth not configured")
|
||||
break
|
||||
}
|
||||
@@ -518,17 +560,17 @@ userAuthLoop:
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
perms, authErr = config.PasswordCallback(s, password)
|
||||
perms, authErr = authConfig.PasswordCallback(s, password)
|
||||
case "keyboard-interactive":
|
||||
if config.KeyboardInteractiveCallback == nil {
|
||||
if authConfig.KeyboardInteractiveCallback == nil {
|
||||
authErr = errors.New("ssh: keyboard-interactive auth not configured")
|
||||
break
|
||||
}
|
||||
|
||||
prompter := &sshClientKeyboardInteractive{s}
|
||||
perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
|
||||
perms, authErr = authConfig.KeyboardInteractiveCallback(s, prompter.Challenge)
|
||||
case "publickey":
|
||||
if config.PublicKeyCallback == nil {
|
||||
if authConfig.PublicKeyCallback == nil {
|
||||
authErr = errors.New("ssh: publickey auth not configured")
|
||||
break
|
||||
}
|
||||
@@ -562,11 +604,18 @@ userAuthLoop:
|
||||
if !ok {
|
||||
candidate.user = s.user
|
||||
candidate.pubKeyData = pubKeyData
|
||||
candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
|
||||
if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
||||
candidate.result = checkSourceAddress(
|
||||
candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey)
|
||||
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||
|
||||
if (candidate.result == nil || isPartialSuccessError) &&
|
||||
candidate.perms != nil &&
|
||||
candidate.perms.CriticalOptions != nil &&
|
||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
||||
if err := checkSourceAddress(
|
||||
s.RemoteAddr(),
|
||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption])
|
||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption]); err != nil {
|
||||
candidate.result = err
|
||||
}
|
||||
}
|
||||
cache.add(candidate)
|
||||
}
|
||||
@@ -578,8 +627,8 @@ userAuthLoop:
|
||||
if len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
if candidate.result == nil {
|
||||
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||
if candidate.result == nil || isPartialSuccessError {
|
||||
okMsg := userAuthPubKeyOkMsg{
|
||||
Algo: algo,
|
||||
PubKey: pubKeyData,
|
||||
@@ -629,11 +678,11 @@ userAuthLoop:
|
||||
perms = candidate.perms
|
||||
}
|
||||
case "gssapi-with-mic":
|
||||
if config.GSSAPIWithMICConfig == nil {
|
||||
if authConfig.GSSAPIWithMICConfig == nil {
|
||||
authErr = errors.New("ssh: gssapi-with-mic auth not configured")
|
||||
break
|
||||
}
|
||||
gssapiConfig := config.GSSAPIWithMICConfig
|
||||
gssapiConfig := authConfig.GSSAPIWithMICConfig
|
||||
userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload)
|
||||
if err != nil {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
@@ -689,49 +738,70 @@ userAuthLoop:
|
||||
break userAuthLoop
|
||||
}
|
||||
|
||||
authFailures++
|
||||
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
|
||||
// If we have hit the max attempts, don't bother sending the
|
||||
// final SSH_MSG_USERAUTH_FAILURE message, since there are
|
||||
// no more authentication methods which can be attempted,
|
||||
// and this message may cause the client to re-attempt
|
||||
// authentication while we send the disconnect message.
|
||||
// Continue, and trigger the disconnect at the start of
|
||||
// the loop.
|
||||
//
|
||||
// The SSH specification is somewhat confusing about this,
|
||||
// RFC 4252 Section 5.1 requires each authentication failure
|
||||
// be responded to with a respective SSH_MSG_USERAUTH_FAILURE
|
||||
// message, but Section 4 says the server should disconnect
|
||||
// after some number of attempts, but it isn't explicit which
|
||||
// message should take precedence (i.e. should there be a failure
|
||||
// message than a disconnect message, or if we are going to
|
||||
// disconnect, should we only send that message.)
|
||||
//
|
||||
// Either way, OpenSSH disconnects immediately after the last
|
||||
// failed authnetication attempt, and given they are typically
|
||||
// considered the golden implementation it seems reasonable
|
||||
// to match that behavior.
|
||||
continue
|
||||
var failureMsg userAuthFailureMsg
|
||||
|
||||
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
||||
// After a partial success error we don't allow changing the user
|
||||
// name and execute the NoClientAuthCallback.
|
||||
partialSuccessReturned = true
|
||||
|
||||
// In case a partial success is returned, the server may send
|
||||
// a new set of authentication methods.
|
||||
authConfig = partialSuccess.Next
|
||||
|
||||
// Reset pubkey cache, as the new PublicKeyCallback might
|
||||
// accept a different set of public keys.
|
||||
cache = pubKeyCache{}
|
||||
|
||||
// Send back a partial success message to the user.
|
||||
failureMsg.PartialSuccess = true
|
||||
} else {
|
||||
// Allow initial attempt of 'none' without penalty.
|
||||
if authFailures > 0 || userAuthReq.Method != "none" || noneAuthCount != 1 {
|
||||
authFailures++
|
||||
}
|
||||
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
|
||||
// If we have hit the max attempts, don't bother sending the
|
||||
// final SSH_MSG_USERAUTH_FAILURE message, since there are
|
||||
// no more authentication methods which can be attempted,
|
||||
// and this message may cause the client to re-attempt
|
||||
// authentication while we send the disconnect message.
|
||||
// Continue, and trigger the disconnect at the start of
|
||||
// the loop.
|
||||
//
|
||||
// The SSH specification is somewhat confusing about this,
|
||||
// RFC 4252 Section 5.1 requires each authentication failure
|
||||
// be responded to with a respective SSH_MSG_USERAUTH_FAILURE
|
||||
// message, but Section 4 says the server should disconnect
|
||||
// after some number of attempts, but it isn't explicit which
|
||||
// message should take precedence (i.e. should there be a failure
|
||||
// message than a disconnect message, or if we are going to
|
||||
// disconnect, should we only send that message.)
|
||||
//
|
||||
// Either way, OpenSSH disconnects immediately after the last
|
||||
// failed authentication attempt, and given they are typically
|
||||
// considered the golden implementation it seems reasonable
|
||||
// to match that behavior.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var failureMsg userAuthFailureMsg
|
||||
if config.PasswordCallback != nil {
|
||||
if authConfig.PasswordCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||
}
|
||||
if config.PublicKeyCallback != nil {
|
||||
if authConfig.PublicKeyCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
||||
}
|
||||
if config.KeyboardInteractiveCallback != nil {
|
||||
if authConfig.KeyboardInteractiveCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
||||
}
|
||||
if config.GSSAPIWithMICConfig != nil && config.GSSAPIWithMICConfig.Server != nil &&
|
||||
config.GSSAPIWithMICConfig.AllowLogin != nil {
|
||||
if authConfig.GSSAPIWithMICConfig != nil && authConfig.GSSAPIWithMICConfig.Server != nil &&
|
||||
authConfig.GSSAPIWithMICConfig.AllowLogin != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic")
|
||||
}
|
||||
|
||||
if len(failureMsg.Methods) == 0 {
|
||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
return nil, errors.New("ssh: no authentication methods available")
|
||||
}
|
||||
|
||||
if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil {
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright 2024 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 ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func doClientServerAuth(t *testing.T, serverConfig *ServerConfig, clientConfig *ClientConfig) ([]error, error) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
var serverAuthErrors []error
|
||||
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
|
||||
serverAuthErrors = append(serverAuthErrors, err)
|
||||
}
|
||||
go newServer(c1, serverConfig)
|
||||
c, _, _, err := NewClientConn(c2, "", clientConfig)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
}
|
||||
return serverAuthErrors, err
|
||||
}
|
||||
|
||||
func TestMultiStepAuth(t *testing.T) {
|
||||
// This user can login with password, public key or public key + password.
|
||||
username := "testuser"
|
||||
// This user can login with public key + password only.
|
||||
usernameSecondFactor := "testuser_second_factor"
|
||||
errPwdAuthFailed := errors.New("password auth failed")
|
||||
errWrongSequence := errors.New("wrong sequence")
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == usernameSecondFactor {
|
||||
return nil, errWrongSequence
|
||||
}
|
||||
if conn.User() == username && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errPwdAuthFailed
|
||||
},
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
if conn.User() == usernameSecondFactor {
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errPwdAuthFailed
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: usernameSecondFactor,
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - partial success
|
||||
// - nil
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
|
||||
t.Fatalf("expected partial success error, got: %v", serverAuthErrors[1])
|
||||
}
|
||||
// Now test a wrong sequence.
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
Password(clientPassword),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("client login with wrong sequence must fail")
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - wrong sequence
|
||||
// - partial success
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if serverAuthErrors[1] != errWrongSequence {
|
||||
t.Fatal("server not returned wrong sequence")
|
||||
}
|
||||
if _, ok := serverAuthErrors[2].(*PartialSuccessError); !ok {
|
||||
t.Fatalf("expected partial success error, got: %v", serverAuthErrors[2])
|
||||
}
|
||||
// Now test using a correct sequence but a wrong password before the right
|
||||
// one.
|
||||
n := 0
|
||||
passwords := []string{"WRONG", "WRONG", clientPassword}
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
RetryableAuthMethod(PasswordCallback(func() (string, error) {
|
||||
p := passwords[n]
|
||||
n++
|
||||
return p, nil
|
||||
}), 3),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - partial success
|
||||
// - wrong password
|
||||
// - wrong password
|
||||
// - nil
|
||||
if len(serverAuthErrors) != 5 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
if serverAuthErrors[2] != errPwdAuthFailed {
|
||||
t.Fatal("server not returned password authentication failed")
|
||||
}
|
||||
if serverAuthErrors[3] != errPwdAuthFailed {
|
||||
t.Fatal("server not returned password authentication failed")
|
||||
}
|
||||
// Only password authentication should fail.
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
Password(clientPassword),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("client login with password only must fail")
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - wrong sequence
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if serverAuthErrors[1] != errWrongSequence {
|
||||
t.Fatal("server not returned wrong sequence")
|
||||
}
|
||||
|
||||
// Only public key authentication should fail.
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("client login with public key only must fail")
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - partial success
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
|
||||
// Public key and wrong password.
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password("WRONG"),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("client login with wrong password after public key must fail")
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - partial success
|
||||
// - password auth failed
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
if serverAuthErrors[2] != errPwdAuthFailed {
|
||||
t.Fatal("server not returned password authentication failed")
|
||||
}
|
||||
|
||||
// Public key, public key again and then correct password. Public key
|
||||
// authentication is attempted only once because the partial success error
|
||||
// returns only "password" as the allowed authentication method.
|
||||
clientConfig.Auth = []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
// The error sequence is:
|
||||
// - no auth passed yet
|
||||
// - partial success
|
||||
// - nil
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
|
||||
// The unrestricted username can do anything
|
||||
clientConfig = &ClientConfig{
|
||||
User: username,
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unrestricted client login error: %s", err)
|
||||
}
|
||||
|
||||
clientConfig = &ClientConfig{
|
||||
User: username,
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unrestricted client login error: %s", err)
|
||||
}
|
||||
|
||||
clientConfig = &ClientConfig{
|
||||
User: username,
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unrestricted client login error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicAuthCallbacks(t *testing.T) {
|
||||
user1 := "user1"
|
||||
user2 := "user2"
|
||||
errInvalidCredentials := errors.New("invalid credentials")
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
|
||||
switch conn.User() {
|
||||
case user1:
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == user1 && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errInvalidCredentials
|
||||
},
|
||||
},
|
||||
}
|
||||
case user2:
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
if conn.User() == user2 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return nil, errInvalidCredentials
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, errInvalidCredentials
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: user1,
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
// The error sequence is:
|
||||
// - partial success
|
||||
// - nil
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
|
||||
clientConfig = &ClientConfig{
|
||||
User: user2,
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
// The error sequence is:
|
||||
// - partial success
|
||||
// - nil
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
|
||||
// user1 cannot login with public key
|
||||
clientConfig = &ClientConfig{
|
||||
User: user1,
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("user1 login with public key must fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no supported methods remain") {
|
||||
t.Errorf("got %v, expected 'no supported methods remain'", err)
|
||||
}
|
||||
if len(serverAuthErrors) != 1 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
// user2 cannot login with password
|
||||
clientConfig = &ClientConfig{
|
||||
User: user2,
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("user2 login with password must fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no supported methods remain") {
|
||||
t.Errorf("got %v, expected 'no supported methods remain'", err)
|
||||
}
|
||||
if len(serverAuthErrors) != 1 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
|
||||
t.Fatal("server not returned partial success")
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -62,6 +64,133 @@ func TestClientAuthRestrictedPublicKeyAlgos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxAuthTriesNoneMethod(t *testing.T) {
|
||||
username := "testuser"
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 2,
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == username && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
var serverAuthErrors []error
|
||||
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
|
||||
serverAuthErrors = append(serverAuthErrors, err)
|
||||
}
|
||||
go newServer(c1, serverConfig)
|
||||
|
||||
clientConfig := ClientConfig{
|
||||
User: username,
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
clientConfig.SetDefaults()
|
||||
// Our client will send 'none' auth only once, so we need to send the
|
||||
// requests manually.
|
||||
c := &connection{
|
||||
sshConn: sshConn{
|
||||
conn: c2,
|
||||
user: username,
|
||||
clientVersion: []byte(packageVersion),
|
||||
},
|
||||
}
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to exchange version: %v", err)
|
||||
}
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, clientConfig.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, &clientConfig, "", c.sshConn.RemoteAddr())
|
||||
if err := c.transport.waitSession(); err != nil {
|
||||
t.Fatalf("unable to wait session: %v", err)
|
||||
}
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||
t.Fatalf("unable to send ssh-userauth message: %v", err)
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(packet) > 0 && packet[0] == msgExtInfo {
|
||||
packet, err = c.transport.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
var serviceAccept serviceAcceptMsg
|
||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i <= serverConfig.MaxAuthTries; i++ {
|
||||
auth := new(noneAuth)
|
||||
_, _, err := auth.auth(c.sessionID, clientConfig.User, c.transport, clientConfig.Rand, nil)
|
||||
if i < serverConfig.MaxAuthTries {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("client: got no error")
|
||||
} else if !strings.Contains(err.Error(), "too many authentication failures") {
|
||||
t.Fatalf("client: got unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
for _, err := range serverAuthErrors {
|
||||
if !errors.Is(err, ErrNoAuth) {
|
||||
t.Errorf("go error: %v; want: %v", err, ErrNoAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxAuthTriesFirstNoneAuthErrorIgnored(t *testing.T) {
|
||||
username := "testuser"
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 1,
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == username && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
clientConfig := &ClientConfig{
|
||||
User: username,
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if !errors.Is(serverAuthErrors[0], ErrNoAuth) {
|
||||
t.Errorf("go error: %v; want: %v", serverAuthErrors[0], ErrNoAuth)
|
||||
}
|
||||
if serverAuthErrors[1] != nil {
|
||||
t.Errorf("unexpected error: %v", serverAuthErrors[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerConnValidationErrors(t *testing.T) {
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
|
||||
|
||||
@@ -357,11 +357,6 @@ func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
||||
|
||||
numBytes := 4096
|
||||
|
||||
// Exercise sending data to the server
|
||||
if _, _, err := conn.Conn.SendRequest("drop-me", false, make([]byte, numBytes)); err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
|
||||
// Exercise receiving data from the server
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
@@ -377,6 +372,11 @@ func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
||||
if len(out) != numBytes {
|
||||
t.Fatalf("got %d bytes, want %d bytes", len(out), numBytes)
|
||||
}
|
||||
|
||||
// Exercise sending data to the server
|
||||
if _, _, err := conn.Conn.SendRequest("drop-me", false, make([]byte, numBytes)); err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var deprecatedCiphers = []string{
|
||||
|
||||
Reference in New Issue
Block a user