Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332fd656f4 | ||
|
|
0b431c7de3 | ||
|
|
349231f7e4 | ||
|
|
44c9b0ff9e | ||
|
|
67b13616a5 | ||
|
|
477a5b4c32 | ||
|
|
59b5a86796 | ||
|
|
10f366e7a2 | ||
|
|
905d78a692 | ||
|
|
ebb717d630 | ||
|
|
0da2a6a1bb | ||
|
|
5defcc193a | ||
|
|
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.21.0
|
||||||
golang.org/x/term v0.17.0
|
golang.org/x/term v0.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.14.0 // indirect
|
require golang.org/x/text v0.16.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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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) {
|
func TestExternallyRevocableKey(t *testing.T) {
|
||||||
kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2023 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !noopt
|
||||||
|
|
||||||
|
package sha3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sink byte
|
||||||
|
|
||||||
|
func TestAllocations(t *testing.T) {
|
||||||
|
want := 0.0
|
||||||
|
|
||||||
|
if runtime.GOARCH == "s390x" {
|
||||||
|
// On s390x the returned hash.Hash is conditional so it escapes.
|
||||||
|
want = 3.0
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("New", func(t *testing.T) {
|
||||||
|
if allocs := testing.AllocsPerRun(10, func() {
|
||||||
|
h := sha3.New256()
|
||||||
|
b := []byte("ABC")
|
||||||
|
h.Write(b)
|
||||||
|
out := make([]byte, 0, 32)
|
||||||
|
out = h.Sum(out)
|
||||||
|
sink ^= out[0]
|
||||||
|
}); allocs > want {
|
||||||
|
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("NewShake", func(t *testing.T) {
|
||||||
|
if allocs := testing.AllocsPerRun(10, func() {
|
||||||
|
h := sha3.NewShake128()
|
||||||
|
b := []byte("ABC")
|
||||||
|
h.Write(b)
|
||||||
|
out := make([]byte, 0, 32)
|
||||||
|
out = h.Sum(out)
|
||||||
|
sink ^= out[0]
|
||||||
|
h.Read(out)
|
||||||
|
sink ^= out[0]
|
||||||
|
}); allocs > want {
|
||||||
|
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Sum", func(t *testing.T) {
|
||||||
|
if allocs := testing.AllocsPerRun(10, func() {
|
||||||
|
b := []byte("ABC")
|
||||||
|
out := sha3.Sum256(b)
|
||||||
|
sink ^= out[0]
|
||||||
|
}); allocs > want {
|
||||||
|
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+19
-15
@@ -16,39 +16,43 @@ import (
|
|||||||
// Its generic security strength is 224 bits against preimage attacks,
|
// Its generic security strength is 224 bits against preimage attacks,
|
||||||
// and 112 bits against collision attacks.
|
// and 112 bits against collision attacks.
|
||||||
func New224() hash.Hash {
|
func New224() hash.Hash {
|
||||||
if h := new224Asm(); h != nil {
|
return new224()
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New256 creates a new SHA3-256 hash.
|
// New256 creates a new SHA3-256 hash.
|
||||||
// Its generic security strength is 256 bits against preimage attacks,
|
// Its generic security strength is 256 bits against preimage attacks,
|
||||||
// and 128 bits against collision attacks.
|
// and 128 bits against collision attacks.
|
||||||
func New256() hash.Hash {
|
func New256() hash.Hash {
|
||||||
if h := new256Asm(); h != nil {
|
return new256()
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New384 creates a new SHA3-384 hash.
|
// New384 creates a new SHA3-384 hash.
|
||||||
// Its generic security strength is 384 bits against preimage attacks,
|
// Its generic security strength is 384 bits against preimage attacks,
|
||||||
// and 192 bits against collision attacks.
|
// and 192 bits against collision attacks.
|
||||||
func New384() hash.Hash {
|
func New384() hash.Hash {
|
||||||
if h := new384Asm(); h != nil {
|
return new384()
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New512 creates a new SHA3-512 hash.
|
// New512 creates a new SHA3-512 hash.
|
||||||
// Its generic security strength is 512 bits against preimage attacks,
|
// Its generic security strength is 512 bits against preimage attacks,
|
||||||
// and 256 bits against collision attacks.
|
// and 256 bits against collision attacks.
|
||||||
func New512() hash.Hash {
|
func New512() hash.Hash {
|
||||||
if h := new512Asm(); h != nil {
|
return new512()
|
||||||
return h
|
}
|
||||||
}
|
|
||||||
|
func new224Generic() *state {
|
||||||
|
return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
|
||||||
|
}
|
||||||
|
|
||||||
|
func new256Generic() *state {
|
||||||
|
return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
|
||||||
|
}
|
||||||
|
|
||||||
|
func new384Generic() *state {
|
||||||
|
return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
|
||||||
|
}
|
||||||
|
|
||||||
|
func new512Generic() *state {
|
||||||
return &state{rate: 72, outputLen: 64, dsbyte: 0x06}
|
return &state{rate: 72, outputLen: 64, dsbyte: 0x06}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !gc || purego || !s390x
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// new224Asm returns an assembly implementation of SHA3-224 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func new224Asm() hash.Hash { return nil }
|
|
||||||
|
|
||||||
// new256Asm returns an assembly implementation of SHA3-256 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func new256Asm() hash.Hash { return nil }
|
|
||||||
|
|
||||||
// new384Asm returns an assembly implementation of SHA3-384 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func new384Asm() hash.Hash { return nil }
|
|
||||||
|
|
||||||
// new512Asm returns an assembly implementation of SHA3-512 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func new512Asm() hash.Hash { return nil }
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2023 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !gc || purego || !s390x
|
||||||
|
|
||||||
|
package sha3
|
||||||
|
|
||||||
|
func new224() *state {
|
||||||
|
return new224Generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func new256() *state {
|
||||||
|
return new256Generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func new384() *state {
|
||||||
|
return new384Generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func new512() *state {
|
||||||
|
return new512Generic()
|
||||||
|
}
|
||||||
+25
-37
@@ -23,7 +23,6 @@ const (
|
|||||||
type state struct {
|
type state struct {
|
||||||
// Generic sponge components.
|
// Generic sponge components.
|
||||||
a [25]uint64 // main state of the hash
|
a [25]uint64 // main state of the hash
|
||||||
buf []byte // points into storage
|
|
||||||
rate int // the number of bytes of state to use
|
rate int // the number of bytes of state to use
|
||||||
|
|
||||||
// dsbyte contains the "domain separation" bits and the first bit of
|
// dsbyte contains the "domain separation" bits and the first bit of
|
||||||
@@ -40,7 +39,8 @@ type state struct {
|
|||||||
// Extendable-Output Functions (May 2014)"
|
// Extendable-Output Functions (May 2014)"
|
||||||
dsbyte byte
|
dsbyte byte
|
||||||
|
|
||||||
storage storageBuf
|
i, n int // storage[i:n] is the buffer, i is only used while squeezing
|
||||||
|
storage [maxRate]byte
|
||||||
|
|
||||||
// Specific to SHA-3 and SHAKE.
|
// Specific to SHA-3 and SHAKE.
|
||||||
outputLen int // the default output size in bytes
|
outputLen int // the default output size in bytes
|
||||||
@@ -54,24 +54,18 @@ func (d *state) BlockSize() int { return d.rate }
|
|||||||
func (d *state) Size() int { return d.outputLen }
|
func (d *state) Size() int { return d.outputLen }
|
||||||
|
|
||||||
// Reset clears the internal state by zeroing the sponge state and
|
// Reset clears the internal state by zeroing the sponge state and
|
||||||
// the byte buffer, and setting Sponge.state to absorbing.
|
// the buffer indexes, and setting Sponge.state to absorbing.
|
||||||
func (d *state) Reset() {
|
func (d *state) Reset() {
|
||||||
// Zero the permutation's state.
|
// Zero the permutation's state.
|
||||||
for i := range d.a {
|
for i := range d.a {
|
||||||
d.a[i] = 0
|
d.a[i] = 0
|
||||||
}
|
}
|
||||||
d.state = spongeAbsorbing
|
d.state = spongeAbsorbing
|
||||||
d.buf = d.storage.asBytes()[:0]
|
d.i, d.n = 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *state) clone() *state {
|
func (d *state) clone() *state {
|
||||||
ret := *d
|
ret := *d
|
||||||
if ret.state == spongeAbsorbing {
|
|
||||||
ret.buf = ret.storage.asBytes()[:len(ret.buf)]
|
|
||||||
} else {
|
|
||||||
ret.buf = ret.storage.asBytes()[d.rate-cap(d.buf) : d.rate]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,43 +76,40 @@ func (d *state) permute() {
|
|||||||
case spongeAbsorbing:
|
case spongeAbsorbing:
|
||||||
// If we're absorbing, we need to xor the input into the state
|
// If we're absorbing, we need to xor the input into the state
|
||||||
// before applying the permutation.
|
// before applying the permutation.
|
||||||
xorIn(d, d.buf)
|
xorIn(d, d.storage[:d.rate])
|
||||||
d.buf = d.storage.asBytes()[:0]
|
d.n = 0
|
||||||
keccakF1600(&d.a)
|
keccakF1600(&d.a)
|
||||||
case spongeSqueezing:
|
case spongeSqueezing:
|
||||||
// If we're squeezing, we need to apply the permutation before
|
// If we're squeezing, we need to apply the permutation before
|
||||||
// copying more output.
|
// copying more output.
|
||||||
keccakF1600(&d.a)
|
keccakF1600(&d.a)
|
||||||
d.buf = d.storage.asBytes()[:d.rate]
|
d.i = 0
|
||||||
copyOut(d, d.buf)
|
copyOut(d, d.storage[:d.rate])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pads appends the domain separation bits in dsbyte, applies
|
// pads appends the domain separation bits in dsbyte, applies
|
||||||
// the multi-bitrate 10..1 padding rule, and permutes the state.
|
// the multi-bitrate 10..1 padding rule, and permutes the state.
|
||||||
func (d *state) padAndPermute(dsbyte byte) {
|
func (d *state) padAndPermute() {
|
||||||
if d.buf == nil {
|
|
||||||
d.buf = d.storage.asBytes()[:0]
|
|
||||||
}
|
|
||||||
// Pad with this instance's domain-separator bits. We know that there's
|
// Pad with this instance's domain-separator bits. We know that there's
|
||||||
// at least one byte of space in d.buf because, if it were full,
|
// at least one byte of space in d.buf because, if it were full,
|
||||||
// permute would have been called to empty it. dsbyte also contains the
|
// permute would have been called to empty it. dsbyte also contains the
|
||||||
// first one bit for the padding. See the comment in the state struct.
|
// first one bit for the padding. See the comment in the state struct.
|
||||||
d.buf = append(d.buf, dsbyte)
|
d.storage[d.n] = d.dsbyte
|
||||||
zerosStart := len(d.buf)
|
d.n++
|
||||||
d.buf = d.storage.asBytes()[:d.rate]
|
for d.n < d.rate {
|
||||||
for i := zerosStart; i < d.rate; i++ {
|
d.storage[d.n] = 0
|
||||||
d.buf[i] = 0
|
d.n++
|
||||||
}
|
}
|
||||||
// This adds the final one bit for the padding. Because of the way that
|
// This adds the final one bit for the padding. Because of the way that
|
||||||
// bits are numbered from the LSB upwards, the final bit is the MSB of
|
// bits are numbered from the LSB upwards, the final bit is the MSB of
|
||||||
// the last byte.
|
// the last byte.
|
||||||
d.buf[d.rate-1] ^= 0x80
|
d.storage[d.rate-1] ^= 0x80
|
||||||
// Apply the permutation
|
// Apply the permutation
|
||||||
d.permute()
|
d.permute()
|
||||||
d.state = spongeSqueezing
|
d.state = spongeSqueezing
|
||||||
d.buf = d.storage.asBytes()[:d.rate]
|
d.n = d.rate
|
||||||
copyOut(d, d.buf)
|
copyOut(d, d.storage[:d.rate])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write absorbs more data into the hash's state. It panics if any
|
// Write absorbs more data into the hash's state. It panics if any
|
||||||
@@ -127,28 +118,25 @@ func (d *state) Write(p []byte) (written int, err error) {
|
|||||||
if d.state != spongeAbsorbing {
|
if d.state != spongeAbsorbing {
|
||||||
panic("sha3: Write after Read")
|
panic("sha3: Write after Read")
|
||||||
}
|
}
|
||||||
if d.buf == nil {
|
|
||||||
d.buf = d.storage.asBytes()[:0]
|
|
||||||
}
|
|
||||||
written = len(p)
|
written = len(p)
|
||||||
|
|
||||||
for len(p) > 0 {
|
for len(p) > 0 {
|
||||||
if len(d.buf) == 0 && len(p) >= d.rate {
|
if d.n == 0 && len(p) >= d.rate {
|
||||||
// The fast path; absorb a full "rate" bytes of input and apply the permutation.
|
// The fast path; absorb a full "rate" bytes of input and apply the permutation.
|
||||||
xorIn(d, p[:d.rate])
|
xorIn(d, p[:d.rate])
|
||||||
p = p[d.rate:]
|
p = p[d.rate:]
|
||||||
keccakF1600(&d.a)
|
keccakF1600(&d.a)
|
||||||
} else {
|
} else {
|
||||||
// The slow path; buffer the input until we can fill the sponge, and then xor it in.
|
// The slow path; buffer the input until we can fill the sponge, and then xor it in.
|
||||||
todo := d.rate - len(d.buf)
|
todo := d.rate - d.n
|
||||||
if todo > len(p) {
|
if todo > len(p) {
|
||||||
todo = len(p)
|
todo = len(p)
|
||||||
}
|
}
|
||||||
d.buf = append(d.buf, p[:todo]...)
|
d.n += copy(d.storage[d.n:], p[:todo])
|
||||||
p = p[todo:]
|
p = p[todo:]
|
||||||
|
|
||||||
// If the sponge is full, apply the permutation.
|
// If the sponge is full, apply the permutation.
|
||||||
if len(d.buf) == d.rate {
|
if d.n == d.rate {
|
||||||
d.permute()
|
d.permute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,19 +149,19 @@ func (d *state) Write(p []byte) (written int, err error) {
|
|||||||
func (d *state) Read(out []byte) (n int, err error) {
|
func (d *state) Read(out []byte) (n int, err error) {
|
||||||
// If we're still absorbing, pad and apply the permutation.
|
// If we're still absorbing, pad and apply the permutation.
|
||||||
if d.state == spongeAbsorbing {
|
if d.state == spongeAbsorbing {
|
||||||
d.padAndPermute(d.dsbyte)
|
d.padAndPermute()
|
||||||
}
|
}
|
||||||
|
|
||||||
n = len(out)
|
n = len(out)
|
||||||
|
|
||||||
// Now, do the squeezing.
|
// Now, do the squeezing.
|
||||||
for len(out) > 0 {
|
for len(out) > 0 {
|
||||||
n := copy(out, d.buf)
|
n := copy(out, d.storage[d.i:d.n])
|
||||||
d.buf = d.buf[n:]
|
d.i += n
|
||||||
out = out[n:]
|
out = out[n:]
|
||||||
|
|
||||||
// Apply the permutation if we've squeezed the sponge dry.
|
// Apply the permutation if we've squeezed the sponge dry.
|
||||||
if len(d.buf) == 0 {
|
if d.i == d.rate {
|
||||||
d.permute()
|
d.permute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-26
@@ -143,6 +143,12 @@ func (s *asmState) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
// Read squeezes an arbitrary number of bytes from the sponge.
|
// Read squeezes an arbitrary number of bytes from the sponge.
|
||||||
func (s *asmState) Read(out []byte) (n int, err error) {
|
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)
|
n = len(out)
|
||||||
|
|
||||||
// need to pad if we were absorbing
|
// 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
|
// Hash the buffer. Note that we don't clear it because we
|
||||||
// aren't updating the state.
|
// aren't updating the state.
|
||||||
klmd(s.function, &a, nil, s.buf)
|
switch s.function {
|
||||||
return append(b, a[:s.outputLen]...)
|
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.
|
// Reset resets the Hash to its initial state.
|
||||||
@@ -233,56 +248,56 @@ func (s *asmState) Clone() ShakeHash {
|
|||||||
return s.clone()
|
return s.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// new224Asm returns an assembly implementation of SHA3-224 if available,
|
// new224 returns an assembly implementation of SHA3-224 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func new224Asm() hash.Hash {
|
func new224() hash.Hash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(sha3_224)
|
return newAsmState(sha3_224)
|
||||||
}
|
}
|
||||||
return nil
|
return new224Generic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// new256Asm returns an assembly implementation of SHA3-256 if available,
|
// new256 returns an assembly implementation of SHA3-256 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func new256Asm() hash.Hash {
|
func new256() hash.Hash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(sha3_256)
|
return newAsmState(sha3_256)
|
||||||
}
|
}
|
||||||
return nil
|
return new256Generic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// new384Asm returns an assembly implementation of SHA3-384 if available,
|
// new384 returns an assembly implementation of SHA3-384 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func new384Asm() hash.Hash {
|
func new384() hash.Hash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(sha3_384)
|
return newAsmState(sha3_384)
|
||||||
}
|
}
|
||||||
return nil
|
return new384Generic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// new512Asm returns an assembly implementation of SHA3-512 if available,
|
// new512 returns an assembly implementation of SHA3-512 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func new512Asm() hash.Hash {
|
func new512() hash.Hash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(sha3_512)
|
return newAsmState(sha3_512)
|
||||||
}
|
}
|
||||||
return nil
|
return new512Generic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
|
// newShake128 returns an assembly implementation of SHAKE-128 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func newShake128Asm() ShakeHash {
|
func newShake128() ShakeHash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(shake_128)
|
return newAsmState(shake_128)
|
||||||
}
|
}
|
||||||
return nil
|
return newShake128Generic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
|
// newShake256 returns an assembly implementation of SHAKE-256 if available,
|
||||||
// otherwise it returns nil.
|
// otherwise it returns a generic implementation.
|
||||||
func newShake256Asm() ShakeHash {
|
func newShake256() ShakeHash {
|
||||||
if cpu.S390X.HasSHA3 {
|
if cpu.S390X.HasSHA3 {
|
||||||
return newAsmState(shake_256)
|
return newAsmState(shake_256)
|
||||||
}
|
}
|
||||||
return nil
|
return newShake256Generic()
|
||||||
}
|
}
|
||||||
|
|||||||
+167
-160
@@ -76,86 +76,73 @@ type KeccakKats struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUnalignedAndGeneric(t *testing.T, testf func(impl string)) {
|
|
||||||
xorInOrig, copyOutOrig := xorIn, copyOut
|
|
||||||
xorIn, copyOut = xorInGeneric, copyOutGeneric
|
|
||||||
testf("generic")
|
|
||||||
if xorImplementationUnaligned != "generic" {
|
|
||||||
xorIn, copyOut = xorInUnaligned, copyOutUnaligned
|
|
||||||
testf("unaligned")
|
|
||||||
}
|
|
||||||
xorIn, copyOut = xorInOrig, copyOutOrig
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestKeccakKats tests the SHA-3 and Shake implementations against all the
|
// TestKeccakKats tests the SHA-3 and Shake implementations against all the
|
||||||
// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
|
// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
|
||||||
// (The testvectors are stored in keccakKats.json.deflate due to their length.)
|
// (The testvectors are stored in keccakKats.json.deflate due to their length.)
|
||||||
func TestKeccakKats(t *testing.T) {
|
func TestKeccakKats(t *testing.T) {
|
||||||
testUnalignedAndGeneric(t, func(impl string) {
|
// Read the KATs.
|
||||||
// Read the KATs.
|
deflated, err := os.Open(katFilename)
|
||||||
deflated, err := os.Open(katFilename)
|
if err != nil {
|
||||||
if err != nil {
|
t.Errorf("error opening %s: %s", katFilename, err)
|
||||||
t.Errorf("error opening %s: %s", katFilename, err)
|
}
|
||||||
}
|
file := flate.NewReader(deflated)
|
||||||
file := flate.NewReader(deflated)
|
dec := json.NewDecoder(file)
|
||||||
dec := json.NewDecoder(file)
|
var katSet KeccakKats
|
||||||
var katSet KeccakKats
|
err = dec.Decode(&katSet)
|
||||||
err = dec.Decode(&katSet)
|
if err != nil {
|
||||||
if err != nil {
|
t.Errorf("error decoding KATs: %s", err)
|
||||||
t.Errorf("error decoding KATs: %s", err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for algo, function := range testDigests {
|
for algo, function := range testDigests {
|
||||||
d := function()
|
d := function()
|
||||||
for _, kat := range katSet.Kats[algo] {
|
for _, kat := range katSet.Kats[algo] {
|
||||||
d.Reset()
|
d.Reset()
|
||||||
in, err := hex.DecodeString(kat.Message)
|
in, err := hex.DecodeString(kat.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error decoding KAT: %s", err)
|
t.Errorf("error decoding KAT: %s", err)
|
||||||
}
|
|
||||||
d.Write(in[:kat.Length/8])
|
|
||||||
got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
|
|
||||||
if got != kat.Digest {
|
|
||||||
t.Errorf("function=%s, implementation=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
|
|
||||||
algo, impl, kat.Length, kat.Message, got, kat.Digest)
|
|
||||||
t.Logf("wanted %+v", kat)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
d.Write(in[:kat.Length/8])
|
||||||
|
got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
|
||||||
for algo, v := range testShakes {
|
if got != kat.Digest {
|
||||||
for _, kat := range katSet.Kats[algo] {
|
t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
|
||||||
N, err := hex.DecodeString(kat.N)
|
algo, kat.Length, kat.Message, got, kat.Digest)
|
||||||
if err != nil {
|
t.Logf("wanted %+v", kat)
|
||||||
t.Errorf("error decoding KAT: %s", err)
|
t.FailNow()
|
||||||
}
|
|
||||||
|
|
||||||
S, err := hex.DecodeString(kat.S)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error decoding KAT: %s", err)
|
|
||||||
}
|
|
||||||
d := v.constructor(N, S)
|
|
||||||
in, err := hex.DecodeString(kat.Message)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error decoding KAT: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Write(in[:kat.Length/8])
|
|
||||||
out := make([]byte, len(kat.Digest)/2)
|
|
||||||
d.Read(out)
|
|
||||||
got := strings.ToUpper(hex.EncodeToString(out))
|
|
||||||
if got != kat.Digest {
|
|
||||||
t.Errorf("function=%s, implementation=%s, length=%d N:%s\n S:%s\nmessage:\n %s \ngot:\n %s\nwanted:\n %s",
|
|
||||||
algo, impl, kat.Length, kat.N, kat.S, kat.Message, got, kat.Digest)
|
|
||||||
t.Logf("wanted %+v", kat)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
for algo, v := range testShakes {
|
||||||
|
for _, kat := range katSet.Kats[algo] {
|
||||||
|
N, err := hex.DecodeString(kat.N)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error decoding KAT: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
S, err := hex.DecodeString(kat.S)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error decoding KAT: %s", err)
|
||||||
|
}
|
||||||
|
d := v.constructor(N, S)
|
||||||
|
in, err := hex.DecodeString(kat.Message)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error decoding KAT: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Write(in[:kat.Length/8])
|
||||||
|
out := make([]byte, len(kat.Digest)/2)
|
||||||
|
d.Read(out)
|
||||||
|
got := strings.ToUpper(hex.EncodeToString(out))
|
||||||
|
if got != kat.Digest {
|
||||||
|
t.Errorf("function=%s, length=%d N:%s\n S:%s\nmessage:\n %s \ngot:\n %s\nwanted:\n %s",
|
||||||
|
algo, kat.Length, kat.N, kat.S, kat.Message, got, kat.Digest)
|
||||||
|
t.Logf("wanted %+v", kat)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeccak does a basic test of the non-standardized Keccak hash functions.
|
// TestKeccak does a basic test of the non-standardized Keccak hash functions.
|
||||||
@@ -188,122 +175,142 @@ 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
|
// TestUnalignedWrite tests that writing data in an arbitrary pattern with
|
||||||
// small input buffers.
|
// small input buffers.
|
||||||
func TestUnalignedWrite(t *testing.T) {
|
func TestUnalignedWrite(t *testing.T) {
|
||||||
testUnalignedAndGeneric(t, func(impl string) {
|
buf := sequentialBytes(0x10000)
|
||||||
buf := sequentialBytes(0x10000)
|
for alg, df := range testDigests {
|
||||||
for alg, df := range testDigests {
|
d := df()
|
||||||
d := df()
|
d.Reset()
|
||||||
d.Reset()
|
d.Write(buf)
|
||||||
d.Write(buf)
|
want := d.Sum(nil)
|
||||||
want := d.Sum(nil)
|
d.Reset()
|
||||||
d.Reset()
|
for i := 0; i < len(buf); {
|
||||||
for i := 0; i < len(buf); {
|
// Cycle through offsets which make a 137 byte sequence.
|
||||||
// Cycle through offsets which make a 137 byte sequence.
|
// Because 137 is prime this sequence should exercise all corner cases.
|
||||||
// Because 137 is prime this sequence should exercise all corner cases.
|
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
||||||
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
for _, j := range offsets {
|
||||||
for _, j := range offsets {
|
if v := len(buf) - i; v < j {
|
||||||
if v := len(buf) - i; v < j {
|
j = v
|
||||||
j = v
|
|
||||||
}
|
|
||||||
d.Write(buf[i : i+j])
|
|
||||||
i += j
|
|
||||||
}
|
}
|
||||||
}
|
d.Write(buf[i : i+j])
|
||||||
got := d.Sum(nil)
|
i += j
|
||||||
if !bytes.Equal(got, want) {
|
|
||||||
t.Errorf("Unaligned writes, implementation=%s, alg=%s\ngot %q, want %q", impl, alg, got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
got := d.Sum(nil)
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Same for SHAKE
|
// Same for SHAKE
|
||||||
for alg, df := range testShakes {
|
for alg, df := range testShakes {
|
||||||
want := make([]byte, 16)
|
want := make([]byte, 16)
|
||||||
got := make([]byte, 16)
|
got := make([]byte, 16)
|
||||||
d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr))
|
d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr))
|
||||||
|
|
||||||
d.Reset()
|
d.Reset()
|
||||||
d.Write(buf)
|
d.Write(buf)
|
||||||
d.Read(want)
|
d.Read(want)
|
||||||
d.Reset()
|
d.Reset()
|
||||||
for i := 0; i < len(buf); {
|
for i := 0; i < len(buf); {
|
||||||
// Cycle through offsets which make a 137 byte sequence.
|
// Cycle through offsets which make a 137 byte sequence.
|
||||||
// Because 137 is prime this sequence should exercise all corner cases.
|
// Because 137 is prime this sequence should exercise all corner cases.
|
||||||
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
||||||
for _, j := range offsets {
|
for _, j := range offsets {
|
||||||
if v := len(buf) - i; v < j {
|
if v := len(buf) - i; v < j {
|
||||||
j = v
|
j = v
|
||||||
}
|
|
||||||
d.Write(buf[i : i+j])
|
|
||||||
i += j
|
|
||||||
}
|
}
|
||||||
}
|
d.Write(buf[i : i+j])
|
||||||
d.Read(got)
|
i += j
|
||||||
if !bytes.Equal(got, want) {
|
|
||||||
t.Errorf("Unaligned writes, implementation=%s, alg=%s\ngot %q, want %q", impl, alg, got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
d.Read(got)
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAppend checks that appending works when reallocation is necessary.
|
// TestAppend checks that appending works when reallocation is necessary.
|
||||||
func TestAppend(t *testing.T) {
|
func TestAppend(t *testing.T) {
|
||||||
testUnalignedAndGeneric(t, func(impl string) {
|
d := New224()
|
||||||
d := New224()
|
|
||||||
|
|
||||||
for capacity := 2; capacity <= 66; capacity += 64 {
|
for capacity := 2; capacity <= 66; capacity += 64 {
|
||||||
// The first time around the loop, Sum will have to reallocate.
|
// The first time around the loop, Sum will have to reallocate.
|
||||||
// The second time, it will not.
|
// The second time, it will not.
|
||||||
buf := make([]byte, 2, capacity)
|
buf := make([]byte, 2, capacity)
|
||||||
d.Reset()
|
d.Reset()
|
||||||
d.Write([]byte{0xcc})
|
d.Write([]byte{0xcc})
|
||||||
buf = d.Sum(buf)
|
buf = d.Sum(buf)
|
||||||
expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
|
expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
|
||||||
if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
|
if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
|
||||||
t.Errorf("got %s, want %s", got, expected)
|
t.Errorf("got %s, want %s", got, expected)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAppendNoRealloc tests that appending works when no reallocation is necessary.
|
// TestAppendNoRealloc tests that appending works when no reallocation is necessary.
|
||||||
func TestAppendNoRealloc(t *testing.T) {
|
func TestAppendNoRealloc(t *testing.T) {
|
||||||
testUnalignedAndGeneric(t, func(impl string) {
|
buf := make([]byte, 1, 200)
|
||||||
buf := make([]byte, 1, 200)
|
d := New224()
|
||||||
d := New224()
|
d.Write([]byte{0xcc})
|
||||||
d.Write([]byte{0xcc})
|
buf = d.Sum(buf)
|
||||||
buf = d.Sum(buf)
|
expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
|
||||||
expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
|
if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
|
||||||
if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
|
t.Errorf("got %s, want %s", got, expected)
|
||||||
t.Errorf("%s: got %s, want %s", impl, got, expected)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSqueezing checks that squeezing the full output a single time produces
|
// TestSqueezing checks that squeezing the full output a single time produces
|
||||||
// the same output as repeatedly squeezing the instance.
|
// the same output as repeatedly squeezing the instance.
|
||||||
func TestSqueezing(t *testing.T) {
|
func TestSqueezing(t *testing.T) {
|
||||||
testUnalignedAndGeneric(t, func(impl string) {
|
for algo, v := range testShakes {
|
||||||
for algo, v := range testShakes {
|
d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
|
||||||
d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
|
d0.Write([]byte(testString))
|
||||||
d0.Write([]byte(testString))
|
ref := make([]byte, 32)
|
||||||
ref := make([]byte, 32)
|
d0.Read(ref)
|
||||||
d0.Read(ref)
|
|
||||||
|
|
||||||
d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
|
d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
|
||||||
d1.Write([]byte(testString))
|
d1.Write([]byte(testString))
|
||||||
var multiple []byte
|
var multiple []byte
|
||||||
for range ref {
|
for range ref {
|
||||||
one := make([]byte, 1)
|
one := make([]byte, 1)
|
||||||
d1.Read(one)
|
d1.Read(one)
|
||||||
multiple = append(multiple, one...)
|
multiple = append(multiple, one...)
|
||||||
}
|
|
||||||
if !bytes.Equal(ref, multiple) {
|
|
||||||
t.Errorf("%s (%s): squeezing %d bytes one at a time failed", algo, impl, len(ref))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
if !bytes.Equal(ref, multiple) {
|
||||||
|
t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
|
// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
|
||||||
|
|||||||
+9
-7
@@ -115,19 +115,21 @@ func (c *state) Clone() ShakeHash {
|
|||||||
// Its generic security strength is 128 bits against all attacks if at
|
// Its generic security strength is 128 bits against all attacks if at
|
||||||
// least 32 bytes of its output are used.
|
// least 32 bytes of its output are used.
|
||||||
func NewShake128() ShakeHash {
|
func NewShake128() ShakeHash {
|
||||||
if h := newShake128Asm(); h != nil {
|
return newShake128()
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
||||||
// Its generic security strength is 256 bits against all attacks if
|
// Its generic security strength is 256 bits against all attacks if
|
||||||
// at least 64 bytes of its output are used.
|
// at least 64 bytes of its output are used.
|
||||||
func NewShake256() ShakeHash {
|
func NewShake256() ShakeHash {
|
||||||
if h := newShake256Asm(); h != nil {
|
return newShake256()
|
||||||
return h
|
}
|
||||||
}
|
|
||||||
|
func newShake128Generic() *state {
|
||||||
|
return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShake256Generic() *state {
|
||||||
return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
|
return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !gc || purego || !s390x
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func newShake128Asm() ShakeHash {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
|
|
||||||
// otherwise it returns nil.
|
|
||||||
func newShake256Asm() ShakeHash {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2023 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !gc || purego || !s390x
|
||||||
|
|
||||||
|
package sha3
|
||||||
|
|
||||||
|
func newShake128() *state {
|
||||||
|
return newShake128Generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShake256() *state {
|
||||||
|
return newShake256Generic()
|
||||||
|
}
|
||||||
+31
-14
@@ -2,22 +2,39 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (!amd64 && !386 && !ppc64le) || purego
|
|
||||||
|
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
// A storageBuf is an aligned array of maxRate bytes.
|
import (
|
||||||
type storageBuf [maxRate]byte
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
func (b *storageBuf) asBytes() *[maxRate]byte {
|
"golang.org/x/sys/cpu"
|
||||||
return (*[maxRate]byte)(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
xorIn = xorInGeneric
|
|
||||||
copyOut = copyOutGeneric
|
|
||||||
xorInUnaligned = xorInGeneric
|
|
||||||
copyOutUnaligned = copyOutGeneric
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const xorImplementationUnaligned = "generic"
|
// xorIn xors the bytes in buf into the state.
|
||||||
|
func xorIn(d *state, buf []byte) {
|
||||||
|
if cpu.IsBigEndian {
|
||||||
|
for i := 0; len(buf) >= 8; i++ {
|
||||||
|
a := binary.LittleEndian.Uint64(buf)
|
||||||
|
d.a[i] ^= a
|
||||||
|
buf = buf[8:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
|
||||||
|
subtle.XORBytes(ab[:], ab[:], buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyOut copies uint64s to a byte buffer.
|
||||||
|
func copyOut(d *state, b []byte) {
|
||||||
|
if cpu.IsBigEndian {
|
||||||
|
for i := 0; len(b) >= 8; i++ {
|
||||||
|
binary.LittleEndian.PutUint64(b, d.a[i])
|
||||||
|
b = b[8:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
|
||||||
|
copy(b, ab[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright 2015 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 sha3
|
|
||||||
|
|
||||||
import "encoding/binary"
|
|
||||||
|
|
||||||
// xorInGeneric xors the bytes in buf into the state; it
|
|
||||||
// makes no non-portable assumptions about memory layout
|
|
||||||
// or alignment.
|
|
||||||
func xorInGeneric(d *state, buf []byte) {
|
|
||||||
n := len(buf) / 8
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
a := binary.LittleEndian.Uint64(buf)
|
|
||||||
d.a[i] ^= a
|
|
||||||
buf = buf[8:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyOutGeneric copies uint64s to a byte buffer.
|
|
||||||
func copyOutGeneric(d *state, b []byte) {
|
|
||||||
for i := 0; len(b) >= 8; i++ {
|
|
||||||
binary.LittleEndian.PutUint64(b, d.a[i])
|
|
||||||
b = b[8:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build (amd64 || 386 || ppc64le) && !purego
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// A storageBuf is an aligned array of maxRate bytes.
|
|
||||||
type storageBuf [maxRate / 8]uint64
|
|
||||||
|
|
||||||
func (b *storageBuf) asBytes() *[maxRate]byte {
|
|
||||||
return (*[maxRate]byte)(unsafe.Pointer(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// xorInUnaligned uses unaligned reads and writes to update d.a to contain d.a
|
|
||||||
// XOR buf.
|
|
||||||
func xorInUnaligned(d *state, buf []byte) {
|
|
||||||
n := len(buf)
|
|
||||||
bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
|
|
||||||
if n >= 72 {
|
|
||||||
d.a[0] ^= bw[0]
|
|
||||||
d.a[1] ^= bw[1]
|
|
||||||
d.a[2] ^= bw[2]
|
|
||||||
d.a[3] ^= bw[3]
|
|
||||||
d.a[4] ^= bw[4]
|
|
||||||
d.a[5] ^= bw[5]
|
|
||||||
d.a[6] ^= bw[6]
|
|
||||||
d.a[7] ^= bw[7]
|
|
||||||
d.a[8] ^= bw[8]
|
|
||||||
}
|
|
||||||
if n >= 104 {
|
|
||||||
d.a[9] ^= bw[9]
|
|
||||||
d.a[10] ^= bw[10]
|
|
||||||
d.a[11] ^= bw[11]
|
|
||||||
d.a[12] ^= bw[12]
|
|
||||||
}
|
|
||||||
if n >= 136 {
|
|
||||||
d.a[13] ^= bw[13]
|
|
||||||
d.a[14] ^= bw[14]
|
|
||||||
d.a[15] ^= bw[15]
|
|
||||||
d.a[16] ^= bw[16]
|
|
||||||
}
|
|
||||||
if n >= 144 {
|
|
||||||
d.a[17] ^= bw[17]
|
|
||||||
}
|
|
||||||
if n >= 168 {
|
|
||||||
d.a[18] ^= bw[18]
|
|
||||||
d.a[19] ^= bw[19]
|
|
||||||
d.a[20] ^= bw[20]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyOutUnaligned(d *state, buf []byte) {
|
|
||||||
ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
|
|
||||||
copy(buf, ab[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
xorIn = xorInUnaligned
|
|
||||||
copyOut = copyOutUnaligned
|
|
||||||
)
|
|
||||||
|
|
||||||
const xorImplementationUnaligned = "unaligned"
|
|
||||||
+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)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -404,10 +404,10 @@ func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, e
|
|||||||
return false, err
|
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()
|
pubKey := key.Marshal()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -425,7 +425,15 @@ func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) {
|
|||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, err
|
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 false, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
+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,
|
||||||
|
|||||||
@@ -904,6 +904,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return errors.New("ssh: signature did not verify")
|
return errors.New("ssh: signature did not verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *skECDSAPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return &k.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
type skEd25519PublicKey struct {
|
type skEd25519PublicKey struct {
|
||||||
// application is a URL-like string, typically "ssh:" for SSH.
|
// application is a URL-like string, typically "ssh:" for SSH.
|
||||||
// see openssh/PROTOCOL.u2f for details.
|
// see openssh/PROTOCOL.u2f for details.
|
||||||
@@ -1000,6 +1004,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *skEd25519PublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return k.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
||||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a
|
// *ecdsa.PrivateKey or any other crypto.Signer and returns a
|
||||||
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or
|
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or
|
||||||
|
|||||||
@@ -726,3 +726,49 @@ func TestNewSignerWithAlgos(t *testing.T) {
|
|||||||
t.Error("signer with algos created with restricted algorithms")
|
t.Error("signer with algos created with restricted algorithms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCryptoPublicKey(t *testing.T) {
|
||||||
|
for _, priv := range testSigners {
|
||||||
|
p1 := priv.PublicKey()
|
||||||
|
key, ok := p1.(CryptoPublicKey)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p2, err := NewPublicKey(key.CryptoPublicKey())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPublicKey(CryptoPublicKey) failed for %s, got: %v", p1.Type(), err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(p1, p2) {
|
||||||
|
t.Errorf("got %#v in NewPublicKey, want %#v", p2, p1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, d := range testdata.SKData {
|
||||||
|
p1, _, _, _, err := ParseAuthorizedKey(d.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseAuthorizedKey returned error: %v", err)
|
||||||
|
}
|
||||||
|
k1, ok := p1.(CryptoPublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%T does not implement CryptoPublicKey", p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var p2 PublicKey
|
||||||
|
switch pub := k1.CryptoPublicKey().(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
p2 = &skECDSAPublicKey{
|
||||||
|
application: "ssh:",
|
||||||
|
PublicKey: *pub,
|
||||||
|
}
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
p2 = &skEd25519PublicKey{
|
||||||
|
application: "ssh:",
|
||||||
|
PublicKey: pub,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected type %T from CryptoPublicKey()", pub)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(p1, p2) {
|
||||||
|
t.Errorf("got %#v, want %#v", p2, p1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+150
-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
|
||||||
@@ -433,14 +462,42 @@ func (l ServerAuthError) Error() string {
|
|||||||
// It is returned in ServerAuthError.Errors from NewServerConn.
|
// It is returned in ServerAuthError.Errors from NewServerConn.
|
||||||
var ErrNoAuth = errors.New("ssh: no auth passed yet")
|
var ErrNoAuth = errors.New("ssh: no auth passed yet")
|
||||||
|
|
||||||
|
// BannerError is an error that can be returned by authentication handlers in
|
||||||
|
// ServerConfig to send a banner message to the client.
|
||||||
|
type BannerError struct {
|
||||||
|
Err error
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BannerError) Unwrap() error {
|
||||||
|
return b.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BannerError) Error() string {
|
||||||
|
if b.Err == nil {
|
||||||
|
return b.Message
|
||||||
|
}
|
||||||
|
return b.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||||
sessionID := s.transport.getSessionID()
|
sessionID := s.transport.getSessionID()
|
||||||
var cache pubKeyCache
|
var cache pubKeyCache
|
||||||
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 +528,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 +553,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 +578,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 +622,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 +645,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 +696,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)
|
||||||
@@ -685,53 +752,86 @@ userAuthLoop:
|
|||||||
config.AuthLogCallback(s, userAuthReq.Method, authErr)
|
config.AuthLogCallback(s, userAuthReq.Method, authErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bannerErr *BannerError
|
||||||
|
if errors.As(authErr, &bannerErr) {
|
||||||
|
if bannerErr.Message != "" {
|
||||||
|
bannerMsg := &userAuthBannerMsg{
|
||||||
|
Message: bannerErr.Message,
|
||||||
|
}
|
||||||
|
if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if authErr == nil {
|
if authErr == nil {
|
||||||
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,12 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -62,6 +66,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},
|
||||||
@@ -96,6 +227,78 @@ func TestNewServerConnValidationErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBannerError(t *testing.T) {
|
||||||
|
serverConfig := &ServerConfig{
|
||||||
|
BannerCallback: func(ConnMetadata) string {
|
||||||
|
return "banner from BannerCallback"
|
||||||
|
},
|
||||||
|
NoClientAuth: true,
|
||||||
|
NoClientAuthCallback: func(ConnMetadata) (*Permissions, error) {
|
||||||
|
err := &BannerError{
|
||||||
|
Err: errors.New("error from NoClientAuthCallback"),
|
||||||
|
Message: "banner from NoClientAuthCallback",
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("wrapped: %w", err)
|
||||||
|
},
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
return &Permissions{}, nil
|
||||||
|
},
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
return nil, &BannerError{
|
||||||
|
Err: errors.New("error from PublicKeyCallback"),
|
||||||
|
Message: "banner from PublicKeyCallback",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KeyboardInteractiveCallback: func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) {
|
||||||
|
return nil, &BannerError{
|
||||||
|
Err: nil, // make sure that a nil inner error is allowed
|
||||||
|
Message: "banner from KeyboardInteractiveCallback",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConfig.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
var banners []string
|
||||||
|
clientConfig := &ClientConfig{
|
||||||
|
User: "test",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
KeyboardInteractive(func(name, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||||
|
return []string{"letmein"}, nil
|
||||||
|
}),
|
||||||
|
Password(clientPassword),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
BannerCallback: func(msg string) error {
|
||||||
|
banners = append(banners, msg)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
go newServer(c1, serverConfig)
|
||||||
|
c, _, _, err := NewClientConn(c2, "", clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("client connection failed: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
wantBanners := []string{
|
||||||
|
"banner from BannerCallback",
|
||||||
|
"banner from NoClientAuthCallback",
|
||||||
|
"banner from PublicKeyCallback",
|
||||||
|
"banner from KeyboardInteractiveCallback",
|
||||||
|
}
|
||||||
|
if !slices.Equal(banners, wantBanners) {
|
||||||
|
t.Errorf("got banners:\n%q\nwant banners:\n%q", banners, wantBanners)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type markerConn struct {
|
type markerConn struct {
|
||||||
closed uint32
|
closed uint32
|
||||||
used uint32
|
used uint32
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -1526,6 +1526,24 @@ u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
|
|||||||
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
|
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
|
||||||
fF6adulZkMV8gzURZVE=
|
fF6adulZkMV8gzURZVE=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
# CN=FIRMAPROFESIONAL CA ROOT-A WEB,O=Firmaprofesional SA,C=ES,2.5.4.97=#130f56415445532d413632363334303638
|
||||||
|
# bef256daf26e9c69bdec1602359798f3caf71821a03e018257c53c65617f3d4a
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw
|
||||||
|
CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE
|
||||||
|
YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB
|
||||||
|
IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw
|
||||||
|
CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE
|
||||||
|
YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB
|
||||||
|
IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf
|
||||||
|
e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C
|
||||||
|
cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB
|
||||||
|
/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O
|
||||||
|
BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO
|
||||||
|
PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw
|
||||||
|
hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG
|
||||||
|
XSaQpYXFuXqUPoeovQA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
# CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\,LTD.,C=CN
|
# CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\,LTD.,C=CN
|
||||||
# bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893
|
# bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
|
|||||||
Reference in New Issue
Block a user