Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d375be9b6 | ||
|
|
b4ddeeda5b | ||
|
|
edc325d13a | ||
|
|
eab9315960 | ||
|
|
ddfa82138e | ||
|
|
d08e19beac | ||
|
|
12e1fcd8c0 | ||
|
|
3f8f0645c2 | ||
|
|
5df3b59941 | ||
|
|
2e82bdd171 | ||
|
|
64e0e99383 | ||
|
|
23b1b90df2 | ||
|
|
e98487292d | ||
|
|
183630ada7 | ||
|
|
a9e447dde7 | ||
|
|
64c3993f5c | ||
|
|
5fe8145aca | ||
|
|
043e94c17a | ||
|
|
0d502d7cd6 | ||
|
|
0ff60057bb | ||
|
|
8e447d8cc5 | ||
|
|
22c9997952 | ||
|
|
d0b316056b | ||
|
|
16222386f4 | ||
|
|
a4e984136a | ||
|
|
3ef8056490 | ||
|
|
a8cc953517 | ||
|
|
7d6d3f5d4a | ||
|
|
1faeef9713 | ||
|
|
00fd4ff485 | ||
|
|
018c28f8a1 | ||
|
|
9cd0187c09 | ||
|
|
c6a20f9984 |
@@ -431,6 +431,14 @@ func (s *String) readBase128Int(out *int) bool {
|
|||||||
}
|
}
|
||||||
ret <<= 7
|
ret <<= 7
|
||||||
b := s.read(1)[0]
|
b := s.read(1)[0]
|
||||||
|
|
||||||
|
// ITU-T X.690, section 8.19.2:
|
||||||
|
// The subidentifier shall be encoded in the fewest possible octets,
|
||||||
|
// that is, the leading octet of the subidentifier shall not have the value 0x80.
|
||||||
|
if i == 0 && b == 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ret |= int(b & 0x7f)
|
ret |= int(b & 0x7f)
|
||||||
if b&0x80 == 0 {
|
if b&0x80 == 0 {
|
||||||
*out = ret
|
*out = ret
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ func TestASN1ObjectIdentifier(t *testing.T) {
|
|||||||
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
|
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
|
||||||
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
|
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
|
||||||
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
|
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
|
||||||
|
{[]byte{6, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range testData {
|
for i, test := range testData {
|
||||||
|
|||||||
@@ -5,71 +5,18 @@
|
|||||||
// Package curve25519 provides an implementation of the X25519 function, which
|
// Package curve25519 provides an implementation of the X25519 function, which
|
||||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||||
// See RFC 7748.
|
// See RFC 7748.
|
||||||
|
//
|
||||||
|
// Starting in Go 1.20, this package is a wrapper for the X25519 implementation
|
||||||
|
// in the crypto/ecdh package.
|
||||||
package curve25519 // import "golang.org/x/crypto/curve25519"
|
package curve25519 // import "golang.org/x/crypto/curve25519"
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519/internal/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ScalarMult sets dst to the product scalar * point.
|
// ScalarMult sets dst to the product scalar * point.
|
||||||
//
|
//
|
||||||
// Deprecated: when provided a low-order point, ScalarMult will set dst to all
|
// Deprecated: when provided a low-order point, ScalarMult will set dst to all
|
||||||
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
|
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
|
||||||
// will return an error.
|
// will return an error.
|
||||||
func ScalarMult(dst, scalar, point *[32]byte) {
|
func ScalarMult(dst, scalar, point *[32]byte) {
|
||||||
var e [32]byte
|
scalarMult(dst, scalar, point)
|
||||||
|
|
||||||
copy(e[:], scalar[:])
|
|
||||||
e[0] &= 248
|
|
||||||
e[31] &= 127
|
|
||||||
e[31] |= 64
|
|
||||||
|
|
||||||
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
|
|
||||||
x1.SetBytes(point[:])
|
|
||||||
x2.One()
|
|
||||||
x3.Set(&x1)
|
|
||||||
z3.One()
|
|
||||||
|
|
||||||
swap := 0
|
|
||||||
for pos := 254; pos >= 0; pos-- {
|
|
||||||
b := e[pos/8] >> uint(pos&7)
|
|
||||||
b &= 1
|
|
||||||
swap ^= int(b)
|
|
||||||
x2.Swap(&x3, swap)
|
|
||||||
z2.Swap(&z3, swap)
|
|
||||||
swap = int(b)
|
|
||||||
|
|
||||||
tmp0.Subtract(&x3, &z3)
|
|
||||||
tmp1.Subtract(&x2, &z2)
|
|
||||||
x2.Add(&x2, &z2)
|
|
||||||
z2.Add(&x3, &z3)
|
|
||||||
z3.Multiply(&tmp0, &x2)
|
|
||||||
z2.Multiply(&z2, &tmp1)
|
|
||||||
tmp0.Square(&tmp1)
|
|
||||||
tmp1.Square(&x2)
|
|
||||||
x3.Add(&z3, &z2)
|
|
||||||
z2.Subtract(&z3, &z2)
|
|
||||||
x2.Multiply(&tmp1, &tmp0)
|
|
||||||
tmp1.Subtract(&tmp1, &tmp0)
|
|
||||||
z2.Square(&z2)
|
|
||||||
|
|
||||||
z3.Mult32(&tmp1, 121666)
|
|
||||||
x3.Square(&x3)
|
|
||||||
tmp0.Add(&tmp0, &z3)
|
|
||||||
z3.Multiply(&x1, &z2)
|
|
||||||
z2.Multiply(&tmp1, &tmp0)
|
|
||||||
}
|
|
||||||
|
|
||||||
x2.Swap(&x3, swap)
|
|
||||||
z2.Swap(&z3, swap)
|
|
||||||
|
|
||||||
z2.Invert(&z2)
|
|
||||||
x2.Multiply(&x2, &z2)
|
|
||||||
copy(dst[:], x2.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScalarBaseMult sets dst to the product scalar * base where base is the
|
// ScalarBaseMult sets dst to the product scalar * base where base is the
|
||||||
@@ -78,7 +25,7 @@ func ScalarMult(dst, scalar, point *[32]byte) {
|
|||||||
// It is recommended to use the X25519 function with Basepoint instead, as
|
// It is recommended to use the X25519 function with Basepoint instead, as
|
||||||
// copying into fixed size arrays can lead to unexpected bugs.
|
// copying into fixed size arrays can lead to unexpected bugs.
|
||||||
func ScalarBaseMult(dst, scalar *[32]byte) {
|
func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||||
ScalarMult(dst, scalar, &basePoint)
|
scalarBaseMult(dst, scalar)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -91,21 +38,10 @@ const (
|
|||||||
// Basepoint is the canonical Curve25519 generator.
|
// Basepoint is the canonical Curve25519 generator.
|
||||||
var Basepoint []byte
|
var Basepoint []byte
|
||||||
|
|
||||||
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var basePoint = [32]byte{9}
|
||||||
|
|
||||||
func init() { Basepoint = basePoint[:] }
|
func init() { Basepoint = basePoint[:] }
|
||||||
|
|
||||||
func checkBasepoint() {
|
|
||||||
if subtle.ConstantTimeCompare(Basepoint, []byte{
|
|
||||||
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
}) != 1 {
|
|
||||||
panic("curve25519: global Basepoint value was modified")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// X25519 returns the result of the scalar multiplication (scalar * point),
|
// X25519 returns the result of the scalar multiplication (scalar * point),
|
||||||
// according to RFC 7748, Section 5. scalar, point and the return value are
|
// according to RFC 7748, Section 5. scalar, point and the return value are
|
||||||
// slices of 32 bytes.
|
// slices of 32 bytes.
|
||||||
@@ -121,26 +57,3 @@ func X25519(scalar, point []byte) ([]byte, error) {
|
|||||||
var dst [32]byte
|
var dst [32]byte
|
||||||
return x25519(&dst, scalar, point)
|
return x25519(&dst, scalar, point)
|
||||||
}
|
}
|
||||||
|
|
||||||
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
|
||||||
var in [32]byte
|
|
||||||
if l := len(scalar); l != 32 {
|
|
||||||
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
|
|
||||||
}
|
|
||||||
if l := len(point); l != 32 {
|
|
||||||
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
|
|
||||||
}
|
|
||||||
copy(in[:], scalar)
|
|
||||||
if &point[0] == &Basepoint[0] {
|
|
||||||
checkBasepoint()
|
|
||||||
ScalarBaseMult(dst, &in)
|
|
||||||
} else {
|
|
||||||
var base, zero [32]byte
|
|
||||||
copy(base[:], point)
|
|
||||||
ScalarMult(dst, &in, &base)
|
|
||||||
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
|
|
||||||
return nil, errors.New("bad input point: low order point")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst[:], nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2019 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 !go1.20
|
||||||
|
|
||||||
|
package curve25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519/internal/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
func scalarMult(dst, scalar, point *[32]byte) {
|
||||||
|
var e [32]byte
|
||||||
|
|
||||||
|
copy(e[:], scalar[:])
|
||||||
|
e[0] &= 248
|
||||||
|
e[31] &= 127
|
||||||
|
e[31] |= 64
|
||||||
|
|
||||||
|
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
|
||||||
|
x1.SetBytes(point[:])
|
||||||
|
x2.One()
|
||||||
|
x3.Set(&x1)
|
||||||
|
z3.One()
|
||||||
|
|
||||||
|
swap := 0
|
||||||
|
for pos := 254; pos >= 0; pos-- {
|
||||||
|
b := e[pos/8] >> uint(pos&7)
|
||||||
|
b &= 1
|
||||||
|
swap ^= int(b)
|
||||||
|
x2.Swap(&x3, swap)
|
||||||
|
z2.Swap(&z3, swap)
|
||||||
|
swap = int(b)
|
||||||
|
|
||||||
|
tmp0.Subtract(&x3, &z3)
|
||||||
|
tmp1.Subtract(&x2, &z2)
|
||||||
|
x2.Add(&x2, &z2)
|
||||||
|
z2.Add(&x3, &z3)
|
||||||
|
z3.Multiply(&tmp0, &x2)
|
||||||
|
z2.Multiply(&z2, &tmp1)
|
||||||
|
tmp0.Square(&tmp1)
|
||||||
|
tmp1.Square(&x2)
|
||||||
|
x3.Add(&z3, &z2)
|
||||||
|
z2.Subtract(&z3, &z2)
|
||||||
|
x2.Multiply(&tmp1, &tmp0)
|
||||||
|
tmp1.Subtract(&tmp1, &tmp0)
|
||||||
|
z2.Square(&z2)
|
||||||
|
|
||||||
|
z3.Mult32(&tmp1, 121666)
|
||||||
|
x3.Square(&x3)
|
||||||
|
tmp0.Add(&tmp0, &z3)
|
||||||
|
z3.Multiply(&x1, &z2)
|
||||||
|
z2.Multiply(&tmp1, &tmp0)
|
||||||
|
}
|
||||||
|
|
||||||
|
x2.Swap(&x3, swap)
|
||||||
|
z2.Swap(&z3, swap)
|
||||||
|
|
||||||
|
z2.Invert(&z2)
|
||||||
|
x2.Multiply(&x2, &z2)
|
||||||
|
copy(dst[:], x2.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarBaseMult(dst, scalar *[32]byte) {
|
||||||
|
checkBasepoint()
|
||||||
|
scalarMult(dst, scalar, &basePoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||||
|
var in [32]byte
|
||||||
|
if l := len(scalar); l != 32 {
|
||||||
|
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
|
||||||
|
}
|
||||||
|
if l := len(point); l != 32 {
|
||||||
|
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
|
||||||
|
}
|
||||||
|
copy(in[:], scalar)
|
||||||
|
if &point[0] == &Basepoint[0] {
|
||||||
|
scalarBaseMult(dst, &in)
|
||||||
|
} else {
|
||||||
|
var base, zero [32]byte
|
||||||
|
copy(base[:], point)
|
||||||
|
scalarMult(dst, &in, &base)
|
||||||
|
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
|
||||||
|
return nil, errors.New("bad input point: low order point")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBasepoint() {
|
||||||
|
if subtle.ConstantTimeCompare(Basepoint, []byte{
|
||||||
|
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}) != 1 {
|
||||||
|
panic("curve25519: global Basepoint value was modified")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2022 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 go1.20
|
||||||
|
|
||||||
|
package curve25519
|
||||||
|
|
||||||
|
import "crypto/ecdh"
|
||||||
|
|
||||||
|
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||||
|
curve := ecdh.X25519()
|
||||||
|
pub, err := curve.NewPublicKey(point)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv, err := curve.NewPrivateKey(scalar)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out, err := priv.ECDH(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
copy(dst[:], out)
|
||||||
|
return dst[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarMult(dst, scalar, point *[32]byte) {
|
||||||
|
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
|
||||||
|
// The only error condition for x25519 when the inputs are 32 bytes long
|
||||||
|
// is if the output would have been the all-zero value.
|
||||||
|
for i := range dst {
|
||||||
|
dst[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarBaseMult(dst, scalar *[32]byte) {
|
||||||
|
curve := ecdh.X25519()
|
||||||
|
priv, err := curve.NewPrivateKey(scalar[:])
|
||||||
|
if err != nil {
|
||||||
|
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
|
||||||
|
}
|
||||||
|
copy(dst[:], priv.PublicKey().Bytes())
|
||||||
|
}
|
||||||
@@ -2,13 +2,15 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
package curve25519
|
package curve25519_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||||
@@ -19,7 +21,7 @@ func TestX25519Basepoint(t *testing.T) {
|
|||||||
|
|
||||||
for i := 0; i < 200; i++ {
|
for i := 0; i < 200; i++ {
|
||||||
var err error
|
var err error
|
||||||
x, err = X25519(x, Basepoint)
|
x, err = curve25519.X25519(x, curve25519.Basepoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -32,12 +34,12 @@ func TestX25519Basepoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLowOrderPoints(t *testing.T) {
|
func TestLowOrderPoints(t *testing.T) {
|
||||||
scalar := make([]byte, ScalarSize)
|
scalar := make([]byte, curve25519.ScalarSize)
|
||||||
if _, err := rand.Read(scalar); err != nil {
|
if _, err := rand.Read(scalar); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for i, p := range lowOrderPoints {
|
for i, p := range lowOrderPoints {
|
||||||
out, err := X25519(scalar, p)
|
out, err := curve25519.X25519(scalar, p)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("%d: expected error, got nil", i)
|
t.Errorf("%d: expected error, got nil", i)
|
||||||
}
|
}
|
||||||
@@ -48,10 +50,10 @@ func TestLowOrderPoints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTestVectors(t *testing.T) {
|
func TestTestVectors(t *testing.T) {
|
||||||
t.Run("Legacy", func(t *testing.T) { testTestVectors(t, ScalarMult) })
|
t.Run("Legacy", func(t *testing.T) { testTestVectors(t, curve25519.ScalarMult) })
|
||||||
t.Run("X25519", func(t *testing.T) {
|
t.Run("X25519", func(t *testing.T) {
|
||||||
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
||||||
out, err := X25519(scalar[:], point[:])
|
out, err := curve25519.X25519(scalar[:], point[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -88,10 +90,10 @@ func TestHighBitIgnored(t *testing.T) {
|
|||||||
var hi0, hi1 [32]byte
|
var hi0, hi1 [32]byte
|
||||||
|
|
||||||
u[31] &= 0x7f
|
u[31] &= 0x7f
|
||||||
ScalarMult(&hi0, &s, &u)
|
curve25519.ScalarMult(&hi0, &s, &u)
|
||||||
|
|
||||||
u[31] |= 0x80
|
u[31] |= 0x80
|
||||||
ScalarMult(&hi1, &s, &u)
|
curve25519.ScalarMult(&hi1, &s, &u)
|
||||||
|
|
||||||
if !bytes.Equal(hi0[:], hi1[:]) {
|
if !bytes.Equal(hi0[:], hi1[:]) {
|
||||||
t.Errorf("high bit of group point should not affect result")
|
t.Errorf("high bit of group point should not affect result")
|
||||||
@@ -101,14 +103,14 @@ func TestHighBitIgnored(t *testing.T) {
|
|||||||
var benchmarkSink byte
|
var benchmarkSink byte
|
||||||
|
|
||||||
func BenchmarkX25519Basepoint(b *testing.B) {
|
func BenchmarkX25519Basepoint(b *testing.B) {
|
||||||
scalar := make([]byte, ScalarSize)
|
scalar := make([]byte, curve25519.ScalarSize)
|
||||||
if _, err := rand.Read(scalar); err != nil {
|
if _, err := rand.Read(scalar); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
out, err := X25519(scalar, Basepoint)
|
out, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -117,11 +119,11 @@ func BenchmarkX25519Basepoint(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkX25519(b *testing.B) {
|
func BenchmarkX25519(b *testing.B) {
|
||||||
scalar := make([]byte, ScalarSize)
|
scalar := make([]byte, curve25519.ScalarSize)
|
||||||
if _, err := rand.Read(scalar); err != nil {
|
if _, err := rand.Read(scalar); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
point, err := X25519(scalar, Basepoint)
|
point, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -131,7 +133,7 @@ func BenchmarkX25519(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
out, err := X25519(scalar, point)
|
out, err := curve25519.X25519(scalar, point)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
package curve25519
|
package curve25519_test
|
||||||
|
|
||||||
// lowOrderPoints from libsodium.
|
// lowOrderPoints from libsodium.
|
||||||
// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
|
// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ module golang.org/x/crypto
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.10.0 // tagx:ignore
|
||||||
golang.org/x/sys v0.6.0
|
golang.org/x/sys v0.12.0
|
||||||
golang.org/x/term v0.6.0
|
golang.org/x/term v0.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.8.0 // indirect
|
require golang.org/x/text v0.13.0 // indirect
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -18,19 +18,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package testenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandContext is like exec.CommandContext, but:
|
||||||
|
// - skips t if the platform does not support os/exec,
|
||||||
|
// - sends SIGQUIT (if supported by the platform) instead of SIGKILL
|
||||||
|
// in its Cancel function
|
||||||
|
// - if the test has a deadline, adds a Context timeout and WaitDelay
|
||||||
|
// for an arbitrary grace period before the test's deadline expires,
|
||||||
|
// - fails the test if the command does not complete before the test's deadline, and
|
||||||
|
// - sets a Cleanup function that verifies that the test did not leak a subprocess.
|
||||||
|
func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var (
|
||||||
|
cancelCtx context.CancelFunc
|
||||||
|
gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging)
|
||||||
|
)
|
||||||
|
|
||||||
|
if t, ok := t.(interface {
|
||||||
|
testing.TB
|
||||||
|
Deadline() (time.Time, bool)
|
||||||
|
}); ok {
|
||||||
|
if td, ok := t.Deadline(); ok {
|
||||||
|
// Start with a minimum grace period, just long enough to consume the
|
||||||
|
// output of a reasonable program after it terminates.
|
||||||
|
gracePeriod = 100 * time.Millisecond
|
||||||
|
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
|
||||||
|
scale, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err)
|
||||||
|
}
|
||||||
|
gracePeriod *= time.Duration(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If time allows, increase the termination grace period to 5% of the
|
||||||
|
// test's remaining time.
|
||||||
|
testTimeout := time.Until(td)
|
||||||
|
if gp := testTimeout / 20; gp > gracePeriod {
|
||||||
|
gracePeriod = gp
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we run commands that execute subprocesses, we want to reserve two
|
||||||
|
// grace periods to clean up: one for the delay between the first
|
||||||
|
// termination signal being sent (via the Cancel callback when the Context
|
||||||
|
// expires) and the process being forcibly terminated (via the WaitDelay
|
||||||
|
// field), and a second one for the delay becween the process being
|
||||||
|
// terminated and and the test logging its output for debugging.
|
||||||
|
//
|
||||||
|
// (We want to ensure that the test process itself has enough time to
|
||||||
|
// log the output before it is also terminated.)
|
||||||
|
cmdTimeout := testTimeout - 2*gracePeriod
|
||||||
|
|
||||||
|
if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout {
|
||||||
|
// Either ctx doesn't have a deadline, or its deadline would expire
|
||||||
|
// after (or too close before) the test has already timed out.
|
||||||
|
// Add a shorter timeout so that the test will produce useful output.
|
||||||
|
ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
|
// Set the Cancel and WaitDelay fields only if present (go 1.20 and later).
|
||||||
|
// TODO: When Go 1.19 is no longer supported, remove this use of reflection
|
||||||
|
// and instead set the fields directly.
|
||||||
|
if cmdCancel := reflect.ValueOf(cmd).Elem().FieldByName("Cancel"); cmdCancel.IsValid() {
|
||||||
|
cmdCancel.Set(reflect.ValueOf(func() error {
|
||||||
|
if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded {
|
||||||
|
// The command timed out due to running too close to the test's deadline.
|
||||||
|
// There is no way the test did that intentionally — it's too close to the
|
||||||
|
// wire! — so mark it as a test failure. That way, if the test expects the
|
||||||
|
// command to fail for some other reason, it doesn't have to distinguish
|
||||||
|
// between that reason and a timeout.
|
||||||
|
t.Errorf("test timed out while running command: %v", cmd)
|
||||||
|
} else {
|
||||||
|
// The command is being terminated due to ctx being canceled, but
|
||||||
|
// apparently not due to an explicit test deadline that we added.
|
||||||
|
// Log that information in case it is useful for diagnosing a failure,
|
||||||
|
// but don't actually fail the test because of it.
|
||||||
|
t.Logf("%v: terminating command: %v", ctx.Err(), cmd)
|
||||||
|
}
|
||||||
|
return cmd.Process.Signal(Sigquit)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if cmdWaitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); cmdWaitDelay.IsValid() {
|
||||||
|
cmdWaitDelay.Set(reflect.ValueOf(gracePeriod))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if cancelCtx != nil {
|
||||||
|
cancelCtx()
|
||||||
|
}
|
||||||
|
if cmd.Process != nil && cmd.ProcessState == nil {
|
||||||
|
t.Errorf("command was started, but test did not wait for it to complete: %v", cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command is like exec.Command, but applies the same changes as
|
||||||
|
// testenv.CommandContext (with a default Context).
|
||||||
|
func Command(t testing.TB, name string, args ...string) *exec.Cmd {
|
||||||
|
t.Helper()
|
||||||
|
return CommandContext(t, context.Background(), name, args...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2021 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 windows || plan9 || (js && wasm) || wasip1
|
||||||
|
|
||||||
|
package testenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||||
|
// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
|
||||||
|
var Sigquit = os.Kill
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2021 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 unix
|
||||||
|
|
||||||
|
package testenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||||
|
// Send SIGQUIT to get a stack trace.
|
||||||
|
var Sigquit = syscall.SIGQUIT
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -28,6 +29,11 @@ const wycheproofModVer = "v0.0.0-20191219022705-2196000605e4"
|
|||||||
var wycheproofTestVectorsDir string
|
var wycheproofTestVectorsDir string
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
flag.Parse()
|
||||||
|
if flag.Lookup("test.short").Value.(flag.Getter).Get().(bool) {
|
||||||
|
log.Println("skipping test that downloads testdata via 'go mod download' in short mode")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
if _, err := exec.LookPath("go"); err != nil {
|
||||||
log.Printf("skipping test because 'go' command is unavailable: %v", err)
|
log.Printf("skipping test because 'go' command is unavailable: %v", err)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -42,8 +48,6 @@ func TestMain(m *testing.M) {
|
|||||||
// can be used in the following tests.
|
// can be used in the following tests.
|
||||||
path := "github.com/google/wycheproof@" + wycheproofModVer
|
path := "github.com/google/wycheproof@" + wycheproofModVer
|
||||||
cmd := exec.Command("go", "mod", "download", "-json", path)
|
cmd := exec.Command("go", "mod", "download", "-json", path)
|
||||||
// TODO: enable the sumdb once the Trybots proxy supports it.
|
|
||||||
cmd.Env = append(os.Environ(), "GONOSUMDB=*")
|
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
|
log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (c *Compressed) parse(r io.Reader) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// compressedWriterCloser represents the serialized compression stream
|
// compressedWriteCloser represents the serialized compression stream
|
||||||
// header and the compressor. Its Close() method ensures that both the
|
// header and the compressor. Its Close() method ensures that both the
|
||||||
// compressor and serialized stream header are closed. Its Write()
|
// compressor and serialized stream header are closed. Its Write()
|
||||||
// method writes to the compressor.
|
// method writes to the compressor.
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
|||||||
// types supported vary by platform.
|
// types supported vary by platform.
|
||||||
t.Skip("skipping test due to -short")
|
t.Skip("skipping test due to -short")
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skipping on windows, we don't support connecting to the ssh-agent via a named pipe")
|
||||||
|
}
|
||||||
|
|
||||||
bin, err := exec.LookPath("ssh-agent")
|
bin, err := exec.LookPath("ssh-agent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -366,7 +369,8 @@ func TestAuth(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Server: %v", err)
|
t.Errorf("NewServerConn error: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server wraps an Agent and uses it to implement the agent side of
|
// server wraps an Agent and uses it to implement the agent side of
|
||||||
// the SSH-agent, wire protocol.
|
// the SSH-agent, wire protocol.
|
||||||
type server struct {
|
type server struct {
|
||||||
agent Agent
|
agent Agent
|
||||||
|
|||||||
@@ -53,10 +53,11 @@ func TestSetupForwardAgent(t *testing.T) {
|
|||||||
incoming := make(chan *ssh.ServerConn, 1)
|
incoming := make(chan *ssh.ServerConn, 1)
|
||||||
go func() {
|
go func() {
|
||||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Server: %v", err)
|
|
||||||
}
|
|
||||||
incoming <- conn
|
incoming <- conn
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewServerConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
conf := ssh.ClientConfig{
|
conf := ssh.ClientConfig{
|
||||||
@@ -71,8 +72,10 @@ func TestSetupForwardAgent(t *testing.T) {
|
|||||||
if err := ForwardToRemote(client, socket); err != nil {
|
if err := ForwardToRemote(client, socket); err != nil {
|
||||||
t.Fatalf("SetupForwardAgent: %v", err)
|
t.Fatalf("SetupForwardAgent: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
server := <-incoming
|
server := <-incoming
|
||||||
|
if server == nil {
|
||||||
|
t.Fatal("Unable to get server")
|
||||||
|
}
|
||||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -90,16 +91,16 @@ func BenchmarkEndToEnd(b *testing.B) {
|
|||||||
go func() {
|
go func() {
|
||||||
newCh, err := server.Accept()
|
newCh, err := server.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Client: %v", err)
|
panic(fmt.Sprintf("Client: %v", err))
|
||||||
}
|
}
|
||||||
ch, incoming, err := newCh.Accept()
|
ch, incoming, err := newCh.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Accept: %v", err)
|
panic(fmt.Sprintf("Accept: %v", err))
|
||||||
}
|
}
|
||||||
go DiscardRequests(incoming)
|
go DiscardRequests(incoming)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if _, err := io.ReadFull(ch, output); err != nil {
|
if _, err := io.ReadFull(ch, output); err != nil {
|
||||||
b.Fatalf("ReadFull: %v", err)
|
panic(fmt.Sprintf("ReadFull: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ch.Close()
|
ch.Close()
|
||||||
|
|||||||
@@ -955,3 +955,124 @@ func TestAuthMethodGSSAPIWithMIC(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompatibleAlgoAndSignatures(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
algo string
|
||||||
|
sigFormat string
|
||||||
|
compatible bool
|
||||||
|
}
|
||||||
|
testcases := []*testcase{
|
||||||
|
{
|
||||||
|
KeyAlgoRSA,
|
||||||
|
KeyAlgoRSA,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSA,
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSA,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
KeyAlgoRSA,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
KeyAlgoRSA,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSAv01,
|
||||||
|
KeyAlgoRSA,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSAv01,
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSAv01,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSASHA256v01,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSASHA512v01,
|
||||||
|
KeyAlgoRSASHA512,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSASHA512v01,
|
||||||
|
KeyAlgoRSASHA256,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSASHA256v01,
|
||||||
|
CertAlgoRSAv01,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CertAlgoRSAv01,
|
||||||
|
CertAlgoRSASHA512v01,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
KeyAlgoRSA,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
KeyAlgoECDSA521,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
KeyAlgoED25519,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KeyAlgoED25519,
|
||||||
|
KeyAlgoED25519,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testcases {
|
||||||
|
if isAlgoCompatible(c.algo, c.sigFormat) != c.compatible {
|
||||||
|
t.Errorf("algorithm %q, signature format %q, expected compatible to be %t", c.algo, c.sigFormat, c.compatible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -254,3 +254,93 @@ func TestNewClientConn(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnsupportedAlgorithm(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
config Config
|
||||||
|
wantError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"unsupported KEX",
|
||||||
|
Config{
|
||||||
|
KeyExchanges: []string{"unsupported"},
|
||||||
|
},
|
||||||
|
"no common algorithm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported and supported KEXs",
|
||||||
|
Config{
|
||||||
|
KeyExchanges: []string{"unsupported", kexAlgoCurve25519SHA256},
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported cipher",
|
||||||
|
Config{
|
||||||
|
Ciphers: []string{"unsupported"},
|
||||||
|
},
|
||||||
|
"no common algorithm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported and supported ciphers",
|
||||||
|
Config{
|
||||||
|
Ciphers: []string{"unsupported", chacha20Poly1305ID},
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported MAC",
|
||||||
|
Config{
|
||||||
|
MACs: []string{"unsupported"},
|
||||||
|
// MAC is used for non AAED ciphers.
|
||||||
|
Ciphers: []string{"aes256-ctr"},
|
||||||
|
},
|
||||||
|
"no common algorithm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported and supported MACs",
|
||||||
|
Config{
|
||||||
|
MACs: []string{"unsupported", "hmac-sha2-256-etm@openssh.com"},
|
||||||
|
// MAC is used for non AAED ciphers.
|
||||||
|
Ciphers: []string{"aes256-ctr"},
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
Config: tt.config,
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
return &Permissions{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
clientConf := &ClientConfig{
|
||||||
|
User: "testuser",
|
||||||
|
Config: tt.config,
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
Password("testpw"),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||||
|
if err != nil {
|
||||||
|
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
|
||||||
|
t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
|
||||||
|
}
|
||||||
|
} else if tt.wantError != "" {
|
||||||
|
t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+38
-13
@@ -49,7 +49,8 @@ var supportedKexAlgos = []string{
|
|||||||
// P384 and P521 are not constant-time yet, but since we don't
|
// P384 and P521 are not constant-time yet, but since we don't
|
||||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||||
kexAlgoDH14SHA256, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
kexAlgoDH14SHA256, kexAlgoDH16SHA512, kexAlgoDH14SHA1,
|
||||||
|
kexAlgoDH1SHA1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden
|
// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden
|
||||||
@@ -59,8 +60,9 @@ var serverForbiddenKexAlgos = map[string]struct{}{
|
|||||||
kexAlgoDHGEXSHA256: {}, // server half implementation is only minimal to satisfy the automated tests
|
kexAlgoDHGEXSHA256: {}, // server half implementation is only minimal to satisfy the automated tests
|
||||||
}
|
}
|
||||||
|
|
||||||
// preferredKexAlgos specifies the default preference for key-exchange algorithms
|
// preferredKexAlgos specifies the default preference for key-exchange
|
||||||
// in preference order.
|
// algorithms in preference order. The diffie-hellman-group16-sha512 algorithm
|
||||||
|
// is disabled by default because it is a bit slower than the others.
|
||||||
var preferredKexAlgos = []string{
|
var preferredKexAlgos = []string{
|
||||||
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
|
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||||
@@ -70,12 +72,12 @@ var preferredKexAlgos = []string{
|
|||||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||||
// of authenticating servers) in preference order.
|
// of authenticating servers) in preference order.
|
||||||
var supportedHostKeyAlgos = []string{
|
var supportedHostKeyAlgos = []string{
|
||||||
CertAlgoRSASHA512v01, CertAlgoRSASHA256v01,
|
CertAlgoRSASHA256v01, CertAlgoRSASHA512v01,
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
||||||
|
|
||||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||||
KeyAlgoRSASHA512, KeyAlgoRSASHA256,
|
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
|
||||||
KeyAlgoRSA, KeyAlgoDSA,
|
KeyAlgoRSA, KeyAlgoDSA,
|
||||||
|
|
||||||
KeyAlgoED25519,
|
KeyAlgoED25519,
|
||||||
@@ -85,7 +87,7 @@ var supportedHostKeyAlgos = []string{
|
|||||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||||
// because they have reached the end of their useful life.
|
// because they have reached the end of their useful life.
|
||||||
var supportedMACs = []string{
|
var supportedMACs = []string{
|
||||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha1-96",
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedCompressions = []string{compressionNone}
|
var supportedCompressions = []string{compressionNone}
|
||||||
@@ -119,6 +121,13 @@ func algorithmsForKeyFormat(keyFormat string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isRSA returns whether algo is a supported RSA algorithm, including certificate
|
||||||
|
// algorithms.
|
||||||
|
func isRSA(algo string) bool {
|
||||||
|
algos := algorithmsForKeyFormat(KeyAlgoRSA)
|
||||||
|
return contains(algos, underlyingAlgo(algo))
|
||||||
|
}
|
||||||
|
|
||||||
// supportedPubKeyAuthAlgos specifies the supported client public key
|
// supportedPubKeyAuthAlgos specifies the supported client public key
|
||||||
// authentication algorithms. Note that this doesn't include certificate types
|
// authentication algorithms. Note that this doesn't include certificate types
|
||||||
// since those use the underlying algorithm. This list is sent to the client if
|
// since those use the underlying algorithm. This list is sent to the client if
|
||||||
@@ -262,16 +271,16 @@ type Config struct {
|
|||||||
// unspecified, a size suitable for the chosen cipher is used.
|
// unspecified, a size suitable for the chosen cipher is used.
|
||||||
RekeyThreshold uint64
|
RekeyThreshold uint64
|
||||||
|
|
||||||
// The allowed key exchanges algorithms. If unspecified then a
|
// The allowed key exchanges algorithms. If unspecified then a default set
|
||||||
// default set of algorithms is used.
|
// of algorithms is used. Unsupported values are silently ignored.
|
||||||
KeyExchanges []string
|
KeyExchanges []string
|
||||||
|
|
||||||
// The allowed cipher algorithms. If unspecified then a sensible
|
// The allowed cipher algorithms. If unspecified then a sensible default is
|
||||||
// default is used.
|
// used. Unsupported values are silently ignored.
|
||||||
Ciphers []string
|
Ciphers []string
|
||||||
|
|
||||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
// The allowed MAC algorithms. If unspecified then a sensible default is
|
||||||
// is used.
|
// used. Unsupported values are silently ignored.
|
||||||
MACs []string
|
MACs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +297,7 @@ func (c *Config) SetDefaults() {
|
|||||||
var ciphers []string
|
var ciphers []string
|
||||||
for _, c := range c.Ciphers {
|
for _, c := range c.Ciphers {
|
||||||
if cipherModes[c] != nil {
|
if cipherModes[c] != nil {
|
||||||
// reject the cipher if we have no cipherModes definition
|
// Ignore the cipher if we have no cipherModes definition.
|
||||||
ciphers = append(ciphers, c)
|
ciphers = append(ciphers, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,10 +306,26 @@ func (c *Config) SetDefaults() {
|
|||||||
if c.KeyExchanges == nil {
|
if c.KeyExchanges == nil {
|
||||||
c.KeyExchanges = preferredKexAlgos
|
c.KeyExchanges = preferredKexAlgos
|
||||||
}
|
}
|
||||||
|
var kexs []string
|
||||||
|
for _, k := range c.KeyExchanges {
|
||||||
|
if kexAlgoMap[k] != nil {
|
||||||
|
// Ignore the KEX if we have no kexAlgoMap definition.
|
||||||
|
kexs = append(kexs, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.KeyExchanges = kexs
|
||||||
|
|
||||||
if c.MACs == nil {
|
if c.MACs == nil {
|
||||||
c.MACs = supportedMACs
|
c.MACs = supportedMACs
|
||||||
}
|
}
|
||||||
|
var macs []string
|
||||||
|
for _, m := range c.MACs {
|
||||||
|
if macModes[m] != nil {
|
||||||
|
// Ignore the MAC if we have no macModes definition.
|
||||||
|
macs = append(macs, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.MACs = macs
|
||||||
|
|
||||||
if c.RekeyThreshold == 0 {
|
if c.RekeyThreshold == 0 {
|
||||||
// cipher specific default
|
// cipher specific default
|
||||||
|
|||||||
+5
-5
@@ -82,11 +82,11 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cases := []testcase{
|
cases := []testcase{
|
||||||
testcase{
|
{
|
||||||
name: "standard",
|
name: "standard",
|
||||||
},
|
},
|
||||||
|
|
||||||
testcase{
|
{
|
||||||
name: "no common hostkey",
|
name: "no common hostkey",
|
||||||
serverIn: kexInitMsg{
|
serverIn: kexInitMsg{
|
||||||
ServerHostKeyAlgos: []string{"hostkey2"},
|
ServerHostKeyAlgos: []string{"hostkey2"},
|
||||||
@@ -94,7 +94,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
testcase{
|
{
|
||||||
name: "no common kex",
|
name: "no common kex",
|
||||||
serverIn: kexInitMsg{
|
serverIn: kexInitMsg{
|
||||||
KexAlgos: []string{"kex2"},
|
KexAlgos: []string{"kex2"},
|
||||||
@@ -102,7 +102,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
testcase{
|
{
|
||||||
name: "no common cipher",
|
name: "no common cipher",
|
||||||
serverIn: kexInitMsg{
|
serverIn: kexInitMsg{
|
||||||
CiphersClientServer: []string{"cipher2"},
|
CiphersClientServer: []string{"cipher2"},
|
||||||
@@ -110,7 +110,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
testcase{
|
{
|
||||||
name: "client decides cipher",
|
name: "client decides cipher",
|
||||||
serverIn: kexInitMsg{
|
serverIn: kexInitMsg{
|
||||||
CiphersClientServer: []string{"cipher1", "cipher2"},
|
CiphersClientServer: []string{"cipher1", "cipher2"},
|
||||||
|
|||||||
+1
-1
@@ -97,7 +97,7 @@ func (c *connection) Close() error {
|
|||||||
return c.sshConn.conn.Close()
|
return c.sshConn.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// sshconn provides net.Conn metadata, but disallows direct reads and
|
// sshConn provides net.Conn metadata, but disallows direct reads and
|
||||||
// writes.
|
// writes.
|
||||||
type sshConn struct {
|
type sshConn struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
|||||||
+19
-17
@@ -148,6 +148,7 @@ func TestHandshakeBasic(t *testing.T) {
|
|||||||
clientDone := make(chan int, 0)
|
clientDone := make(chan int, 0)
|
||||||
gotHalf := make(chan int, 0)
|
gotHalf := make(chan int, 0)
|
||||||
const N = 20
|
const N = 20
|
||||||
|
errorCh := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(clientDone)
|
defer close(clientDone)
|
||||||
@@ -158,7 +159,9 @@ func TestHandshakeBasic(t *testing.T) {
|
|||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
p := []byte{msgRequestSuccess, byte(i)}
|
p := []byte{msgRequestSuccess, byte(i)}
|
||||||
if err := trC.writePacket(p); err != nil {
|
if err := trC.writePacket(p); err != nil {
|
||||||
t.Fatalf("sendPacket: %v", err)
|
errorCh <- err
|
||||||
|
trC.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (i % 10) == 5 {
|
if (i % 10) == 5 {
|
||||||
<-gotHalf
|
<-gotHalf
|
||||||
@@ -177,16 +180,15 @@ func TestHandshakeBasic(t *testing.T) {
|
|||||||
checker.waitCall <- 1
|
checker.waitCall <- 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
errorCh <- nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Server checks that client messages come in cleanly
|
// Server checks that client messages come in cleanly
|
||||||
i := 0
|
i := 0
|
||||||
err = nil
|
|
||||||
for ; i < N; i++ {
|
for ; i < N; i++ {
|
||||||
var p []byte
|
p, err := trS.readPacket()
|
||||||
p, err = trS.readPacket()
|
if err != nil && err != io.EOF {
|
||||||
if err != nil {
|
t.Fatalf("server error: %v", err)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if (i % 10) == 5 {
|
if (i % 10) == 5 {
|
||||||
gotHalf <- 1
|
gotHalf <- 1
|
||||||
@@ -198,8 +200,8 @@ func TestHandshakeBasic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
<-clientDone
|
<-clientDone
|
||||||
if err != nil && err != io.EOF {
|
if err := <-errorCh; err != nil {
|
||||||
t.Fatalf("server error: %v", err)
|
t.Fatalf("sendPacket: %v", err)
|
||||||
}
|
}
|
||||||
if i != N {
|
if i != N {
|
||||||
t.Errorf("received %d messages, want 10.", i)
|
t.Errorf("received %d messages, want 10.", i)
|
||||||
@@ -345,16 +347,16 @@ func TestHandshakeAutoRekeyRead(t *testing.T) {
|
|||||||
|
|
||||||
// While we read out the packet, a key change will be
|
// While we read out the packet, a key change will be
|
||||||
// initiated.
|
// initiated.
|
||||||
done := make(chan int, 1)
|
errorCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
_, err := trC.readPacket()
|
||||||
if _, err := trC.readPacket(); err != nil {
|
errorCh <- err
|
||||||
t.Fatalf("readPacket(client): %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-done
|
if err := <-errorCh; err != nil {
|
||||||
|
t.Fatalf("readPacket(client): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
<-sync.called
|
<-sync.called
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,8 +423,8 @@ func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
|||||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||||
// panic.
|
// panic.
|
||||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
||||||
if runtime.GOOS == "js" && runtime.GOARCH == "wasm" {
|
if (runtime.GOOS == "js" || runtime.GOOS == "wasip1") && runtime.GOARCH == "wasm" {
|
||||||
t.Skip("skipping on js/wasm; see golang.org/issue/32840")
|
t.Skipf("skipping on %s/wasm; see golang.org/issue/32840", runtime.GOOS)
|
||||||
}
|
}
|
||||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||||
|
|
||||||
|
|||||||
+12
@@ -23,6 +23,7 @@ const (
|
|||||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||||
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
|
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
|
||||||
|
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
|
||||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||||
@@ -430,6 +431,17 @@ func init() {
|
|||||||
hashFunc: crypto.SHA256,
|
hashFunc: crypto.SHA256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the group called diffie-hellman-group16-sha512 in RFC
|
||||||
|
// 8268 and Oakley Group 16 in RFC 3526.
|
||||||
|
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", 16)
|
||||||
|
|
||||||
|
kexAlgoMap[kexAlgoDH16SHA512] = &dhGroup{
|
||||||
|
g: new(big.Int).SetInt64(2),
|
||||||
|
p: p,
|
||||||
|
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||||
|
hashFunc: crypto.SHA512,
|
||||||
|
}
|
||||||
|
|
||||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -63,3 +64,43 @@ func TestKexes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkKexes(b *testing.B) {
|
||||||
|
type kexResultErr struct {
|
||||||
|
result *kexResult
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, kex := range kexAlgoMap {
|
||||||
|
b.Run(name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
t1, t2 := memPipe()
|
||||||
|
|
||||||
|
s := make(chan kexResultErr, 1)
|
||||||
|
c := make(chan kexResultErr, 1)
|
||||||
|
var magics handshakeMagics
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
r, e := kex.Client(t1, rand.Reader, &magics)
|
||||||
|
t1.Close()
|
||||||
|
c <- kexResultErr{r, e}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
r, e := kex.Server(t2, rand.Reader, &magics, testSigners["ecdsa"].(AlgorithmSigner), testSigners["ecdsa"].PublicKey().Type())
|
||||||
|
t2.Close()
|
||||||
|
s <- kexResultErr{r, e}
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientRes := <-c
|
||||||
|
serverRes := <-s
|
||||||
|
|
||||||
|
if clientRes.err != nil {
|
||||||
|
panic(fmt.Sprintf("client: %v", clientRes.err))
|
||||||
|
}
|
||||||
|
if serverRes.err != nil {
|
||||||
|
panic(fmt.Sprintf("server: %v", serverRes.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -1087,9 +1087,9 @@ func (*PassphraseMissingError) Error() string {
|
|||||||
return "ssh: this private key is passphrase protected"
|
return "ssh: this private key is passphrase protected"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It supports
|
||||||
// supports RSA (PKCS#1), PKCS#8, DSA (OpenSSL), and ECDSA private keys. If the
|
// RSA, DSA, ECDSA, and Ed25519 private keys in PKCS#1, PKCS#8, OpenSSL, and OpenSSH
|
||||||
// private key is encrypted, it will return a PassphraseMissingError.
|
// formats. If the private key is encrypted, it will return a PassphraseMissingError.
|
||||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||||
block, _ := pem.Decode(pemBytes)
|
block, _ := pem.Decode(pemBytes)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"hash"
|
"hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,9 +47,15 @@ func (t truncatingMAC) Size() int {
|
|||||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||||
|
|
||||||
var macModes = map[string]*macMode{
|
var macModes = map[string]*macMode{
|
||||||
|
"hmac-sha2-512-etm@openssh.com": {64, true, func(key []byte) hash.Hash {
|
||||||
|
return hmac.New(sha512.New, key)
|
||||||
|
}},
|
||||||
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
|
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha256.New, key)
|
return hmac.New(sha256.New, key)
|
||||||
}},
|
}},
|
||||||
|
"hmac-sha2-512": {64, false, func(key []byte) hash.Hash {
|
||||||
|
return hmac.New(sha512.New, key)
|
||||||
|
}},
|
||||||
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
|
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha256.New, key)
|
return hmac.New(sha256.New, key)
|
||||||
}},
|
}},
|
||||||
|
|||||||
+85
-38
@@ -5,6 +5,8 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -29,14 +31,21 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
|||||||
go func() {
|
go func() {
|
||||||
newCh, ok := <-s.incomingChannels
|
newCh, ok := <-s.incomingChannels
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("No incoming channel")
|
t.Error("no incoming channel")
|
||||||
|
close(res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if newCh.ChannelType() != "chan" {
|
if newCh.ChannelType() != "chan" {
|
||||||
t.Fatalf("got type %q want chan", newCh.ChannelType())
|
t.Errorf("got type %q want chan", newCh.ChannelType())
|
||||||
|
newCh.Reject(Prohibited, fmt.Sprintf("got type %q want chan", newCh.ChannelType()))
|
||||||
|
close(res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ch, _, err := newCh.Accept()
|
ch, _, err := newCh.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Accept %v", err)
|
t.Errorf("accept: %v", err)
|
||||||
|
close(res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
res <- ch.(*channel)
|
res <- ch.(*channel)
|
||||||
}()
|
}()
|
||||||
@@ -45,8 +54,12 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("OpenChannel: %v", err)
|
t.Fatalf("OpenChannel: %v", err)
|
||||||
}
|
}
|
||||||
|
w := <-res
|
||||||
|
if w == nil {
|
||||||
|
t.Fatal("unable to get write channel")
|
||||||
|
}
|
||||||
|
|
||||||
return <-res, ch, c
|
return w, ch, c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that stderr and stdout can be addressed from different
|
// Test that stderr and stdout can be addressed from different
|
||||||
@@ -74,14 +87,14 @@ func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
c, err := io.ReadAll(reader)
|
c, err := io.ReadAll(reader)
|
||||||
if string(c) != magic {
|
if string(c) != magic {
|
||||||
t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err)
|
t.Errorf("stdout read got %q, want %q (error %s)", c, magic, err)
|
||||||
}
|
}
|
||||||
rd.Done()
|
rd.Done()
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
c, err := io.ReadAll(reader.Stderr())
|
c, err := io.ReadAll(reader.Stderr())
|
||||||
if string(c) != magic {
|
if string(c) != magic {
|
||||||
t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err)
|
t.Errorf("stderr read got %q, want %q (error %s)", c, magic, err)
|
||||||
}
|
}
|
||||||
rd.Done()
|
rd.Done()
|
||||||
}()
|
}()
|
||||||
@@ -102,11 +115,13 @@ func TestMuxReadWrite(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
_, err := s.Write([]byte(magic))
|
_, err := s.Write([]byte(magic))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Write: %v", err)
|
t.Errorf("Write: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
_, err = s.Extended(1).Write([]byte(magicExt))
|
_, err = s.Extended(1).Write([]byte(magicExt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Write: %v", err)
|
t.Errorf("Write: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -215,10 +230,13 @@ func TestMuxReject(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
ch, ok := <-server.incomingChannels
|
ch, ok := <-server.incomingChannels
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Accept")
|
t.Error("cannot accept channel")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
||||||
t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
t.Errorf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
||||||
|
ch.Reject(RejectionReason(UnknownChannelType), UnknownChannelType.String())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ch.Reject(RejectionReason(42), "message")
|
ch.Reject(RejectionReason(42), "message")
|
||||||
}()
|
}()
|
||||||
@@ -294,7 +312,7 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
|||||||
defer serverPipe.Close()
|
defer serverPipe.Close()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
kDone := make(chan struct{})
|
kDone := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
// Ignore unknown channel messages that don't want a reply.
|
// Ignore unknown channel messages that don't want a reply.
|
||||||
err := serverPipe.writePacket(Marshal(channelRequestMsg{
|
err := serverPipe.writePacket(Marshal(channelRequestMsg{
|
||||||
@@ -304,7 +322,8 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
|||||||
RequestSpecificData: []byte{},
|
RequestSpecificData: []byte{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("send: %v", err)
|
kDone <- fmt.Errorf("send: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a keepalive, which should get a channel failure message
|
// Send a keepalive, which should get a channel failure message
|
||||||
@@ -316,44 +335,53 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
|||||||
RequestSpecificData: []byte{},
|
RequestSpecificData: []byte{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("send: %v", err)
|
kDone <- fmt.Errorf("send: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
packet, err := serverPipe.readPacket()
|
packet, err := serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
decoded, err := decode(packet)
|
decoded, err := decode(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("decode failed: %v", err)
|
kDone <- fmt.Errorf("decode failed: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := decoded.(type) {
|
switch msg := decoded.(type) {
|
||||||
case *channelRequestFailureMsg:
|
case *channelRequestFailureMsg:
|
||||||
if msg.PeersID != 2 {
|
if msg.PeersID != 2 {
|
||||||
t.Fatalf("received response to wrong message: %v", msg)
|
kDone <- fmt.Errorf("received response to wrong message: %v", msg)
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected channel message: %v", msg)
|
kDone <- fmt.Errorf("unexpected channel message: %v", msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kDone <- struct{}{}
|
kDone <- nil
|
||||||
|
|
||||||
// Receive and respond to the keepalive to confirm the mux is
|
// Receive and respond to the keepalive to confirm the mux is
|
||||||
// still processing requests.
|
// still processing requests.
|
||||||
packet, err = serverPipe.readPacket()
|
packet, err = serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if packet[0] != msgGlobalRequest {
|
if packet[0] != msgGlobalRequest {
|
||||||
t.Fatalf("expected global request")
|
kDone <- errors.New("expected global request")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||||
Data: []byte{},
|
Data: []byte{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to send failure msg: %v", err)
|
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
close(kDone)
|
close(kDone)
|
||||||
@@ -362,7 +390,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
|||||||
// Wait for the server to send the keepalive message and receive back a
|
// Wait for the server to send the keepalive message and receive back a
|
||||||
// response.
|
// response.
|
||||||
select {
|
select {
|
||||||
case <-kDone:
|
case err := <-kDone:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
t.Fatalf("server never received ack")
|
t.Fatalf("server never received ack")
|
||||||
}
|
}
|
||||||
@@ -373,7 +404,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-kDone:
|
case err := <-kDone:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
t.Fatalf("server never shut down")
|
t.Fatalf("server never shut down")
|
||||||
}
|
}
|
||||||
@@ -385,20 +419,23 @@ func TestMuxClosedChannel(t *testing.T) {
|
|||||||
defer serverPipe.Close()
|
defer serverPipe.Close()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
kDone := make(chan struct{})
|
kDone := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
// Open the channel.
|
// Open the channel.
|
||||||
packet, err := serverPipe.readPacket()
|
packet, err := serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if packet[0] != msgChannelOpen {
|
if packet[0] != msgChannelOpen {
|
||||||
t.Fatalf("expected chan open")
|
kDone <- errors.New("expected chan open")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var openMsg channelOpenMsg
|
var openMsg channelOpenMsg
|
||||||
if err := Unmarshal(packet, &openMsg); err != nil {
|
if err := Unmarshal(packet, &openMsg); err != nil {
|
||||||
t.Fatalf("unmarshal: %v", err)
|
kDone <- fmt.Errorf("unmarshal: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send back the opened channel confirmation.
|
// Send back the opened channel confirmation.
|
||||||
@@ -409,7 +446,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
|||||||
MaxPacketSize: channelMaxPacket,
|
MaxPacketSize: channelMaxPacket,
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("send: %v", err)
|
kDone <- fmt.Errorf("send: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the channel.
|
// Close the channel.
|
||||||
@@ -417,7 +455,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
|||||||
PeersID: openMsg.PeersID,
|
PeersID: openMsg.PeersID,
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("send: %v", err)
|
kDone <- fmt.Errorf("send: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a keepalive message on the channel we just closed.
|
// Send a keepalive message on the channel we just closed.
|
||||||
@@ -428,43 +467,51 @@ func TestMuxClosedChannel(t *testing.T) {
|
|||||||
RequestSpecificData: []byte{},
|
RequestSpecificData: []byte{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("send: %v", err)
|
kDone <- fmt.Errorf("send: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive the channel closed response.
|
// Receive the channel closed response.
|
||||||
packet, err = serverPipe.readPacket()
|
packet, err = serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if packet[0] != msgChannelClose {
|
if packet[0] != msgChannelClose {
|
||||||
t.Fatalf("expected channel close")
|
kDone <- errors.New("expected channel close")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive the keepalive response failure.
|
// Receive the keepalive response failure.
|
||||||
packet, err = serverPipe.readPacket()
|
packet, err = serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if packet[0] != msgChannelFailure {
|
if packet[0] != msgChannelFailure {
|
||||||
t.Fatalf("expected channel close")
|
kDone <- errors.New("expected channel failure")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
kDone <- struct{}{}
|
kDone <- nil
|
||||||
|
|
||||||
// Receive and respond to the keepalive to confirm the mux is
|
// Receive and respond to the keepalive to confirm the mux is
|
||||||
// still processing requests.
|
// still processing requests.
|
||||||
packet, err = serverPipe.readPacket()
|
packet, err = serverPipe.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read packet: %v", err)
|
kDone <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if packet[0] != msgGlobalRequest {
|
if packet[0] != msgGlobalRequest {
|
||||||
t.Fatalf("expected global request")
|
kDone <- errors.New("expected global request")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||||
Data: []byte{},
|
Data: []byte{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to send failure msg: %v", err)
|
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
close(kDone)
|
close(kDone)
|
||||||
|
|||||||
+20
-1
@@ -370,6 +370,25 @@ func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *c
|
|||||||
return authErr, perms, nil
|
return authErr, perms, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isAlgoCompatible checks if the signature format is compatible with the
|
||||||
|
// selected algorithm taking into account edge cases that occur with old
|
||||||
|
// clients.
|
||||||
|
func isAlgoCompatible(algo, sigFormat string) bool {
|
||||||
|
// Compatibility for old clients.
|
||||||
|
//
|
||||||
|
// For certificate authentication with OpenSSH 7.2-7.7 signature format can
|
||||||
|
// be rsa-sha2-256 or rsa-sha2-512 for the algorithm
|
||||||
|
// ssh-rsa-cert-v01@openssh.com.
|
||||||
|
//
|
||||||
|
// With gpg-agent < 2.2.6 the algorithm can be rsa-sha2-256 or rsa-sha2-512
|
||||||
|
// for signature format ssh-rsa.
|
||||||
|
if isRSA(algo) && isRSA(sigFormat) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Standard case: the underlying algorithm must match the signature format.
|
||||||
|
return underlyingAlgo(algo) == sigFormat
|
||||||
|
}
|
||||||
|
|
||||||
// ServerAuthError represents server authentication errors and is
|
// ServerAuthError represents server authentication errors and is
|
||||||
// sometimes returned by NewServerConn. It appends any authentication
|
// sometimes returned by NewServerConn. It appends any authentication
|
||||||
// errors that may occur, and is returned if all of the authentication
|
// errors that may occur, and is returned if all of the authentication
|
||||||
@@ -567,7 +586,7 @@ userAuthLoop:
|
|||||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if underlyingAlgo(algo) != sig.Format {
|
if !isAlgoCompatible(algo, sig.Format) {
|
||||||
authErr = fmt.Errorf("ssh: signature %q not compatible with selected algorithm %q", sig.Format, algo)
|
authErr = fmt.Errorf("ssh: signature %q not compatible with selected algorithm %q", sig.Format, algo)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-3
@@ -36,7 +36,8 @@ func dial(handler serverType, t *testing.T) *Client {
|
|||||||
|
|
||||||
conn, chans, reqs, err := NewServerConn(c1, &conf)
|
conn, chans, reqs, err := NewServerConn(c1, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to handshake: %v", err)
|
t.Errorf("Unable to handshake: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
go DiscardRequests(reqs)
|
go DiscardRequests(reqs)
|
||||||
|
|
||||||
@@ -647,10 +648,12 @@ func TestSessionID(t *testing.T) {
|
|||||||
User: "user",
|
User: "user",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srvErrCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
conn, chans, reqs, err := NewServerConn(c1, serverConf)
|
conn, chans, reqs, err := NewServerConn(c1, serverConf)
|
||||||
|
srvErrCh <- err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("server handshake: %v", err)
|
return
|
||||||
}
|
}
|
||||||
serverID <- conn.SessionID()
|
serverID <- conn.SessionID()
|
||||||
go DiscardRequests(reqs)
|
go DiscardRequests(reqs)
|
||||||
@@ -659,10 +662,12 @@ func TestSessionID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
cliErrCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
|
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
|
||||||
|
cliErrCh <- err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("client handshake: %v", err)
|
return
|
||||||
}
|
}
|
||||||
clientID <- conn.SessionID()
|
clientID <- conn.SessionID()
|
||||||
go DiscardRequests(reqs)
|
go DiscardRequests(reqs)
|
||||||
@@ -671,6 +676,14 @@ func TestSessionID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if err := <-srvErrCh; err != nil {
|
||||||
|
t.Fatalf("server handshake: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := <-cliErrCh; err != nil {
|
||||||
|
t.Fatalf("client handshake: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
s := <-serverID
|
s := <-serverID
|
||||||
c := <-clientID
|
c := <-clientID
|
||||||
if bytes.Compare(s, c) != 0 {
|
if bytes.Compare(s, c) != 0 {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
|
|
||||||
func TestAgentForward(t *testing.T) {
|
func TestAgentForward(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
func TestBannerCallbackAgainstOpenSSH(t *testing.T) {
|
func TestBannerCallbackAgainstOpenSSH(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
|
|
||||||
clientConf := clientConfig()
|
clientConf := clientConfig()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
|
// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
|
||||||
func TestCertLogin(t *testing.T) {
|
func TestCertLogin(t *testing.T) {
|
||||||
s := newServer(t)
|
s := newServer(t)
|
||||||
defer s.Shutdown()
|
|
||||||
|
|
||||||
// Use a key different from the default.
|
// Use a key different from the default.
|
||||||
clientKey := testSigners["dsa"]
|
clientKey := testSigners["dsa"]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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 !windows && !solaris && !js
|
//go:build !windows && !js && !wasip1
|
||||||
// +build !windows,!solaris,!js
|
// +build !windows,!js,!wasip1
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ type dialTester interface {
|
|||||||
|
|
||||||
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
sshConn := server.Dial(clientConfig())
|
sshConn := server.Dial(clientConfig())
|
||||||
defer sshConn.Close()
|
defer sshConn.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ type closeWriter interface {
|
|||||||
|
|
||||||
func testPortForward(t *testing.T, n, listenAddr string) {
|
func testPortForward(t *testing.T, n, listenAddr string) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -120,7 +119,6 @@ func TestPortForwardUnix(t *testing.T) {
|
|||||||
|
|
||||||
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
|
|
||||||
sshListener, err := conn.Listen(n, listenAddr)
|
sshListener, err := conn.Listen(n, listenAddr)
|
||||||
@@ -162,10 +160,9 @@ func TestAcceptCloseUnix(t *testing.T) {
|
|||||||
// Check that listeners exit if the underlying client transport dies.
|
// Check that listeners exit if the underlying client transport dies.
|
||||||
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
client := server.Dial(clientConfig())
|
||||||
conn := server.Dial(clientConfig())
|
|
||||||
|
|
||||||
sshListener, err := conn.Listen(n, listenAddr)
|
sshListener, err := client.Listen(n, listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -184,14 +181,10 @@ func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
|||||||
|
|
||||||
// It would be even nicer if we closed the server side, but it
|
// It would be even nicer if we closed the server side, but it
|
||||||
// is more involved as the fd for that side is dup()ed.
|
// is more involved as the fd for that side is dup()ed.
|
||||||
server.clientConn.Close()
|
server.lastDialConn.Close()
|
||||||
|
|
||||||
select {
|
err = <-quit
|
||||||
case <-time.After(1 * time.Second):
|
t.Logf("quit as expected (error %v)", err)
|
||||||
t.Errorf("timeout: listener did not close.")
|
|
||||||
case err := <-quit:
|
|
||||||
t.Logf("quit as expected (error %v)", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
||||||
|
|||||||
@@ -77,27 +77,27 @@ func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []stri
|
|||||||
func TestMultiAuth(t *testing.T) {
|
func TestMultiAuth(t *testing.T) {
|
||||||
testCases := []multiAuthTestCase{
|
testCases := []multiAuthTestCase{
|
||||||
// Test password,publickey authentication, assert that password callback is called 1 time
|
// Test password,publickey authentication, assert that password callback is called 1 time
|
||||||
multiAuthTestCase{
|
{
|
||||||
authMethods: []string{"password", "publickey"},
|
authMethods: []string{"password", "publickey"},
|
||||||
expectedPasswordCbs: 1,
|
expectedPasswordCbs: 1,
|
||||||
},
|
},
|
||||||
// Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
|
// Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
|
||||||
multiAuthTestCase{
|
{
|
||||||
authMethods: []string{"keyboard-interactive", "publickey"},
|
authMethods: []string{"keyboard-interactive", "publickey"},
|
||||||
expectedKbdIntCbs: 1,
|
expectedKbdIntCbs: 1,
|
||||||
},
|
},
|
||||||
// Test publickey,password authentication, assert that password callback is called 1 time
|
// Test publickey,password authentication, assert that password callback is called 1 time
|
||||||
multiAuthTestCase{
|
{
|
||||||
authMethods: []string{"publickey", "password"},
|
authMethods: []string{"publickey", "password"},
|
||||||
expectedPasswordCbs: 1,
|
expectedPasswordCbs: 1,
|
||||||
},
|
},
|
||||||
// Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
|
// Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
|
||||||
multiAuthTestCase{
|
{
|
||||||
authMethods: []string{"publickey", "keyboard-interactive"},
|
authMethods: []string{"publickey", "keyboard-interactive"},
|
||||||
expectedKbdIntCbs: 1,
|
expectedKbdIntCbs: 1,
|
||||||
},
|
},
|
||||||
// Test password,password authentication, assert that password callback is called 2 times
|
// Test password,password authentication, assert that password callback is called 2 times
|
||||||
multiAuthTestCase{
|
{
|
||||||
authMethods: []string{"password", "password"},
|
authMethods: []string{"password", "password"},
|
||||||
expectedPasswordCbs: 2,
|
expectedPasswordCbs: 2,
|
||||||
},
|
},
|
||||||
@@ -108,7 +108,6 @@ func TestMultiAuth(t *testing.T) {
|
|||||||
ctx := newMultiAuthTestCtx(t)
|
ctx := newMultiAuthTestCtx(t)
|
||||||
|
|
||||||
server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
|
server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
|
||||||
defer server.Shutdown()
|
|
||||||
|
|
||||||
clientConfig := clientConfig()
|
clientConfig := clientConfig()
|
||||||
server.setTestPassword(clientConfig.User, ctx.password)
|
server.setTestPassword(clientConfig.User, ctx.password)
|
||||||
|
|||||||
+27
-21
@@ -2,8 +2,8 @@
|
|||||||
// 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 !windows && !solaris && !js
|
//go:build !windows && !js && !wasip1
|
||||||
// +build !windows,!solaris,!js
|
// +build !windows,!js,!wasip1
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -23,7 +25,6 @@ import (
|
|||||||
|
|
||||||
func TestRunCommandSuccess(t *testing.T) {
|
func TestRunCommandSuccess(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -40,7 +41,6 @@ func TestRunCommandSuccess(t *testing.T) {
|
|||||||
|
|
||||||
func TestHostKeyCheck(t *testing.T) {
|
func TestHostKeyCheck(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
|
|
||||||
conf := clientConfig()
|
conf := clientConfig()
|
||||||
hostDB := hostKeyDB()
|
hostDB := hostKeyDB()
|
||||||
@@ -62,7 +62,6 @@ func TestHostKeyCheck(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunCommandStdin(t *testing.T) {
|
func TestRunCommandStdin(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -85,7 +84,6 @@ func TestRunCommandStdin(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunCommandStdinError(t *testing.T) {
|
func TestRunCommandStdinError(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -109,7 +107,6 @@ func TestRunCommandStdinError(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunCommandFailed(t *testing.T) {
|
func TestRunCommandFailed(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -126,7 +123,6 @@ func TestRunCommandFailed(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunCommandWeClosed(t *testing.T) {
|
func TestRunCommandWeClosed(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -146,7 +142,6 @@ func TestRunCommandWeClosed(t *testing.T) {
|
|||||||
|
|
||||||
func TestFuncLargeRead(t *testing.T) {
|
func TestFuncLargeRead(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -178,7 +173,6 @@ func TestFuncLargeRead(t *testing.T) {
|
|||||||
|
|
||||||
func TestKeyChange(t *testing.T) {
|
func TestKeyChange(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conf := clientConfig()
|
conf := clientConfig()
|
||||||
hostDB := hostKeyDB()
|
hostDB := hostKeyDB()
|
||||||
conf.HostKeyCallback = hostDB.Check
|
conf.HostKeyCallback = hostDB.Check
|
||||||
@@ -225,7 +219,6 @@ func TestValidTerminalMode(t *testing.T) {
|
|||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -255,15 +248,31 @@ func TestValidTerminalMode(t *testing.T) {
|
|||||||
t.Fatalf("session failed: %s", err)
|
t.Fatalf("session failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin.Write([]byte("stty -a && exit\n"))
|
if _, err := io.WriteString(stdin, "echo && echo SHELL $SHELL && stty -a && exit\n"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
buf := new(strings.Builder)
|
||||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
if _, err := io.Copy(buf, stdout); err != nil {
|
||||||
t.Fatalf("reading failed: %s", err)
|
t.Fatalf("reading failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testing.Verbose() {
|
||||||
|
t.Logf("echo && echo SHELL $SHELL && stty -a && exit:\n%s", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
shellLine := regexp.MustCompile("(?m)^SHELL (.*)$").FindStringSubmatch(buf.String())
|
||||||
|
if len(shellLine) != 2 {
|
||||||
|
t.Fatalf("missing output from echo SHELL $SHELL")
|
||||||
|
}
|
||||||
|
switch shell := filepath.Base(strings.TrimSpace(shellLine[1])); shell {
|
||||||
|
case "sh", "bash":
|
||||||
|
default:
|
||||||
|
t.Skipf("skipping test on non-Bourne shell %q", shell)
|
||||||
|
}
|
||||||
|
|
||||||
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
|
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
|
||||||
t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput)
|
t.Fatal("terminal mode failure: expected -echo in stty output")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +283,6 @@ func TestWindowChange(t *testing.T) {
|
|||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conn := server.Dial(clientConfig())
|
conn := server.Dial(clientConfig())
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -322,7 +330,6 @@ func TestWindowChange(t *testing.T) {
|
|||||||
|
|
||||||
func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conf := clientConfig()
|
conf := clientConfig()
|
||||||
conf.Ciphers = []string{cipher}
|
conf.Ciphers = []string{cipher}
|
||||||
// Don't fail if sshd doesn't have the cipher.
|
// Don't fail if sshd doesn't have the cipher.
|
||||||
@@ -381,7 +388,6 @@ func TestMACs(t *testing.T) {
|
|||||||
for _, mac := range macOrder {
|
for _, mac := range macOrder {
|
||||||
t.Run(mac, func(t *testing.T) {
|
t.Run(mac, func(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conf := clientConfig()
|
conf := clientConfig()
|
||||||
conf.MACs = []string{mac}
|
conf.MACs = []string{mac}
|
||||||
// Don't fail if sshd doesn't have the MAC.
|
// Don't fail if sshd doesn't have the MAC.
|
||||||
@@ -404,10 +410,12 @@ func TestKeyExchanges(t *testing.T) {
|
|||||||
// are not included in the default list of supported kex so we have to add them
|
// are not included in the default list of supported kex so we have to add them
|
||||||
// here manually.
|
// here manually.
|
||||||
kexOrder = append(kexOrder, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
|
kexOrder = append(kexOrder, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
|
||||||
|
// The key exchange algorithms diffie-hellman-group16-sha512 is disabled by
|
||||||
|
// default so we add it here manually.
|
||||||
|
kexOrder = append(kexOrder, "diffie-hellman-group16-sha512")
|
||||||
for _, kex := range kexOrder {
|
for _, kex := range kexOrder {
|
||||||
t.Run(kex, func(t *testing.T) {
|
t.Run(kex, func(t *testing.T) {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
defer server.Shutdown()
|
|
||||||
conf := clientConfig()
|
conf := clientConfig()
|
||||||
// Don't fail if sshd doesn't have the kex.
|
// Don't fail if sshd doesn't have the kex.
|
||||||
conf.KeyExchanges = append([]string{kex}, kexOrder...)
|
conf.KeyExchanges = append([]string{kex}, kexOrder...)
|
||||||
@@ -442,8 +450,6 @@ func TestClientAuthAlgorithms(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
t.Errorf("failed for key %q", key)
|
t.Errorf("failed for key %q", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Shutdown()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-39
@@ -2,8 +2,8 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd plan9
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd plan9 solaris
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/internal/testenv"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/testdata"
|
"golang.org/x/crypto/ssh/testdata"
|
||||||
)
|
)
|
||||||
@@ -67,17 +68,13 @@ var configTmpl = map[string]*template.Template{
|
|||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
cleanup func() // executed during Shutdown
|
|
||||||
configfile string
|
configfile string
|
||||||
cmd *exec.Cmd
|
|
||||||
output bytes.Buffer // holds stderr from sshd process
|
|
||||||
|
|
||||||
testUser string // test username for sshd
|
testUser string // test username for sshd
|
||||||
testPasswd string // test password for sshd
|
testPasswd string // test password for sshd
|
||||||
sshdTestPwSo string // dynamic library to inject a custom password into sshd
|
sshdTestPwSo string // dynamic library to inject a custom password into sshd
|
||||||
|
|
||||||
// Client half of the network connection.
|
lastDialConn net.Conn
|
||||||
clientConn net.Conn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func username() string {
|
func username() string {
|
||||||
@@ -193,15 +190,15 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
|||||||
s.t.Fatalf("unixConnection: %v", err)
|
s.t.Fatalf("unixConnection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
|
cmd := testenv.Command(s.t, sshd, "-f", s.configfile, "-i", "-e")
|
||||||
f, err := c2.File()
|
f, err := c2.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.t.Fatalf("UnixConn.File: %v", err)
|
s.t.Fatalf("UnixConn.File: %v", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
s.cmd.Stdin = f
|
cmd.Stdin = f
|
||||||
s.cmd.Stdout = f
|
cmd.Stdout = f
|
||||||
s.cmd.Stderr = &s.output
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
|
|
||||||
if s.sshdTestPwSo != "" {
|
if s.sshdTestPwSo != "" {
|
||||||
if s.testUser == "" {
|
if s.testUser == "" {
|
||||||
@@ -210,18 +207,29 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
|||||||
if s.testPasswd == "" {
|
if s.testPasswd == "" {
|
||||||
s.t.Fatal("password missing from sshd_test_pw.so config")
|
s.t.Fatal("password missing from sshd_test_pw.so config")
|
||||||
}
|
}
|
||||||
s.cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
|
fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
|
||||||
fmt.Sprintf("TEST_USER=%s", s.testUser),
|
fmt.Sprintf("TEST_USER=%s", s.testUser),
|
||||||
fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
|
fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
s.t.Fail()
|
|
||||||
s.Shutdown()
|
|
||||||
s.t.Fatalf("s.cmd.Start: %v", err)
|
s.t.Fatalf("s.cmd.Start: %v", err)
|
||||||
}
|
}
|
||||||
s.clientConn = c1
|
s.lastDialConn = c1
|
||||||
|
s.t.Cleanup(func() {
|
||||||
|
// Don't check for errors; if it fails it's most
|
||||||
|
// likely "os: process already finished", and we don't
|
||||||
|
// care about that. Use os.Interrupt, so child
|
||||||
|
// processes are killed too.
|
||||||
|
cmd.Process.Signal(os.Interrupt)
|
||||||
|
cmd.Wait()
|
||||||
|
if s.t.Failed() {
|
||||||
|
// log any output from sshd process
|
||||||
|
s.t.Logf("sshd:\n%s", cmd.Stderr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
|
conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -232,29 +240,11 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
|||||||
func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
|
func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
|
||||||
conn, err := s.TryDial(config)
|
conn, err := s.TryDial(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.t.Fail()
|
|
||||||
s.Shutdown()
|
|
||||||
s.t.Fatalf("ssh.Client: %v", err)
|
s.t.Fatalf("ssh.Client: %v", err)
|
||||||
}
|
}
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) Shutdown() {
|
|
||||||
if s.cmd != nil && s.cmd.Process != nil {
|
|
||||||
// Don't check for errors; if it fails it's most
|
|
||||||
// likely "os: process already finished", and we don't
|
|
||||||
// care about that. Use os.Interrupt, so child
|
|
||||||
// processes are killed too.
|
|
||||||
s.cmd.Process.Signal(os.Interrupt)
|
|
||||||
s.cmd.Wait()
|
|
||||||
}
|
|
||||||
if s.t.Failed() {
|
|
||||||
// log any output from sshd process
|
|
||||||
s.t.Logf("sshd: %s", s.output.String())
|
|
||||||
}
|
|
||||||
s.cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFile(path string, contents []byte) {
|
func writeFile(path string, contents []byte) {
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -351,15 +341,15 @@ func newServerForConfig(t *testing.T, config string, configVars map[string]strin
|
|||||||
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||||
}
|
}
|
||||||
writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
|
writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return &server{
|
return &server{
|
||||||
t: t,
|
t: t,
|
||||||
configfile: f.Name(),
|
configfile: f.Name(),
|
||||||
cleanup: func() {
|
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 go1.20
|
||||||
|
|
||||||
|
// Package fallback embeds a set of fallback X.509 trusted roots in the
|
||||||
|
// application by automatically invoking [x509.SetFallbackRoots]. This allows
|
||||||
|
// the application to work correctly even if the operating system does not
|
||||||
|
// provide a verifier or system roots pool.
|
||||||
|
//
|
||||||
|
// To use it, import the package like
|
||||||
|
//
|
||||||
|
// import _ "golang.org/x/crypto/x509roots/fallback"
|
||||||
|
//
|
||||||
|
// It's recommended that only binaries, and not libraries, import this package.
|
||||||
|
//
|
||||||
|
// This package must be kept up to date for security and compatibility reasons.
|
||||||
|
// Use govulncheck to be notified of when new versions of the package are
|
||||||
|
// available.
|
||||||
|
package fallback
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
p := x509.NewCertPool()
|
||||||
|
for _, c := range bundle {
|
||||||
|
p.AddCert(c)
|
||||||
|
}
|
||||||
|
x509.SetFallbackRoots(p)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module golang.org/x/crypto/x509roots/fallback
|
||||||
|
|
||||||
|
go 1.20
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package goissue52287 is an empty internal package.
|
||||||
|
// It exists only to work around go.dev/issue/52287 and
|
||||||
|
// can be removed after Go 1.19 stops being supported.
|
||||||
|
package goissue52287
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// 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 generate
|
||||||
|
// +build generate
|
||||||
|
|
||||||
|
//go:generate go run gen_fallback_bundle.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/pem"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/x509roots/nss"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build go1.20
|
||||||
|
|
||||||
|
package fallback
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
import "encoding/pem"
|
||||||
|
|
||||||
|
func mustParse(b []byte) []*x509.Certificate {
|
||||||
|
var roots []*x509.Certificate
|
||||||
|
for len(b) > 0 {
|
||||||
|
var block *pem.Block
|
||||||
|
block, b = pem.Decode(b)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
panic("unexpected PEM block type: " + block.Type)
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
roots = append(roots, cert)
|
||||||
|
}
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
|
var bundle = mustParse([]byte(pemRoots))
|
||||||
|
|
||||||
|
// Format of the PEM list is:
|
||||||
|
// * Subject common name
|
||||||
|
// * SHA256 hash
|
||||||
|
// * PEM block
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var (
|
||||||
|
certDataURL = flag.String("certdata-url", "https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt", "URL to the raw certdata.txt file to parse (certdata-path overrides this, if provided)")
|
||||||
|
certDataPath = flag.String("certdata-path", "", "Path to the NSS certdata.txt file to parse (this overrides certdata-url, if provided)")
|
||||||
|
output = flag.String("output", "fallback/bundle.go", "Path to file to write output to")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var certdata io.Reader
|
||||||
|
|
||||||
|
if *certDataPath != "" {
|
||||||
|
f, err := os.Open(*certDataPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to open %q: %s", *certDataPath, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
certdata = f
|
||||||
|
} else {
|
||||||
|
resp, err := http.Get(*certDataURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to request %q: %s", *certDataURL, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
certdata = resp.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := nss.Parse(certdata)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to parse %q: %s", *certDataPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(certs, func(i, j int) bool {
|
||||||
|
// Sort based on the stringified subject (which may not be unique), and
|
||||||
|
// break any ties by just sorting on the raw DER (which will be unique,
|
||||||
|
// but is expensive). This should produce a stable sorting, which should
|
||||||
|
// be mostly readable by a human looking for a specific root or set of
|
||||||
|
// roots.
|
||||||
|
subjI, subjJ := certs[i].X509.Subject.String(), certs[j].X509.Subject.String()
|
||||||
|
if subjI == subjJ {
|
||||||
|
return string(certs[i].X509.Raw) < string(certs[j].X509.Raw)
|
||||||
|
}
|
||||||
|
return subjI < subjJ
|
||||||
|
})
|
||||||
|
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
b.WriteString(tmpl)
|
||||||
|
fmt.Fprintln(b, "const pemRoots = `")
|
||||||
|
for _, c := range certs {
|
||||||
|
if len(c.Constraints) > 0 {
|
||||||
|
// Until the constrained roots API lands, skip anything that has any
|
||||||
|
// additional constraints. Once that API is available, we can add
|
||||||
|
// build constraints that support both the current version and the
|
||||||
|
// new version.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "# %s\n# %x\n", c.X509.Subject.String(), sha256.Sum256(c.X509.Raw))
|
||||||
|
pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})
|
||||||
|
}
|
||||||
|
fmt.Fprintln(b, "`")
|
||||||
|
|
||||||
|
formatted, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to format source: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(*output, formatted, 0644); err != nil {
|
||||||
|
log.Fatalf("failed to write to %q: %s", *output, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package nss provides functionality for parsing NSS certdata.txt
|
||||||
|
// formatted certificate lists and extracting serverAuth roots. Most
|
||||||
|
// users should not use this package themselves, and should instead
|
||||||
|
// rely on the golang.org/x/crypto/x509roots/fallback package which
|
||||||
|
// calls x509.SetFallbackRoots on a pre-parsed set of roots.
|
||||||
|
package nss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraint is a constraint to be applied to a certificate or
|
||||||
|
// certificate chain.
|
||||||
|
type Constraint interface {
|
||||||
|
Kind() Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind is the constraint kind, using the NSS enumeration.
|
||||||
|
type Kind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CKA_NSS_SERVER_DISTRUST_AFTER Kind = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// DistrustAfter is a Constraint that indicates a certificate has a
|
||||||
|
// CKA_NSS_SERVER_DISTRUST_AFTER constraint. This constraint defines a date
|
||||||
|
// after which any certificate issued which is rooted by the constrained
|
||||||
|
// certificate should be distrusted.
|
||||||
|
type DistrustAfter time.Time
|
||||||
|
|
||||||
|
func (DistrustAfter) Kind() Kind {
|
||||||
|
return CKA_NSS_SERVER_DISTRUST_AFTER
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Certificate represents a single trusted serverAuth certificate in the NSS
|
||||||
|
// certdata.txt list and any constraints that should be applied to chains
|
||||||
|
// rooted by it.
|
||||||
|
type Certificate struct {
|
||||||
|
// Certificate is the parsed certificate
|
||||||
|
X509 *x509.Certificate
|
||||||
|
// Constraints contains a list of additional constraints that should be
|
||||||
|
// applied to any certificates that chain to Certificate. If there are
|
||||||
|
// any unknown constraints in the slice, Certificate should not be
|
||||||
|
// trusted.
|
||||||
|
Constraints []Constraint
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMulitLineOctal(s *bufio.Scanner) ([]byte, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for s.Scan() {
|
||||||
|
if s.Text() == "END" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b, err := strconv.Unquote(fmt.Sprintf("\"%s\"", s.Text()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf.Write([]byte(b))
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type certObj struct {
|
||||||
|
c *x509.Certificate
|
||||||
|
DistrustAfter *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertClass(s *bufio.Scanner) ([sha1.Size]byte, *certObj, error) {
|
||||||
|
var h [sha1.Size]byte
|
||||||
|
co := &certObj{}
|
||||||
|
for s.Scan() {
|
||||||
|
l := s.Text()
|
||||||
|
if l == "" {
|
||||||
|
// assume an empty newline indicates the end of a block
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(l, "CKA_VALUE") {
|
||||||
|
b, err := parseMulitLineOctal(s)
|
||||||
|
if err != nil {
|
||||||
|
return h, nil, err
|
||||||
|
}
|
||||||
|
co.c, err = x509.ParseCertificate(b)
|
||||||
|
if err != nil {
|
||||||
|
return h, nil, err
|
||||||
|
}
|
||||||
|
h = sha1.Sum(b)
|
||||||
|
} else if strings.HasPrefix(l, "CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE") {
|
||||||
|
// we don't want it
|
||||||
|
return h, nil, nil
|
||||||
|
} else if l == "CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL" {
|
||||||
|
dateStr, err := parseMulitLineOctal(s)
|
||||||
|
if err != nil {
|
||||||
|
return h, nil, err
|
||||||
|
}
|
||||||
|
t, err := time.Parse("060102150405Z0700", string(dateStr))
|
||||||
|
if err != nil {
|
||||||
|
return h, nil, err
|
||||||
|
}
|
||||||
|
co.DistrustAfter = &t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if co.c == nil {
|
||||||
|
return h, nil, errors.New("malformed CKO_CERTIFICATE object")
|
||||||
|
}
|
||||||
|
return h, co, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type trustObj struct {
|
||||||
|
trusted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTrustClass(s *bufio.Scanner) ([sha1.Size]byte, *trustObj, error) {
|
||||||
|
var h [sha1.Size]byte
|
||||||
|
to := &trustObj{trusted: false} // default to untrusted
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
l := s.Text()
|
||||||
|
if l == "" {
|
||||||
|
// assume an empty newline indicates the end of a block
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if l == "CKA_CERT_SHA1_HASH MULTILINE_OCTAL" {
|
||||||
|
hash, err := parseMulitLineOctal(s)
|
||||||
|
if err != nil {
|
||||||
|
return h, nil, err
|
||||||
|
}
|
||||||
|
copy(h[:], hash)
|
||||||
|
} else if l == "CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR" {
|
||||||
|
// we only care about server auth
|
||||||
|
to.trusted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, to, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a NSS certdata.txt formatted file, returning only
|
||||||
|
// trusted serverAuth roots, as well as any additional constraints. This parser
|
||||||
|
// is very opinionated, only returning roots that are currently trusted for
|
||||||
|
// serverAuth. As such roots returned by this package should only be used for
|
||||||
|
// making trust decisions about serverAuth certificates, as the trust status for
|
||||||
|
// other uses is not considered. Using the roots returned by this package for
|
||||||
|
// trust decisions should be done carefully.
|
||||||
|
//
|
||||||
|
// Some roots returned by the parser may include additional constraints
|
||||||
|
// (currently only DistrustAfter) which need to be considered when verifying
|
||||||
|
// certificates which chain to them.
|
||||||
|
//
|
||||||
|
// Parse is not intended to be a general purpose parser for certdata.txt.
|
||||||
|
func Parse(r io.Reader) ([]*Certificate, error) {
|
||||||
|
// certdata.txt is a rather strange format. It is essentially a list of
|
||||||
|
// textual PKCS#11 objects, delimited by empty lines. There are two main
|
||||||
|
// types of objects, certificates (CKO_CERTIFICATE) and trust definitions
|
||||||
|
// (CKO_NSS_TRUST). These objects appear to alternate, but this ordering is
|
||||||
|
// not defined anywhere, and should probably not be relied on. A single root
|
||||||
|
// certificate requires both the certificate object and the trust definition
|
||||||
|
// object in order to be properly understood.
|
||||||
|
//
|
||||||
|
// The list contains not just serverAuth certificates, so we need to be
|
||||||
|
// careful to only extract certificates which have the serverAuth trust bit
|
||||||
|
// set. Similarly there are a number of trust related bool fields that
|
||||||
|
// appear to _always_ be CKA_TRUE, but it seems unsafe to assume this is the
|
||||||
|
// case, so we should always double check.
|
||||||
|
//
|
||||||
|
// Since we only really care about a couple of fields, this parser throws
|
||||||
|
// away a lot of information, essentially just consuming CKA_CLASS objects
|
||||||
|
// and looking for the individual fields we care about. We could write a
|
||||||
|
// siginificantly more complex parser, which handles the entire format, but
|
||||||
|
// it feels like that would be over engineered for the little information
|
||||||
|
// that we really care about.
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
type nssEntry struct {
|
||||||
|
cert *certObj
|
||||||
|
trust *trustObj
|
||||||
|
}
|
||||||
|
entries := map[[sha1.Size]byte]*nssEntry{}
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
// scan until we hit CKA_CLASS
|
||||||
|
if !strings.HasPrefix(scanner.Text(), "CKA_CLASS") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f := strings.Fields(scanner.Text())
|
||||||
|
if len(f) != 3 {
|
||||||
|
return nil, errors.New("malformed CKA_CLASS")
|
||||||
|
}
|
||||||
|
switch f[2] {
|
||||||
|
case "CKO_CERTIFICATE":
|
||||||
|
h, co, err := parseCertClass(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if co != nil {
|
||||||
|
e, ok := entries[h]
|
||||||
|
if !ok {
|
||||||
|
e = &nssEntry{}
|
||||||
|
entries[h] = e
|
||||||
|
}
|
||||||
|
e.cert = co
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CKO_NSS_TRUST":
|
||||||
|
h, to, err := parseTrustClass(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if to != nil {
|
||||||
|
e, ok := entries[h]
|
||||||
|
if !ok {
|
||||||
|
e = &nssEntry{}
|
||||||
|
entries[h] = e
|
||||||
|
}
|
||||||
|
e.trust = to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var certs []*Certificate
|
||||||
|
for h, e := range entries {
|
||||||
|
if e.cert == nil && e.trust != nil {
|
||||||
|
// We may skip some certificates which are distrusted due to mozilla
|
||||||
|
// policy (CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE), which means
|
||||||
|
// we might get entries that appear to have a trust object, but no
|
||||||
|
// certificate. We can just continue on here.
|
||||||
|
continue
|
||||||
|
} else if e.cert != nil && e.trust == nil {
|
||||||
|
return nil, fmt.Errorf("missing trust object for certificate with SHA1 hash: %x", h)
|
||||||
|
}
|
||||||
|
if !e.trust.trusted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nssCert := &Certificate{X509: e.cert.c}
|
||||||
|
if e.cert.DistrustAfter != nil {
|
||||||
|
nssCert.Constraints = append(nssCert.Constraints, DistrustAfter(*e.cert.DistrustAfter))
|
||||||
|
}
|
||||||
|
certs = append(certs, nssCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user