Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d042a396a6 | ||
|
|
b92bf9480d | ||
|
|
6f79b5a20c | ||
|
|
8d0d405eed | ||
|
|
b91329d961 | ||
|
|
7067223927 | ||
|
|
0d2316b267 |
+52
-58
@@ -33,6 +33,9 @@
|
|||||||
#define CONSTBASE R16
|
#define CONSTBASE R16
|
||||||
#define BLOCKS R17
|
#define BLOCKS R17
|
||||||
|
|
||||||
|
// for VPERMXOR
|
||||||
|
#define MASK R18
|
||||||
|
|
||||||
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
|
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
|
||||||
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
|
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
|
||||||
DATA consts<>+0x10(SB)/8, $0x0000000000000001
|
DATA consts<>+0x10(SB)/8, $0x0000000000000001
|
||||||
@@ -53,7 +56,11 @@ DATA consts<>+0x80(SB)/8, $0x6b2065746b206574
|
|||||||
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
|
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
|
||||||
DATA consts<>+0x90(SB)/8, $0x0000000100000000
|
DATA consts<>+0x90(SB)/8, $0x0000000100000000
|
||||||
DATA consts<>+0x98(SB)/8, $0x0000000300000002
|
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)
|
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||||
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||||
@@ -70,6 +77,9 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
|||||||
MOVD $48, R10
|
MOVD $48, R10
|
||||||
MOVD $64, R11
|
MOVD $64, R11
|
||||||
SRD $6, LEN, BLOCKS
|
SRD $6, LEN, BLOCKS
|
||||||
|
// for VPERMXOR
|
||||||
|
MOVD $consts<>+0xa0(SB), MASK
|
||||||
|
MOVD $16, R20
|
||||||
// V16
|
// V16
|
||||||
LXVW4X (CONSTBASE)(R0), VS48
|
LXVW4X (CONSTBASE)(R0), VS48
|
||||||
ADD $80,CONSTBASE
|
ADD $80,CONSTBASE
|
||||||
@@ -87,6 +97,10 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
|||||||
// V28
|
// V28
|
||||||
LXVW4X (CONSTBASE)(R11), VS60
|
LXVW4X (CONSTBASE)(R11), VS60
|
||||||
|
|
||||||
|
// Load mask constants for VPERMXOR
|
||||||
|
LXVW4X (MASK)(R0), V20
|
||||||
|
LXVW4X (MASK)(R20), V21
|
||||||
|
|
||||||
// splat slot from V19 -> V26
|
// splat slot from V19 -> V26
|
||||||
VSPLTW $0, V19, V26
|
VSPLTW $0, V19, V26
|
||||||
|
|
||||||
@@ -97,7 +111,7 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
|||||||
|
|
||||||
MOVD $10, R14
|
MOVD $10, R14
|
||||||
MOVD R14, CTR
|
MOVD R14, CTR
|
||||||
|
PCALIGN $16
|
||||||
loop_outer_vsx:
|
loop_outer_vsx:
|
||||||
// V0, V1, V2, V3
|
// V0, V1, V2, V3
|
||||||
LXVW4X (R0)(CONSTBASE), VS32
|
LXVW4X (R0)(CONSTBASE), VS32
|
||||||
@@ -128,22 +142,17 @@ loop_outer_vsx:
|
|||||||
VSPLTISW $12, V28
|
VSPLTISW $12, V28
|
||||||
VSPLTISW $8, V29
|
VSPLTISW $8, V29
|
||||||
VSPLTISW $7, V30
|
VSPLTISW $7, V30
|
||||||
|
PCALIGN $16
|
||||||
loop_vsx:
|
loop_vsx:
|
||||||
VADDUWM V0, V4, V0
|
VADDUWM V0, V4, V0
|
||||||
VADDUWM V1, V5, V1
|
VADDUWM V1, V5, V1
|
||||||
VADDUWM V2, V6, V2
|
VADDUWM V2, V6, V2
|
||||||
VADDUWM V3, V7, V3
|
VADDUWM V3, V7, V3
|
||||||
|
|
||||||
VXOR V12, V0, V12
|
VPERMXOR V12, V0, V21, V12
|
||||||
VXOR V13, V1, V13
|
VPERMXOR V13, V1, V21, V13
|
||||||
VXOR V14, V2, V14
|
VPERMXOR V14, V2, V21, V14
|
||||||
VXOR V15, V3, V15
|
VPERMXOR V15, V3, V21, V15
|
||||||
|
|
||||||
VRLW V12, V27, V12
|
|
||||||
VRLW V13, V27, V13
|
|
||||||
VRLW V14, V27, V14
|
|
||||||
VRLW V15, V27, V15
|
|
||||||
|
|
||||||
VADDUWM V8, V12, V8
|
VADDUWM V8, V12, V8
|
||||||
VADDUWM V9, V13, V9
|
VADDUWM V9, V13, V9
|
||||||
@@ -165,15 +174,10 @@ loop_vsx:
|
|||||||
VADDUWM V2, V6, V2
|
VADDUWM V2, V6, V2
|
||||||
VADDUWM V3, V7, V3
|
VADDUWM V3, V7, V3
|
||||||
|
|
||||||
VXOR V12, V0, V12
|
VPERMXOR V12, V0, V20, V12
|
||||||
VXOR V13, V1, V13
|
VPERMXOR V13, V1, V20, V13
|
||||||
VXOR V14, V2, V14
|
VPERMXOR V14, V2, V20, V14
|
||||||
VXOR V15, V3, V15
|
VPERMXOR V15, V3, V20, V15
|
||||||
|
|
||||||
VRLW V12, V29, V12
|
|
||||||
VRLW V13, V29, V13
|
|
||||||
VRLW V14, V29, V14
|
|
||||||
VRLW V15, V29, V15
|
|
||||||
|
|
||||||
VADDUWM V8, V12, V8
|
VADDUWM V8, V12, V8
|
||||||
VADDUWM V9, V13, V9
|
VADDUWM V9, V13, V9
|
||||||
@@ -195,15 +199,10 @@ loop_vsx:
|
|||||||
VADDUWM V2, V7, V2
|
VADDUWM V2, V7, V2
|
||||||
VADDUWM V3, V4, V3
|
VADDUWM V3, V4, V3
|
||||||
|
|
||||||
VXOR V15, V0, V15
|
VPERMXOR V15, V0, V21, V15
|
||||||
VXOR V12, V1, V12
|
VPERMXOR V12, V1, V21, V12
|
||||||
VXOR V13, V2, V13
|
VPERMXOR V13, V2, V21, V13
|
||||||
VXOR V14, V3, V14
|
VPERMXOR V14, V3, V21, V14
|
||||||
|
|
||||||
VRLW V15, V27, V15
|
|
||||||
VRLW V12, V27, V12
|
|
||||||
VRLW V13, V27, V13
|
|
||||||
VRLW V14, V27, V14
|
|
||||||
|
|
||||||
VADDUWM V10, V15, V10
|
VADDUWM V10, V15, V10
|
||||||
VADDUWM V11, V12, V11
|
VADDUWM V11, V12, V11
|
||||||
@@ -225,15 +224,10 @@ loop_vsx:
|
|||||||
VADDUWM V2, V7, V2
|
VADDUWM V2, V7, V2
|
||||||
VADDUWM V3, V4, V3
|
VADDUWM V3, V4, V3
|
||||||
|
|
||||||
VXOR V15, V0, V15
|
VPERMXOR V15, V0, V20, V15
|
||||||
VXOR V12, V1, V12
|
VPERMXOR V12, V1, V20, V12
|
||||||
VXOR V13, V2, V13
|
VPERMXOR V13, V2, V20, V13
|
||||||
VXOR V14, V3, V14
|
VPERMXOR V14, V3, V20, V14
|
||||||
|
|
||||||
VRLW V15, V29, V15
|
|
||||||
VRLW V12, V29, V12
|
|
||||||
VRLW V13, V29, V13
|
|
||||||
VRLW V14, V29, V14
|
|
||||||
|
|
||||||
VADDUWM V10, V15, V10
|
VADDUWM V10, V15, V10
|
||||||
VADDUWM V11, V12, V11
|
VADDUWM V11, V12, V11
|
||||||
@@ -249,48 +243,48 @@ loop_vsx:
|
|||||||
VRLW V6, V30, V6
|
VRLW V6, V30, V6
|
||||||
VRLW V7, V30, V7
|
VRLW V7, V30, V7
|
||||||
VRLW V4, V30, V4
|
VRLW V4, V30, V4
|
||||||
BC 16, LT, loop_vsx
|
BDNZ loop_vsx
|
||||||
|
|
||||||
VADDUWM V12, V26, V12
|
VADDUWM V12, V26, V12
|
||||||
|
|
||||||
WORD $0x13600F8C // VMRGEW V0, V1, V27
|
VMRGEW V0, V1, V27
|
||||||
WORD $0x13821F8C // VMRGEW V2, V3, V28
|
VMRGEW V2, V3, V28
|
||||||
|
|
||||||
WORD $0x10000E8C // VMRGOW V0, V1, V0
|
VMRGOW V0, V1, V0
|
||||||
WORD $0x10421E8C // VMRGOW V2, V3, V2
|
VMRGOW V2, V3, V2
|
||||||
|
|
||||||
WORD $0x13A42F8C // VMRGEW V4, V5, V29
|
VMRGEW V4, V5, V29
|
||||||
WORD $0x13C63F8C // VMRGEW V6, V7, V30
|
VMRGEW V6, V7, V30
|
||||||
|
|
||||||
XXPERMDI VS32, VS34, $0, VS33
|
XXPERMDI VS32, VS34, $0, VS33
|
||||||
XXPERMDI VS32, VS34, $3, VS35
|
XXPERMDI VS32, VS34, $3, VS35
|
||||||
XXPERMDI VS59, VS60, $0, VS32
|
XXPERMDI VS59, VS60, $0, VS32
|
||||||
XXPERMDI VS59, VS60, $3, VS34
|
XXPERMDI VS59, VS60, $3, VS34
|
||||||
|
|
||||||
WORD $0x10842E8C // VMRGOW V4, V5, V4
|
VMRGOW V4, V5, V4
|
||||||
WORD $0x10C63E8C // VMRGOW V6, V7, V6
|
VMRGOW V6, V7, V6
|
||||||
|
|
||||||
WORD $0x13684F8C // VMRGEW V8, V9, V27
|
VMRGEW V8, V9, V27
|
||||||
WORD $0x138A5F8C // VMRGEW V10, V11, V28
|
VMRGEW V10, V11, V28
|
||||||
|
|
||||||
XXPERMDI VS36, VS38, $0, VS37
|
XXPERMDI VS36, VS38, $0, VS37
|
||||||
XXPERMDI VS36, VS38, $3, VS39
|
XXPERMDI VS36, VS38, $3, VS39
|
||||||
XXPERMDI VS61, VS62, $0, VS36
|
XXPERMDI VS61, VS62, $0, VS36
|
||||||
XXPERMDI VS61, VS62, $3, VS38
|
XXPERMDI VS61, VS62, $3, VS38
|
||||||
|
|
||||||
WORD $0x11084E8C // VMRGOW V8, V9, V8
|
VMRGOW V8, V9, V8
|
||||||
WORD $0x114A5E8C // VMRGOW V10, V11, V10
|
VMRGOW V10, V11, V10
|
||||||
|
|
||||||
WORD $0x13AC6F8C // VMRGEW V12, V13, V29
|
VMRGEW V12, V13, V29
|
||||||
WORD $0x13CE7F8C // VMRGEW V14, V15, V30
|
VMRGEW V14, V15, V30
|
||||||
|
|
||||||
XXPERMDI VS40, VS42, $0, VS41
|
XXPERMDI VS40, VS42, $0, VS41
|
||||||
XXPERMDI VS40, VS42, $3, VS43
|
XXPERMDI VS40, VS42, $3, VS43
|
||||||
XXPERMDI VS59, VS60, $0, VS40
|
XXPERMDI VS59, VS60, $0, VS40
|
||||||
XXPERMDI VS59, VS60, $3, VS42
|
XXPERMDI VS59, VS60, $3, VS42
|
||||||
|
|
||||||
WORD $0x118C6E8C // VMRGOW V12, V13, V12
|
VMRGOW V12, V13, V12
|
||||||
WORD $0x11CE7E8C // VMRGOW V14, V15, V14
|
VMRGOW V14, V15, V14
|
||||||
|
|
||||||
VSPLTISW $4, V27
|
VSPLTISW $4, V27
|
||||||
VADDUWM V26, V27, V26
|
VADDUWM V26, V27, V26
|
||||||
@@ -431,7 +425,7 @@ tail_vsx:
|
|||||||
ADD $-1, R11, R12
|
ADD $-1, R11, R12
|
||||||
ADD $-1, INP
|
ADD $-1, INP
|
||||||
ADD $-1, OUT
|
ADD $-1, OUT
|
||||||
|
PCALIGN $16
|
||||||
looptail_vsx:
|
looptail_vsx:
|
||||||
// Copying the result to OUT
|
// Copying the result to OUT
|
||||||
// in bytes.
|
// in bytes.
|
||||||
@@ -439,7 +433,7 @@ looptail_vsx:
|
|||||||
MOVBZU 1(INP), TMP
|
MOVBZU 1(INP), TMP
|
||||||
XOR KEY, TMP, KEY
|
XOR KEY, TMP, KEY
|
||||||
MOVBU KEY, 1(OUT)
|
MOVBU KEY, 1(OUT)
|
||||||
BC 16, LT, looptail_vsx
|
BDNZ looptail_vsx
|
||||||
|
|
||||||
// Clear the stack values
|
// Clear the stack values
|
||||||
STXVW4X VS48, (R11)(R0)
|
STXVW4X VS48, (R11)(R0)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/net v0.21.0 // tagx:ignore
|
golang.org/x/net v0.21.0 // tagx:ignore
|
||||||
golang.org/x/sys v0.17.0
|
golang.org/x/sys v0.19.0
|
||||||
golang.org/x/term v0.17.0
|
golang.org/x/term v0.19.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.14.0 // indirect
|
require golang.org/x/text v0.14.0 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
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/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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
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/text v0.14.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
|
// grace periods to clean up: one for the delay between the first
|
||||||
// termination signal being sent (via the Cancel callback when the Context
|
// termination signal being sent (via the Cancel callback when the Context
|
||||||
// expires) and the process being forcibly terminated (via the WaitDelay
|
// expires) and the process being forcibly terminated (via the WaitDelay
|
||||||
// field), and a second one for the delay becween the process being
|
// field), and a second one for the delay between the process being
|
||||||
// terminated and and the test logging its output for debugging.
|
// terminated and the test logging its output for debugging.
|
||||||
//
|
//
|
||||||
// (We want to ensure that the test process itself has enough time to
|
// (We want to ensure that the test process itself has enough time to
|
||||||
// log the output before it is also terminated.)
|
// 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.
|
// 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,
|
// where compatibility with legacy systems, not security, is the goal. Instead,
|
||||||
// use a secure hash like SHA-256 (from crypto/sha256).
|
// use a secure hash like SHA-256 (from crypto/sha256).
|
||||||
package md4 // import "golang.org/x/crypto/md4"
|
package md4 // import "golang.org/x/crypto/md4"
|
||||||
|
|||||||
+9
-9
@@ -367,21 +367,21 @@ func TestCertTypes(t *testing.T) {
|
|||||||
|
|
||||||
func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
sigAlgo string
|
sigAlgo string
|
||||||
algoritms []string
|
algorithms []string
|
||||||
}
|
}
|
||||||
cases := []testcase{
|
cases := []testcase{
|
||||||
{
|
{
|
||||||
sigAlgo: KeyAlgoRSA,
|
sigAlgo: KeyAlgoRSA,
|
||||||
algoritms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
|
algorithms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sigAlgo: KeyAlgoRSASHA256,
|
sigAlgo: KeyAlgoRSASHA256,
|
||||||
algoritms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
|
algorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sigAlgo: KeyAlgoRSASHA512,
|
sigAlgo: KeyAlgoRSASHA512,
|
||||||
algoritms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
algorithms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +393,7 @@ func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
|||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.sigAlgo, func(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("NewSignerWithAlgorithms error: %v", err)
|
t.Fatalf("NewSignerWithAlgorithms error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -384,7 +384,7 @@ func ExampleCertificate_SignCert() {
|
|||||||
}
|
}
|
||||||
mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256})
|
mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("unable to create signer with algoritms: ", err)
|
log.Fatal("unable to create signer with algorithms: ", err)
|
||||||
}
|
}
|
||||||
certificate := ssh.Certificate{
|
certificate := ssh.Certificate{
|
||||||
Key: publicKey,
|
Key: publicKey,
|
||||||
|
|||||||
+120
-50
@@ -426,6 +426,35 @@ func (l ServerAuthError) Error() string {
|
|||||||
return "[" + strings.Join(errs, ", ") + "]"
|
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
|
// ErrNoAuth is the error value returned if no
|
||||||
// authentication method has been passed yet. This happens as a normal
|
// authentication method has been passed yet. This happens as a normal
|
||||||
// part of the authentication loop, since the client first tries
|
// 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
|
var perms *Permissions
|
||||||
|
|
||||||
authFailures := 0
|
authFailures := 0
|
||||||
|
noneAuthCount := 0
|
||||||
var authErrs []error
|
var authErrs []error
|
||||||
var displayedBanner bool
|
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:
|
userAuthLoop:
|
||||||
for {
|
for {
|
||||||
@@ -471,6 +510,11 @@ userAuthLoop:
|
|||||||
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
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
|
s.user = userAuthReq.User
|
||||||
|
|
||||||
if !displayedBanner && config.BannerCallback != nil {
|
if !displayedBanner && config.BannerCallback != nil {
|
||||||
@@ -491,20 +535,18 @@ userAuthLoop:
|
|||||||
|
|
||||||
switch userAuthReq.Method {
|
switch userAuthReq.Method {
|
||||||
case "none":
|
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 {
|
if config.NoClientAuthCallback != nil {
|
||||||
perms, authErr = config.NoClientAuthCallback(s)
|
perms, authErr = config.NoClientAuthCallback(s)
|
||||||
} else {
|
} else {
|
||||||
authErr = nil
|
authErr = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow initial attempt of 'none' without penalty
|
|
||||||
if authFailures == 0 {
|
|
||||||
authFailures--
|
|
||||||
}
|
|
||||||
case "password":
|
case "password":
|
||||||
if config.PasswordCallback == nil {
|
if authConfig.PasswordCallback == nil {
|
||||||
authErr = errors.New("ssh: password auth not configured")
|
authErr = errors.New("ssh: password auth not configured")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -518,17 +560,17 @@ userAuthLoop:
|
|||||||
return nil, parseError(msgUserAuthRequest)
|
return nil, parseError(msgUserAuthRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
perms, authErr = config.PasswordCallback(s, password)
|
perms, authErr = authConfig.PasswordCallback(s, password)
|
||||||
case "keyboard-interactive":
|
case "keyboard-interactive":
|
||||||
if config.KeyboardInteractiveCallback == nil {
|
if authConfig.KeyboardInteractiveCallback == nil {
|
||||||
authErr = errors.New("ssh: keyboard-interactive auth not configured")
|
authErr = errors.New("ssh: keyboard-interactive auth not configured")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
prompter := &sshClientKeyboardInteractive{s}
|
prompter := &sshClientKeyboardInteractive{s}
|
||||||
perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
|
perms, authErr = authConfig.KeyboardInteractiveCallback(s, prompter.Challenge)
|
||||||
case "publickey":
|
case "publickey":
|
||||||
if config.PublicKeyCallback == nil {
|
if authConfig.PublicKeyCallback == nil {
|
||||||
authErr = errors.New("ssh: publickey auth not configured")
|
authErr = errors.New("ssh: publickey auth not configured")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -562,11 +604,18 @@ userAuthLoop:
|
|||||||
if !ok {
|
if !ok {
|
||||||
candidate.user = s.user
|
candidate.user = s.user
|
||||||
candidate.pubKeyData = pubKeyData
|
candidate.pubKeyData = pubKeyData
|
||||||
candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
|
candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey)
|
||||||
if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||||
candidate.result = checkSourceAddress(
|
|
||||||
|
if (candidate.result == nil || isPartialSuccessError) &&
|
||||||
|
candidate.perms != nil &&
|
||||||
|
candidate.perms.CriticalOptions != nil &&
|
||||||
|
candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
||||||
|
if err := checkSourceAddress(
|
||||||
s.RemoteAddr(),
|
s.RemoteAddr(),
|
||||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption])
|
candidate.perms.CriticalOptions[sourceAddressCriticalOption]); err != nil {
|
||||||
|
candidate.result = err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cache.add(candidate)
|
cache.add(candidate)
|
||||||
}
|
}
|
||||||
@@ -578,8 +627,8 @@ userAuthLoop:
|
|||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
return nil, parseError(msgUserAuthRequest)
|
return nil, parseError(msgUserAuthRequest)
|
||||||
}
|
}
|
||||||
|
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||||
if candidate.result == nil {
|
if candidate.result == nil || isPartialSuccessError {
|
||||||
okMsg := userAuthPubKeyOkMsg{
|
okMsg := userAuthPubKeyOkMsg{
|
||||||
Algo: algo,
|
Algo: algo,
|
||||||
PubKey: pubKeyData,
|
PubKey: pubKeyData,
|
||||||
@@ -629,11 +678,11 @@ userAuthLoop:
|
|||||||
perms = candidate.perms
|
perms = candidate.perms
|
||||||
}
|
}
|
||||||
case "gssapi-with-mic":
|
case "gssapi-with-mic":
|
||||||
if config.GSSAPIWithMICConfig == nil {
|
if authConfig.GSSAPIWithMICConfig == nil {
|
||||||
authErr = errors.New("ssh: gssapi-with-mic auth not configured")
|
authErr = errors.New("ssh: gssapi-with-mic auth not configured")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
gssapiConfig := config.GSSAPIWithMICConfig
|
gssapiConfig := authConfig.GSSAPIWithMICConfig
|
||||||
userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload)
|
userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseError(msgUserAuthRequest)
|
return nil, parseError(msgUserAuthRequest)
|
||||||
@@ -689,49 +738,70 @@ userAuthLoop:
|
|||||||
break userAuthLoop
|
break userAuthLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
authFailures++
|
var failureMsg userAuthFailureMsg
|
||||||
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
|
|
||||||
// If we have hit the max attempts, don't bother sending the
|
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
||||||
// final SSH_MSG_USERAUTH_FAILURE message, since there are
|
// After a partial success error we don't allow changing the user
|
||||||
// no more authentication methods which can be attempted,
|
// name and execute the NoClientAuthCallback.
|
||||||
// and this message may cause the client to re-attempt
|
partialSuccessReturned = true
|
||||||
// authentication while we send the disconnect message.
|
|
||||||
// Continue, and trigger the disconnect at the start of
|
// In case a partial success is returned, the server may send
|
||||||
// the loop.
|
// a new set of authentication methods.
|
||||||
//
|
authConfig = partialSuccess.Next
|
||||||
// The SSH specification is somewhat confusing about this,
|
|
||||||
// RFC 4252 Section 5.1 requires each authentication failure
|
// Reset pubkey cache, as the new PublicKeyCallback might
|
||||||
// be responded to with a respective SSH_MSG_USERAUTH_FAILURE
|
// accept a different set of public keys.
|
||||||
// message, but Section 4 says the server should disconnect
|
cache = pubKeyCache{}
|
||||||
// after some number of attempts, but it isn't explicit which
|
|
||||||
// message should take precedence (i.e. should there be a failure
|
// Send back a partial success message to the user.
|
||||||
// message than a disconnect message, or if we are going to
|
failureMsg.PartialSuccess = true
|
||||||
// disconnect, should we only send that message.)
|
} else {
|
||||||
//
|
// Allow initial attempt of 'none' without penalty.
|
||||||
// Either way, OpenSSH disconnects immediately after the last
|
if authFailures > 0 || userAuthReq.Method != "none" || noneAuthCount != 1 {
|
||||||
// failed authnetication attempt, and given they are typically
|
authFailures++
|
||||||
// considered the golden implementation it seems reasonable
|
}
|
||||||
// to match that behavior.
|
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
|
||||||
continue
|
// 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 authConfig.PasswordCallback != nil {
|
||||||
if config.PasswordCallback != nil {
|
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||||
}
|
}
|
||||||
if config.PublicKeyCallback != nil {
|
if authConfig.PublicKeyCallback != nil {
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
||||||
}
|
}
|
||||||
if config.KeyboardInteractiveCallback != nil {
|
if authConfig.KeyboardInteractiveCallback != nil {
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
||||||
}
|
}
|
||||||
if config.GSSAPIWithMICConfig != nil && config.GSSAPIWithMICConfig.Server != nil &&
|
if authConfig.GSSAPIWithMICConfig != nil && authConfig.GSSAPIWithMICConfig.Server != nil &&
|
||||||
config.GSSAPIWithMICConfig.AllowLogin != nil {
|
authConfig.GSSAPIWithMICConfig.AllowLogin != nil {
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic")
|
failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(failureMsg.Methods) == 0 {
|
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 {
|
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
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func TestNewServerConnValidationErrors(t *testing.T) {
|
||||||
serverConf := &ServerConfig{
|
serverConf := &ServerConfig{
|
||||||
PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
|
PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
|
||||||
|
|||||||
@@ -357,11 +357,6 @@ func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
|||||||
|
|
||||||
numBytes := 4096
|
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
|
// Exercise receiving data from the server
|
||||||
session, err := conn.NewSession()
|
session, err := conn.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -377,6 +372,11 @@ func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
|||||||
if len(out) != numBytes {
|
if len(out) != numBytes {
|
||||||
t.Fatalf("got %d bytes, want %d bytes", 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{
|
var deprecatedCiphers = []string{
|
||||||
|
|||||||
Reference in New Issue
Block a user