Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cc52e598 | ||
|
|
833695f0a5 | ||
|
|
ec07f4e35b | ||
|
|
b665ba6f6f | ||
|
|
a1aeb9b34e | ||
|
|
28c53ff63c | ||
|
|
3f0842a464 | ||
|
|
e90f1e17ee | ||
|
|
d359caa4a3 | ||
|
|
c5370d2cc6 | ||
|
|
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 | ||
|
|
776e461a4e | ||
|
|
ebe92624d1 | ||
|
|
a9f661cb6e | ||
|
|
310bfa40f1 | ||
|
|
59ff47295c |
+1
-1
@@ -117,7 +117,7 @@ func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error)
|
||||
return responseAccount(res)
|
||||
}
|
||||
|
||||
// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
|
||||
// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
|
||||
req := json.RawMessage(`{"onlyReturnExisting": true}`)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.11 && gc && !purego
|
||||
// +build go1.11,gc,!purego
|
||||
//go:build gc && !purego
|
||||
// +build gc,!purego
|
||||
|
||||
package chacha20
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.11 && gc && !purego
|
||||
// +build go1.11,gc,!purego
|
||||
//go:build gc && !purego
|
||||
// +build gc,!purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!arm64 && !s390x && !ppc64le) || (arm64 && !go1.11) || !gc || purego
|
||||
// +build !arm64,!s390x,!ppc64le arm64,!go1.11 !gc purego
|
||||
//go:build (!arm64 && !s390x && !ppc64le) || !gc || purego
|
||||
// +build !arm64,!s390x,!ppc64le !gc purego
|
||||
|
||||
package chacha20
|
||||
|
||||
|
||||
+9
-1
@@ -431,6 +431,14 @@ func (s *String) readBase128Int(out *int) bool {
|
||||
}
|
||||
ret <<= 7
|
||||
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)
|
||||
if b&0x80 == 0 {
|
||||
*out = ret
|
||||
@@ -559,7 +567,7 @@ func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It is
|
||||
// ReadASN1BitStringAsBytes decodes an ASN.1 BIT STRING into out and advances. It is
|
||||
// an error if the BIT STRING is not a whole number of bytes. It reports
|
||||
// whether the read was successful.
|
||||
func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||
|
||||
@@ -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, 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, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
|
||||
}
|
||||
|
||||
for i, test := range testData {
|
||||
|
||||
+11
-3
@@ -95,6 +95,11 @@ func (b *Builder) AddUint32(v uint32) {
|
||||
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint48 appends a big-endian, 48-bit value to the byte string.
|
||||
func (b *Builder) AddUint48(v uint64) {
|
||||
b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint64 appends a big-endian, 64-bit value to the byte string.
|
||||
func (b *Builder) AddUint64(v uint64) {
|
||||
b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
@@ -303,9 +308,9 @@ func (b *Builder) add(bytes ...byte) {
|
||||
b.result = append(b.result, bytes...)
|
||||
}
|
||||
|
||||
// Unwrite rolls back n bytes written directly to the Builder. An attempt by a
|
||||
// child builder passed to a continuation to unwrite bytes from its parent will
|
||||
// panic.
|
||||
// Unwrite rolls back non-negative n bytes written directly to the Builder.
|
||||
// An attempt by a child builder passed to a continuation to unwrite bytes
|
||||
// from its parent will panic.
|
||||
func (b *Builder) Unwrite(n int) {
|
||||
if b.err != nil {
|
||||
return
|
||||
@@ -317,6 +322,9 @@ func (b *Builder) Unwrite(n int) {
|
||||
if length < 0 {
|
||||
panic("cryptobyte: internal error")
|
||||
}
|
||||
if n < 0 {
|
||||
panic("cryptobyte: attempted to unwrite negative number of bytes")
|
||||
}
|
||||
if n > length {
|
||||
panic("cryptobyte: attempted to unwrite more than was written")
|
||||
}
|
||||
|
||||
@@ -179,6 +179,27 @@ func TestUint32(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint48(t *testing.T) {
|
||||
var b Builder
|
||||
var u uint64 = 0xfefcff3cfdfc
|
||||
b.AddUint48(u)
|
||||
if err := builderBytesEq(&b, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint48(&v) {
|
||||
t.Error("ReadUint48() = false, want true")
|
||||
}
|
||||
if v != u {
|
||||
t.Errorf("v = %x, want %x", v, u)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint64(0xf2fefefcff3cfdfc)
|
||||
|
||||
@@ -81,6 +81,17 @@ func (s *String) ReadUint32(out *uint32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint48(out *uint64) bool {
|
||||
v := s.read(6)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5])
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint64(out *uint64) bool {
|
||||
|
||||
@@ -5,71 +5,18 @@
|
||||
// Package curve25519 provides an implementation of the X25519 function, which
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||
// 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"
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/curve25519/internal/field"
|
||||
)
|
||||
|
||||
// ScalarMult sets dst to the product scalar * point.
|
||||
//
|
||||
// Deprecated: when provided a low-order point, ScalarMult will set dst to all
|
||||
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
|
||||
// will return an error.
|
||||
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())
|
||||
scalarMult(dst, scalar, point)
|
||||
}
|
||||
|
||||
// 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
|
||||
// copying into fixed size arrays can lead to unexpected bugs.
|
||||
func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||
ScalarMult(dst, scalar, &basePoint)
|
||||
scalarBaseMult(dst, scalar)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -91,21 +38,10 @@ const (
|
||||
// Basepoint is the canonical Curve25519 generator.
|
||||
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 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),
|
||||
// according to RFC 7748, Section 5. scalar, point and the return value are
|
||||
// slices of 32 bytes.
|
||||
@@ -121,26 +57,3 @@ func X25519(scalar, point []byte) ([]byte, error) {
|
||||
var dst [32]byte
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519
|
||||
package curve25519_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||
@@ -19,7 +21,7 @@ func TestX25519Basepoint(t *testing.T) {
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
var err error
|
||||
x, err = X25519(x, Basepoint)
|
||||
x, err = curve25519.X25519(x, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -32,12 +34,12 @@ func TestX25519Basepoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLowOrderPoints(t *testing.T) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, p := range lowOrderPoints {
|
||||
out, err := X25519(scalar, p)
|
||||
out, err := curve25519.X25519(scalar, p)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error, got nil", i)
|
||||
}
|
||||
@@ -48,10 +50,10 @@ func TestLowOrderPoints(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) {
|
||||
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
||||
out, err := X25519(scalar[:], point[:])
|
||||
out, err := curve25519.X25519(scalar[:], point[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -88,10 +90,10 @@ func TestHighBitIgnored(t *testing.T) {
|
||||
var hi0, hi1 [32]byte
|
||||
|
||||
u[31] &= 0x7f
|
||||
ScalarMult(&hi0, &s, &u)
|
||||
curve25519.ScalarMult(&hi0, &s, &u)
|
||||
|
||||
u[31] |= 0x80
|
||||
ScalarMult(&hi1, &s, &u)
|
||||
curve25519.ScalarMult(&hi1, &s, &u)
|
||||
|
||||
if !bytes.Equal(hi0[:], hi1[:]) {
|
||||
t.Errorf("high bit of group point should not affect result")
|
||||
@@ -101,14 +103,14 @@ func TestHighBitIgnored(t *testing.T) {
|
||||
var benchmarkSink byte
|
||||
|
||||
func BenchmarkX25519Basepoint(b *testing.B) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out, err := X25519(scalar, Basepoint)
|
||||
out, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -117,11 +119,11 @@ func BenchmarkX25519Basepoint(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkX25519(b *testing.B) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
point, err := X25519(scalar, Basepoint)
|
||||
point, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -131,7 +133,7 @@ func BenchmarkX25519(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out, err := X25519(scalar, point)
|
||||
out, err := curve25519.X25519(scalar, point)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ func feSquareGeneric(v, a *Element) {
|
||||
v.carryPropagate()
|
||||
}
|
||||
|
||||
// carryPropagate brings the limbs below 52 bits by applying the reduction
|
||||
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
|
||||
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. TODO inline
|
||||
func (v *Element) carryPropagateGeneric() *Element {
|
||||
c0 := v.l0 >> 51
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519
|
||||
package curve25519_test
|
||||
|
||||
// lowOrderPoints from libsodium.
|
||||
// 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
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/net v0.10.0 // tagx:ignore
|
||||
golang.org/x/sys v0.13.0
|
||||
golang.org/x/term v0.13.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.6.0 // indirect
|
||||
require golang.org/x/text v0.13.0 // indirect
|
||||
|
||||
@@ -2,30 +2,40 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.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.9.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-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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -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
|
||||
@@ -8,9 +8,8 @@
|
||||
package wycheproof
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
func TestEddsa(t *testing.T) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -28,6 +29,11 @@ const wycheproofModVer = "v0.0.0-20191219022705-2196000605e4"
|
||||
var wycheproofTestVectorsDir string
|
||||
|
||||
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 {
|
||||
log.Printf("skipping test because 'go' command is unavailable: %v", err)
|
||||
os.Exit(0)
|
||||
@@ -42,8 +48,6 @@ func TestMain(m *testing.M) {
|
||||
// can be used in the following tests.
|
||||
path := "github.com/google/wycheproof@" + wycheproofModVer
|
||||
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()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
|
||||
|
||||
+1
-1
@@ -21,9 +21,9 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ type Key struct {
|
||||
type KeyRing interface {
|
||||
// KeysById returns the set of keys that have the given key id.
|
||||
KeysById(id uint64) []Key
|
||||
// KeysByIdAndUsage returns the set of keys with the given id
|
||||
// KeysByIdUsage returns the set of keys with the given id
|
||||
// that also meet the key usage given by requiredUsage.
|
||||
// The requiredUsage is expressed as the bitwise-OR of
|
||||
// packet.KeyFlag* values.
|
||||
@@ -183,7 +183,7 @@ func (el EntityList) KeysById(id uint64) (keys []Key) {
|
||||
return
|
||||
}
|
||||
|
||||
// KeysByIdAndUsage returns the set of keys with the given id that also meet
|
||||
// KeysByIdUsage returns the set of keys with the given id that also meet
|
||||
// the key usage given by requiredUsage. The requiredUsage is expressed as
|
||||
// the bitwise-OR of packet.KeyFlag* values.
|
||||
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
|
||||
|
||||
@@ -60,7 +60,7 @@ func (c *Compressed) parse(r io.Reader) error {
|
||||
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
|
||||
// compressor and serialized stream header are closed. Its Write()
|
||||
// method writes to the compressor.
|
||||
|
||||
+9
-5
@@ -121,11 +121,11 @@ func (d *state) padAndPermute(dsbyte byte) {
|
||||
copyOut(d, d.buf)
|
||||
}
|
||||
|
||||
// Write absorbs more data into the hash's state. It produces an error
|
||||
// if more data is written to the ShakeHash after writing
|
||||
// Write absorbs more data into the hash's state. It panics if any
|
||||
// output has already been read.
|
||||
func (d *state) Write(p []byte) (written int, err error) {
|
||||
if d.state != spongeAbsorbing {
|
||||
panic("sha3: write to sponge after read")
|
||||
panic("sha3: Write after Read")
|
||||
}
|
||||
if d.buf == nil {
|
||||
d.buf = d.storage.asBytes()[:0]
|
||||
@@ -182,12 +182,16 @@ func (d *state) Read(out []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// Sum applies padding to the hash state and then squeezes out the desired
|
||||
// number of output bytes.
|
||||
// number of output bytes. It panics if any output has already been read.
|
||||
func (d *state) Sum(in []byte) []byte {
|
||||
if d.state != spongeAbsorbing {
|
||||
panic("sha3: Sum after Read")
|
||||
}
|
||||
|
||||
// Make a copy of the original hash so that caller can keep writing
|
||||
// and summing.
|
||||
dup := d.clone()
|
||||
hash := make([]byte, dup.outputLen)
|
||||
hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation
|
||||
dup.Read(hash)
|
||||
return append(in, hash...)
|
||||
}
|
||||
|
||||
+6
-4
@@ -49,7 +49,7 @@ type asmState struct {
|
||||
buf []byte // care must be taken to ensure cap(buf) is a multiple of rate
|
||||
rate int // equivalent to block size
|
||||
storage [3072]byte // underlying storage for buf
|
||||
outputLen int // output length if fixed, 0 if not
|
||||
outputLen int // output length for full security
|
||||
function code // KIMD/KLMD function code
|
||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||
}
|
||||
@@ -72,8 +72,10 @@ func newAsmState(function code) *asmState {
|
||||
s.outputLen = 64
|
||||
case shake_128:
|
||||
s.rate = 168
|
||||
s.outputLen = 32
|
||||
case shake_256:
|
||||
s.rate = 136
|
||||
s.outputLen = 64
|
||||
default:
|
||||
panic("sha3: unrecognized function code")
|
||||
}
|
||||
@@ -108,7 +110,7 @@ func (s *asmState) resetBuf() {
|
||||
// It never returns an error.
|
||||
func (s *asmState) Write(b []byte) (int, error) {
|
||||
if s.state != spongeAbsorbing {
|
||||
panic("sha3: write to sponge after read")
|
||||
panic("sha3: Write after Read")
|
||||
}
|
||||
length := len(b)
|
||||
for len(b) > 0 {
|
||||
@@ -192,8 +194,8 @@ func (s *asmState) Read(out []byte) (n int, err error) {
|
||||
// Sum appends the current hash to b and returns the resulting slice.
|
||||
// It does not change the underlying hash state.
|
||||
func (s *asmState) Sum(b []byte) []byte {
|
||||
if s.outputLen == 0 {
|
||||
panic("sha3: cannot call Sum on SHAKE functions")
|
||||
if s.state != spongeAbsorbing {
|
||||
panic("sha3: Sum after Read")
|
||||
}
|
||||
|
||||
// Copy the state to preserve the original.
|
||||
|
||||
+14
-15
@@ -17,26 +17,25 @@ package sha3
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ShakeHash defines the interface to hash functions that
|
||||
// support arbitrary-length output.
|
||||
// ShakeHash defines the interface to hash functions that support
|
||||
// arbitrary-length output. When used as a plain [hash.Hash], it
|
||||
// produces minimum-length outputs that provide full-strength generic
|
||||
// security.
|
||||
type ShakeHash interface {
|
||||
// Write absorbs more data into the hash's state. It panics if input is
|
||||
// written to it after output has been read from it.
|
||||
io.Writer
|
||||
hash.Hash
|
||||
|
||||
// Read reads more output from the hash; reading affects the hash's
|
||||
// state. (ShakeHash.Read is thus very different from Hash.Sum)
|
||||
// It never returns an error.
|
||||
// It never returns an error, but subsequent calls to Write or Sum
|
||||
// will panic.
|
||||
io.Reader
|
||||
|
||||
// Clone returns a copy of the ShakeHash in its current state.
|
||||
Clone() ShakeHash
|
||||
|
||||
// Reset resets the ShakeHash to its initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// cSHAKE specific context
|
||||
@@ -81,8 +80,8 @@ func leftEncode(value uint64) []byte {
|
||||
return b[i-1:]
|
||||
}
|
||||
|
||||
func newCShake(N, S []byte, rate int, dsbyte byte) ShakeHash {
|
||||
c := cshakeState{state: &state{rate: rate, dsbyte: dsbyte}}
|
||||
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
|
||||
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
|
||||
|
||||
// leftEncode returns max 9 bytes
|
||||
c.initBlock = make([]byte, 0, 9*2+len(N)+len(S))
|
||||
@@ -119,7 +118,7 @@ func NewShake128() ShakeHash {
|
||||
if h := newShake128Asm(); h != nil {
|
||||
return h
|
||||
}
|
||||
return &state{rate: rate128, dsbyte: dsbyteShake}
|
||||
return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
|
||||
}
|
||||
|
||||
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
||||
@@ -129,7 +128,7 @@ func NewShake256() ShakeHash {
|
||||
if h := newShake256Asm(); h != nil {
|
||||
return h
|
||||
}
|
||||
return &state{rate: rate256, dsbyte: dsbyteShake}
|
||||
return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
|
||||
}
|
||||
|
||||
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
|
||||
@@ -142,7 +141,7 @@ func NewCShake128(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake128()
|
||||
}
|
||||
return newCShake(N, S, rate128, dsbyteCShake)
|
||||
return newCShake(N, S, rate128, 32, dsbyteCShake)
|
||||
}
|
||||
|
||||
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
|
||||
@@ -155,7 +154,7 @@ func NewCShake256(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake256()
|
||||
}
|
||||
return newCShake(N, S, rate256, dsbyteCShake)
|
||||
return newCShake(N, S, rate256, 64, dsbyteCShake)
|
||||
}
|
||||
|
||||
// ShakeSum128 writes an arbitrary-length digest of data into hash.
|
||||
|
||||
+1
-1
@@ -16,6 +16,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
@@ -26,7 +27,6 @@ import (
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
||||
// types supported vary by platform.
|
||||
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")
|
||||
if err != nil {
|
||||
@@ -366,7 +369,8 @@ func TestAuth(t *testing.T) {
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
t.Errorf("NewServerConn error: %v", err)
|
||||
return
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
+2
-2
@@ -7,6 +7,7 @@ package agent
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
@@ -16,11 +17,10 @@ import (
|
||||
"log"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"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.
|
||||
type server struct {
|
||||
agent Agent
|
||||
|
||||
@@ -53,10 +53,11 @@ func TestSetupForwardAgent(t *testing.T) {
|
||||
incoming := make(chan *ssh.ServerConn, 1)
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
incoming <- conn
|
||||
if err != nil {
|
||||
t.Errorf("NewServerConn error: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{
|
||||
@@ -71,8 +72,10 @@ func TestSetupForwardAgent(t *testing.T) {
|
||||
if err := ForwardToRemote(client, socket); err != nil {
|
||||
t.Fatalf("SetupForwardAgent: %v", err)
|
||||
}
|
||||
|
||||
server := <-incoming
|
||||
if server == nil {
|
||||
t.Fatal("Unable to get server")
|
||||
}
|
||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||
|
||||
@@ -6,6 +6,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -90,16 +91,16 @@ func BenchmarkEndToEnd(b *testing.B) {
|
||||
go func() {
|
||||
newCh, err := server.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Client: %v", err)
|
||||
panic(fmt.Sprintf("Client: %v", err))
|
||||
}
|
||||
ch, incoming, err := newCh.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Accept: %v", err)
|
||||
panic(fmt.Sprintf("Accept: %v", err))
|
||||
}
|
||||
go DiscardRequests(incoming)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := io.ReadFull(ch, output); err != nil {
|
||||
b.Fatalf("ReadFull: %v", err)
|
||||
panic(fmt.Sprintf("ReadFull: %v", err))
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
|
||||
+30
-8
@@ -16,8 +16,9 @@ import (
|
||||
|
||||
// Certificate algorithm names from [PROTOCOL.certkeys]. These values can appear
|
||||
// in Certificate.Type, PublicKey.Type, and ClientConfig.HostKeyAlgorithms.
|
||||
// Unlike key algorithm names, these are not passed to AlgorithmSigner and don't
|
||||
// appear in the Signature.Format field.
|
||||
// Unlike key algorithm names, these are not passed to AlgorithmSigner nor
|
||||
// returned by MultiAlgorithmSigner and don't appear in the Signature.Format
|
||||
// field.
|
||||
const (
|
||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
||||
@@ -255,10 +256,17 @@ func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
||||
return nil, errors.New("ssh: signer and cert have different public key")
|
||||
}
|
||||
|
||||
if algorithmSigner, ok := signer.(AlgorithmSigner); ok {
|
||||
switch s := signer.(type) {
|
||||
case MultiAlgorithmSigner:
|
||||
return &multiAlgorithmSigner{
|
||||
AlgorithmSigner: &algorithmOpenSSHCertSigner{
|
||||
&openSSHCertSigner{cert, signer}, s},
|
||||
supportedAlgorithms: s.Algorithms(),
|
||||
}, nil
|
||||
case AlgorithmSigner:
|
||||
return &algorithmOpenSSHCertSigner{
|
||||
&openSSHCertSigner{cert, signer}, algorithmSigner}, nil
|
||||
} else {
|
||||
&openSSHCertSigner{cert, signer}, s}, nil
|
||||
default:
|
||||
return &openSSHCertSigner{cert, signer}, nil
|
||||
}
|
||||
}
|
||||
@@ -432,7 +440,9 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
}
|
||||
|
||||
// SignCert signs the certificate with an authority, setting the Nonce,
|
||||
// SignatureKey, and Signature fields.
|
||||
// SignatureKey, and Signature fields. If the authority implements the
|
||||
// MultiAlgorithmSigner interface the first algorithm in the list is used. This
|
||||
// is useful if you want to sign with a specific algorithm.
|
||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
c.Nonce = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||
@@ -440,8 +450,20 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
}
|
||||
c.SignatureKey = authority.PublicKey()
|
||||
|
||||
// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
|
||||
if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
|
||||
if v, ok := authority.(MultiAlgorithmSigner); ok {
|
||||
if len(v.Algorithms()) == 0 {
|
||||
return errors.New("the provided authority has no signature algorithm")
|
||||
}
|
||||
// Use the first algorithm in the list.
|
||||
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), v.Algorithms()[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Signature = sig
|
||||
return nil
|
||||
} else if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
|
||||
// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
|
||||
// TODO: consider using KeyAlgoRSASHA256 as default.
|
||||
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), KeyAlgoRSASHA512)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+94
-6
@@ -187,10 +187,30 @@ func TestHostKeyCert(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
addr string
|
||||
succeed bool
|
||||
addr string
|
||||
succeed bool
|
||||
certSignerAlgorithms []string // Empty means no algorithm restrictions.
|
||||
clientHostKeyAlgorithms []string
|
||||
}{
|
||||
{addr: "hostname:22", succeed: true},
|
||||
{
|
||||
addr: "hostname:22",
|
||||
succeed: true,
|
||||
certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
|
||||
clientHostKeyAlgorithms: []string{CertAlgoRSASHA512v01},
|
||||
},
|
||||
{
|
||||
addr: "hostname:22",
|
||||
succeed: false,
|
||||
certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
|
||||
clientHostKeyAlgorithms: []string{CertAlgoRSAv01},
|
||||
},
|
||||
{
|
||||
addr: "hostname:22",
|
||||
succeed: false,
|
||||
certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
|
||||
clientHostKeyAlgorithms: []string{KeyAlgoRSASHA512}, // Not a certificate algorithm.
|
||||
},
|
||||
{addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22'
|
||||
{addr: "lasthost:22", succeed: false},
|
||||
} {
|
||||
@@ -207,14 +227,24 @@ func TestHostKeyCert(t *testing.T) {
|
||||
conf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
conf.AddHostKey(certSigner)
|
||||
if len(test.certSignerAlgorithms) > 0 {
|
||||
mas, err := NewSignerWithAlgorithms(certSigner.(AlgorithmSigner), test.certSignerAlgorithms)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
conf.AddHostKey(mas)
|
||||
} else {
|
||||
conf.AddHostKey(certSigner)
|
||||
}
|
||||
_, _, _, err := NewServerConn(c1, &conf)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
HostKeyAlgorithms: test.clientHostKeyAlgorithms,
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, test.addr, config)
|
||||
|
||||
@@ -242,6 +272,20 @@ func (s *legacyRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error)
|
||||
}
|
||||
|
||||
func TestCertTypes(t *testing.T) {
|
||||
algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
multiAlgoSignerSHA256, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create multi algorithm signer SHA256: %v", err)
|
||||
}
|
||||
// Algorithms are in order of preference, we expect rsa-sha2-512 to be used.
|
||||
multiAlgoSignerSHA512, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create multi algorithm signer SHA512: %v", err)
|
||||
}
|
||||
|
||||
var testVars = []struct {
|
||||
name string
|
||||
signer Signer
|
||||
@@ -251,8 +295,10 @@ func TestCertTypes(t *testing.T) {
|
||||
{CertAlgoECDSA384v01, testSigners["ecdsap384"], ""},
|
||||
{CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
|
||||
{CertAlgoED25519v01, testSigners["ed25519"], ""},
|
||||
{CertAlgoRSAv01, testSigners["rsa"], KeyAlgoRSASHA512},
|
||||
{CertAlgoRSAv01, testSigners["rsa"], KeyAlgoRSASHA256},
|
||||
{"legacyRSASigner", &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
|
||||
{"multiAlgoRSASignerSHA256", multiAlgoSignerSHA256, KeyAlgoRSASHA256},
|
||||
{"multiAlgoRSASignerSHA512", multiAlgoSignerSHA512, KeyAlgoRSASHA512},
|
||||
{CertAlgoDSAv01, testSigners["dsa"], ""},
|
||||
}
|
||||
|
||||
@@ -318,3 +364,45 @@ func TestCertTypes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
||||
type testcase struct {
|
||||
sigAlgo string
|
||||
algoritms []string
|
||||
}
|
||||
cases := []testcase{
|
||||
{
|
||||
sigAlgo: KeyAlgoRSA,
|
||||
algoritms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
},
|
||||
{
|
||||
sigAlgo: KeyAlgoRSASHA256,
|
||||
algoritms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
|
||||
},
|
||||
{
|
||||
sigAlgo: KeyAlgoRSASHA512,
|
||||
algoritms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||
},
|
||||
}
|
||||
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.sigAlgo, func(t *testing.T) {
|
||||
signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), c.algoritms)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSignerWithAlgorithms error: %v", err)
|
||||
}
|
||||
if err := cert.SignCert(rand.Reader, signer); err != nil {
|
||||
t.Fatalf("SignCert error: %v", err)
|
||||
}
|
||||
if cert.Signature.Format != c.sigAlgo {
|
||||
t.Fatalf("got signature format %q, want %q", cert.Signature.Format, c.sigAlgo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -114,7 +114,8 @@ var cipherModes = map[string]*cipherMode{
|
||||
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
|
||||
|
||||
// AEAD ciphers
|
||||
gcmCipherID: {16, 12, newGCMCipher},
|
||||
gcm128CipherID: {16, 12, newGCMCipher},
|
||||
gcm256CipherID: {32, 12, newGCMCipher},
|
||||
chacha20Poly1305ID: {64, 0, newChaCha20Cipher},
|
||||
|
||||
// CBC mode is insecure and so is not included in the default config.
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ func TestCVE202143565(t *testing.T) {
|
||||
constructPacket func(packetCipher) io.Reader
|
||||
}{
|
||||
{
|
||||
cipher: gcmCipherID,
|
||||
cipher: gcm128CipherID,
|
||||
constructPacket: func(client packetCipher) io.Reader {
|
||||
internalCipher := client.(*gcmCipher)
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
+66
-30
@@ -71,7 +71,9 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
|
||||
if err != nil {
|
||||
return err
|
||||
// We return the error later if there is no other method left to
|
||||
// try.
|
||||
ok = authFailure
|
||||
}
|
||||
if ok == authSuccess {
|
||||
// success
|
||||
@@ -101,6 +103,12 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if auth == nil && err != nil {
|
||||
// We have an error and there are no other authentication methods to
|
||||
// try, so we return it.
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
|
||||
}
|
||||
@@ -217,21 +225,45 @@ func (cb publicKeyCallback) method() string {
|
||||
return "publickey"
|
||||
}
|
||||
|
||||
func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (as AlgorithmSigner, algo string) {
|
||||
func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiAlgorithmSigner, string, error) {
|
||||
var as MultiAlgorithmSigner
|
||||
keyFormat := signer.PublicKey().Type()
|
||||
|
||||
// Like in sendKexInit, if the public key implements AlgorithmSigner we
|
||||
// assume it supports all algorithms, otherwise only the key format one.
|
||||
as, ok := signer.(AlgorithmSigner)
|
||||
if !ok {
|
||||
return algorithmSignerWrapper{signer}, keyFormat
|
||||
// If the signer implements MultiAlgorithmSigner we use the algorithms it
|
||||
// support, if it implements AlgorithmSigner we assume it supports all
|
||||
// algorithms, otherwise only the key format one.
|
||||
switch s := signer.(type) {
|
||||
case MultiAlgorithmSigner:
|
||||
as = s
|
||||
case AlgorithmSigner:
|
||||
as = &multiAlgorithmSigner{
|
||||
AlgorithmSigner: s,
|
||||
supportedAlgorithms: algorithmsForKeyFormat(underlyingAlgo(keyFormat)),
|
||||
}
|
||||
default:
|
||||
as = &multiAlgorithmSigner{
|
||||
AlgorithmSigner: algorithmSignerWrapper{signer},
|
||||
supportedAlgorithms: []string{underlyingAlgo(keyFormat)},
|
||||
}
|
||||
}
|
||||
|
||||
getFallbackAlgo := func() (string, error) {
|
||||
// Fallback to use if there is no "server-sig-algs" extension or a
|
||||
// common algorithm cannot be found. We use the public key format if the
|
||||
// MultiAlgorithmSigner supports it, otherwise we return an error.
|
||||
if !contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
|
||||
return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v",
|
||||
underlyingAlgo(keyFormat), keyFormat, as.Algorithms())
|
||||
}
|
||||
return keyFormat, nil
|
||||
}
|
||||
|
||||
extPayload, ok := extensions["server-sig-algs"]
|
||||
if !ok {
|
||||
// If there is no "server-sig-algs" extension, fall back to the key
|
||||
// format algorithm.
|
||||
return as, keyFormat
|
||||
// If there is no "server-sig-algs" extension use the fallback
|
||||
// algorithm.
|
||||
algo, err := getFallbackAlgo()
|
||||
return as, algo, err
|
||||
}
|
||||
|
||||
// The server-sig-algs extension only carries underlying signature
|
||||
@@ -245,15 +277,22 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (as Alg
|
||||
}
|
||||
}
|
||||
|
||||
keyAlgos := algorithmsForKeyFormat(keyFormat)
|
||||
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
||||
var keyAlgos []string
|
||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||
if contains(as.Algorithms(), underlyingAlgo(algo)) {
|
||||
keyAlgos = append(keyAlgos, algo)
|
||||
}
|
||||
}
|
||||
|
||||
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos)
|
||||
if err != nil {
|
||||
// If there is no overlap, try the key anyway with the key format
|
||||
// algorithm, to support servers that fail to list all supported
|
||||
// algorithms.
|
||||
return as, keyFormat
|
||||
// If there is no overlap, return the fallback algorithm to support
|
||||
// servers that fail to list all supported algorithms.
|
||||
algo, err := getFallbackAlgo()
|
||||
return as, algo, err
|
||||
}
|
||||
return as, algo
|
||||
return as, algo, nil
|
||||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
|
||||
@@ -267,10 +306,17 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||
return authFailure, nil, err
|
||||
}
|
||||
var methods []string
|
||||
var errSigAlgo error
|
||||
for _, signer := range signers {
|
||||
pub := signer.PublicKey()
|
||||
as, algo := pickSignatureAlgorithm(signer, extensions)
|
||||
|
||||
as, algo, err := pickSignatureAlgorithm(signer, extensions)
|
||||
if err != nil && errSigAlgo == nil {
|
||||
// If we cannot negotiate a signature algorithm store the first
|
||||
// error so we can return it to provide a more meaningful message if
|
||||
// no other signers work.
|
||||
errSigAlgo = err
|
||||
continue
|
||||
}
|
||||
ok, err := validateKey(pub, algo, user, c)
|
||||
if err != nil {
|
||||
return authFailure, nil, err
|
||||
@@ -317,22 +363,12 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||
// contain the "publickey" method, do not attempt to authenticate with any
|
||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||
// additional authentication methods are required.
|
||||
if success == authSuccess || !containsMethod(methods, cb.method()) {
|
||||
if success == authSuccess || !contains(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
|
||||
return authFailure, methods, nil
|
||||
}
|
||||
|
||||
func containsMethod(methods []string, method string) bool {
|
||||
for _, m := range methods {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return authFailure, methods, errSigAlgo
|
||||
}
|
||||
|
||||
// validateKey validates the key provided is acceptable to the server.
|
||||
|
||||
@@ -955,3 +955,330 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickSignatureAlgorithm(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
extensions map[string][]byte
|
||||
}
|
||||
cases := []testcase{
|
||||
{
|
||||
name: "server with empty server-sig-algs",
|
||||
extensions: map[string][]byte{
|
||||
"server-sig-algs": []byte(``),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "server with no server-sig-algs",
|
||||
extensions: nil,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
signer, ok := testSigners["rsa"].(MultiAlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatalf("rsa test signer does not implement the MultiAlgorithmSigner interface")
|
||||
}
|
||||
// The signer supports the public key algorithm which is then returned.
|
||||
_, algo, err := pickSignatureAlgorithm(signer, c.extensions)
|
||||
if err != nil {
|
||||
t.Fatalf("got %v, want no error", err)
|
||||
}
|
||||
if algo != signer.PublicKey().Type() {
|
||||
t.Fatalf("got algo %q, want %q", algo, signer.PublicKey().Type())
|
||||
}
|
||||
// Test a signer that uses a certificate algorithm as the public key
|
||||
// type.
|
||||
cert := &Certificate{
|
||||
CertType: UserCert,
|
||||
Key: signer.PublicKey(),
|
||||
}
|
||||
cert.SignCert(rand.Reader, signer)
|
||||
|
||||
certSigner, err := NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("error generating cert signer: %v", err)
|
||||
}
|
||||
// The signer supports the public key algorithm and the
|
||||
// public key format is a certificate type so the cerificate
|
||||
// algorithm matching the key format must be returned
|
||||
_, algo, err = pickSignatureAlgorithm(certSigner, c.extensions)
|
||||
if err != nil {
|
||||
t.Fatalf("got %v, want no error", err)
|
||||
}
|
||||
if algo != certSigner.PublicKey().Type() {
|
||||
t.Fatalf("got algo %q, want %q", algo, certSigner.PublicKey().Type())
|
||||
}
|
||||
signer, err = NewSignerWithAlgorithms(signer.(AlgorithmSigner), []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create signer with algorithms: %v", err)
|
||||
}
|
||||
// The signer does not support the public key algorithm so an error
|
||||
// is returned.
|
||||
_, _, err = pickSignatureAlgorithm(signer, c.extensions)
|
||||
if err == nil {
|
||||
t.Fatal("got no error, no common public key signature algorithm error expected")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// configurablePublicKeyCallback is a public key callback that allows to
|
||||
// configure the signature algorithm and format. This way we can emulate the
|
||||
// behavior of buggy clients.
|
||||
type configurablePublicKeyCallback struct {
|
||||
signer AlgorithmSigner
|
||||
signatureAlgo string
|
||||
signatureFormat string
|
||||
}
|
||||
|
||||
func (cb configurablePublicKeyCallback) method() string {
|
||||
return "publickey"
|
||||
}
|
||||
|
||||
func (cb configurablePublicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
|
||||
pub := cb.signer.PublicKey()
|
||||
|
||||
ok, err := validateKey(pub, cb.signatureAlgo, user, c)
|
||||
if err != nil {
|
||||
return authFailure, nil, err
|
||||
}
|
||||
if !ok {
|
||||
return authFailure, nil, fmt.Errorf("invalid public key")
|
||||
}
|
||||
|
||||
pubKey := pub.Marshal()
|
||||
data := buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
}, cb.signatureAlgo, pubKey)
|
||||
sign, err := cb.signer.SignWithAlgorithm(rand, data, underlyingAlgo(cb.signatureFormat))
|
||||
if err != nil {
|
||||
return authFailure, nil, err
|
||||
}
|
||||
|
||||
s := Marshal(sign)
|
||||
sig := make([]byte, stringLength(len(s)))
|
||||
marshalString(sig, s)
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
HasSig: true,
|
||||
Algoname: cb.signatureAlgo,
|
||||
PubKey: pubKey,
|
||||
Sig: sig,
|
||||
}
|
||||
p := Marshal(&msg)
|
||||
if err := c.writePacket(p); err != nil {
|
||||
return authFailure, nil, err
|
||||
}
|
||||
var success authResult
|
||||
success, methods, err := handleAuthResponse(c)
|
||||
if err != nil {
|
||||
return authFailure, nil, err
|
||||
}
|
||||
if success == authSuccess || !contains(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
|
||||
return authFailure, methods, nil
|
||||
}
|
||||
|
||||
func TestPublicKeyAndAlgoCompatibility(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
Auth: []AuthMethod{
|
||||
configurablePublicKeyCallback{
|
||||
signer: certSigner.(AlgorithmSigner),
|
||||
signatureAlgo: KeyAlgoRSASHA256,
|
||||
signatureFormat: KeyAlgoRSASHA256,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Error("cert login passed with incompatible public key type and algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientAuthGPGAgentCompat(t *testing.T) {
|
||||
clientConfig := &ClientConfig{
|
||||
User: "testuser",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
Auth: []AuthMethod{
|
||||
// algorithm rsa-sha2-512 and signature format ssh-rsa.
|
||||
configurablePublicKeyCallback{
|
||||
signer: testSigners["rsa"].(AlgorithmSigner),
|
||||
signatureAlgo: KeyAlgoRSASHA512,
|
||||
signatureFormat: KeyAlgoRSA,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertAuthOpenSSHCompat(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
Auth: []AuthMethod{
|
||||
// algorithm ssh-rsa-cert-v01@openssh.com and signature format
|
||||
// rsa-sha2-256.
|
||||
configurablePublicKeyCallback{
|
||||
signer: certSigner.(AlgorithmSigner),
|
||||
signatureAlgo: CertAlgoRSAv01,
|
||||
signatureFormat: KeyAlgoRSASHA256,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+43
-17
@@ -28,7 +28,7 @@ const (
|
||||
// supportedCiphers lists ciphers we support but might not recommend.
|
||||
var supportedCiphers = []string{
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes128-gcm@openssh.com", gcm256CipherID,
|
||||
chacha20Poly1305ID,
|
||||
"arcfour256", "arcfour128", "arcfour",
|
||||
aes128cbcID,
|
||||
@@ -37,7 +37,7 @@ var supportedCiphers = []string{
|
||||
|
||||
// preferredCiphers specifies the default preference for ciphers.
|
||||
var preferredCiphers = []string{
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes128-gcm@openssh.com", gcm256CipherID,
|
||||
chacha20Poly1305ID,
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
}
|
||||
@@ -49,7 +49,8 @@ var supportedKexAlgos = []string{
|
||||
// P384 and P521 are not constant-time yet, but since we don't
|
||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
kexAlgoDH14SHA256, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
kexAlgoDH14SHA256, kexAlgoDH16SHA512, kexAlgoDH14SHA1,
|
||||
kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// preferredKexAlgos specifies the default preference for key-exchange algorithms
|
||||
// in preference order.
|
||||
// preferredKexAlgos specifies the default preference for key-exchange
|
||||
// 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{
|
||||
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
@@ -70,12 +72,12 @@ var preferredKexAlgos = []string{
|
||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSASHA512v01, CertAlgoRSASHA256v01,
|
||||
CertAlgoRSASHA256v01, CertAlgoRSASHA512v01,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
||||
|
||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
KeyAlgoRSASHA512, KeyAlgoRSASHA256,
|
||||
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
|
||||
KeyAlgoRSA, KeyAlgoDSA,
|
||||
|
||||
KeyAlgoED25519,
|
||||
@@ -85,7 +87,7 @@ var supportedHostKeyAlgos = []string{
|
||||
// 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.
|
||||
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}
|
||||
@@ -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
|
||||
// authentication algorithms. Note that this doesn't include certificate types
|
||||
// since those use the underlying algorithm. This list is sent to the client if
|
||||
@@ -168,7 +177,7 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
|
||||
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
|
||||
// 128.
|
||||
switch a.Cipher {
|
||||
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID:
|
||||
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcm128CipherID, gcm256CipherID, aes128cbcID:
|
||||
return 16 * (1 << 32)
|
||||
|
||||
}
|
||||
@@ -178,7 +187,8 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
|
||||
}
|
||||
|
||||
var aeadCiphers = map[string]bool{
|
||||
gcmCipherID: true,
|
||||
gcm128CipherID: true,
|
||||
gcm256CipherID: true,
|
||||
chacha20Poly1305ID: true,
|
||||
}
|
||||
|
||||
@@ -261,16 +271,16 @@ type Config struct {
|
||||
// unspecified, a size suitable for the chosen cipher is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
// default set of algorithms is used.
|
||||
// The allowed key exchanges algorithms. If unspecified then a default set
|
||||
// of algorithms is used. Unsupported values are silently ignored.
|
||||
KeyExchanges []string
|
||||
|
||||
// The allowed cipher algorithms. If unspecified then a sensible
|
||||
// default is used.
|
||||
// The allowed cipher algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
Ciphers []string
|
||||
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
||||
// is used.
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
MACs []string
|
||||
}
|
||||
|
||||
@@ -287,7 +297,7 @@ func (c *Config) SetDefaults() {
|
||||
var ciphers []string
|
||||
for _, c := range c.Ciphers {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -296,10 +306,26 @@ func (c *Config) SetDefaults() {
|
||||
if c.KeyExchanges == nil {
|
||||
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 {
|
||||
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 {
|
||||
// cipher specific default
|
||||
|
||||
+5
-5
@@ -82,11 +82,11 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []testcase{
|
||||
testcase{
|
||||
{
|
||||
name: "standard",
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common hostkey",
|
||||
serverIn: kexInitMsg{
|
||||
ServerHostKeyAlgos: []string{"hostkey2"},
|
||||
@@ -94,7 +94,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common kex",
|
||||
serverIn: kexInitMsg{
|
||||
KexAlgos: []string{"kex2"},
|
||||
@@ -102,7 +102,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "no common cipher",
|
||||
serverIn: kexInitMsg{
|
||||
CiphersClientServer: []string{"cipher2"},
|
||||
@@ -110,7 +110,7 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
testcase{
|
||||
{
|
||||
name: "client decides cipher",
|
||||
serverIn: kexInitMsg{
|
||||
CiphersClientServer: []string{"cipher1", "cipher2"},
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ func (c *connection) Close() error {
|
||||
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.
|
||||
type sshConn struct {
|
||||
conn net.Conn
|
||||
|
||||
@@ -13,6 +13,7 @@ others.
|
||||
|
||||
References:
|
||||
|
||||
[PROTOCOL]: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD
|
||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||
|
||||
|
||||
+67
-1
@@ -7,6 +7,8 @@ package ssh_test
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@@ -75,7 +77,6 @@ func ExampleNewServerConn() {
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key: ", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
@@ -139,6 +140,36 @@ func ExampleNewServerConn() {
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleServerConfig_AddHostKey() {
|
||||
// Minimal ServerConfig supporting only password authentication.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := os.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key: ", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key: ", err)
|
||||
}
|
||||
// Restrict host key algorithms to disable ssh-rsa.
|
||||
signer, err := ssh.NewSignerWithAlgorithms(private.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create private key with restricted algorithms: ", err)
|
||||
}
|
||||
config.AddHostKey(signer)
|
||||
}
|
||||
|
||||
func ExampleClientConfig_HostKeyCallback() {
|
||||
// Every client must provide a host key check. Here is a
|
||||
// simple-minded parse of OpenSSH's known_hosts file
|
||||
@@ -318,3 +349,38 @@ func ExampleSession_RequestPty() {
|
||||
log.Fatal("failed to start shell: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleCertificate_SignCert() {
|
||||
// Sign a certificate with a specific algorithm.
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 3072)
|
||||
if err != nil {
|
||||
log.Fatal("unable to generate RSA key: ", err)
|
||||
}
|
||||
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
||||
if err != nil {
|
||||
log.Fatal("unable to get RSA public key: ", err)
|
||||
}
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 3072)
|
||||
if err != nil {
|
||||
log.Fatal("unable to generate CA key: ", err)
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(caKey)
|
||||
if err != nil {
|
||||
log.Fatal("unable to generate signer from key: ", err)
|
||||
}
|
||||
mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
log.Fatal("unable to create signer with algoritms: ", err)
|
||||
}
|
||||
certificate := ssh.Certificate{
|
||||
Key: publicKey,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
if err := certificate.SignCert(rand.Reader, mas); err != nil {
|
||||
log.Fatal("unable to sign certificate: ", err)
|
||||
}
|
||||
// Save the public key to a file and check that rsa-sha-256 is used for
|
||||
// signing:
|
||||
// ssh-keygen -L -f <path to the file>
|
||||
fmt.Println(string(ssh.MarshalAuthorizedKey(&certificate)))
|
||||
}
|
||||
|
||||
+30
-14
@@ -461,19 +461,24 @@ func (t *handshakeTransport) sendKexInit() error {
|
||||
isServer := len(t.hostKeys) > 0
|
||||
if isServer {
|
||||
for _, k := range t.hostKeys {
|
||||
// If k is an AlgorithmSigner, presume it supports all signature algorithms
|
||||
// associated with the key format. (Ideally AlgorithmSigner would have a
|
||||
// method to advertise supported algorithms, but it doesn't. This means that
|
||||
// adding support for a new algorithm is a breaking change, as we will
|
||||
// immediately negotiate it even if existing implementations don't support
|
||||
// it. If that ever happens, we'll have to figure something out.)
|
||||
// If k is not an AlgorithmSigner, we can only assume it only supports the
|
||||
// algorithms that matches the key format. (This means that Sign can't pick
|
||||
// a different default.)
|
||||
// If k is a MultiAlgorithmSigner, we restrict the signature
|
||||
// algorithms. If k is a AlgorithmSigner, presume it supports all
|
||||
// signature algorithms associated with the key format. If k is not
|
||||
// an AlgorithmSigner, we can only assume it only supports the
|
||||
// algorithms that matches the key format. (This means that Sign
|
||||
// can't pick a different default).
|
||||
keyFormat := k.PublicKey().Type()
|
||||
if _, ok := k.(AlgorithmSigner); ok {
|
||||
|
||||
switch s := k.(type) {
|
||||
case MultiAlgorithmSigner:
|
||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||
if contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
|
||||
}
|
||||
}
|
||||
case AlgorithmSigner:
|
||||
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...)
|
||||
} else {
|
||||
default:
|
||||
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
|
||||
}
|
||||
}
|
||||
@@ -642,16 +647,20 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
|
||||
// On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO
|
||||
// message with the server-sig-algs extension if the client supports it. See
|
||||
// RFC 8308, Sections 2.4 and 3.1.
|
||||
// RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9.
|
||||
if !isClient && firstKeyExchange && contains(clientInit.KexAlgos, "ext-info-c") {
|
||||
extInfo := &extInfoMsg{
|
||||
NumExtensions: 1,
|
||||
Payload: make([]byte, 0, 4+15+4+len(supportedPubKeyAuthAlgosList)),
|
||||
NumExtensions: 2,
|
||||
Payload: make([]byte, 0, 4+15+4+len(supportedPubKeyAuthAlgosList)+4+16+4+1),
|
||||
}
|
||||
extInfo.Payload = appendInt(extInfo.Payload, len("server-sig-algs"))
|
||||
extInfo.Payload = append(extInfo.Payload, "server-sig-algs"...)
|
||||
extInfo.Payload = appendInt(extInfo.Payload, len(supportedPubKeyAuthAlgosList))
|
||||
extInfo.Payload = append(extInfo.Payload, supportedPubKeyAuthAlgosList...)
|
||||
extInfo.Payload = appendInt(extInfo.Payload, len("ping@openssh.com"))
|
||||
extInfo.Payload = append(extInfo.Payload, "ping@openssh.com"...)
|
||||
extInfo.Payload = appendInt(extInfo.Payload, 1)
|
||||
extInfo.Payload = append(extInfo.Payload, "0"...)
|
||||
if err := t.conn.writePacket(Marshal(extInfo)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -685,9 +694,16 @@ func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, a
|
||||
|
||||
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
|
||||
for _, k := range hostKeys {
|
||||
if s, ok := k.(MultiAlgorithmSigner); ok {
|
||||
if !contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if algo == k.PublicKey().Type() {
|
||||
return algorithmSignerWrapper{k}
|
||||
}
|
||||
|
||||
k, ok := k.(AlgorithmSigner)
|
||||
if !ok {
|
||||
continue
|
||||
|
||||
+110
-18
@@ -148,6 +148,7 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
clientDone := make(chan int, 0)
|
||||
gotHalf := make(chan int, 0)
|
||||
const N = 20
|
||||
errorCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(clientDone)
|
||||
@@ -158,7 +159,9 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
for i := 0; i < N; i++ {
|
||||
p := []byte{msgRequestSuccess, byte(i)}
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
errorCh <- err
|
||||
trC.Close()
|
||||
return
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
<-gotHalf
|
||||
@@ -177,16 +180,15 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
checker.waitCall <- 1
|
||||
}
|
||||
}
|
||||
errorCh <- nil
|
||||
}()
|
||||
|
||||
// Server checks that client messages come in cleanly
|
||||
i := 0
|
||||
err = nil
|
||||
for ; i < N; i++ {
|
||||
var p []byte
|
||||
p, err = trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
p, err := trS.readPacket()
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
gotHalf <- 1
|
||||
@@ -198,8 +200,8 @@ func TestHandshakeBasic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
<-clientDone
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
if err := <-errorCh; err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
}
|
||||
if i != N {
|
||||
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
|
||||
// initiated.
|
||||
done := make(chan int, 1)
|
||||
errorCh := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
_, err := trC.readPacket()
|
||||
errorCh <- err
|
||||
}()
|
||||
|
||||
<-done
|
||||
if err := <-errorCh; err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
<-sync.called
|
||||
}
|
||||
|
||||
@@ -421,8 +423,8 @@ func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||
// panic.
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
||||
if runtime.GOOS == "js" && runtime.GOARCH == "wasm" {
|
||||
t.Skip("skipping on js/wasm; see golang.org/issue/32840")
|
||||
if (runtime.GOOS == "js" || runtime.GOOS == "wasip1") && runtime.GOARCH == "wasm" {
|
||||
t.Skipf("skipping on %s/wasm; see golang.org/issue/32840", runtime.GOOS)
|
||||
}
|
||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||
|
||||
@@ -562,7 +564,7 @@ func TestHandshakeRekeyDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandshakeAEADCipherNoMAC(t *testing.T) {
|
||||
for _, cipher := range []string{chacha20Poly1305ID, gcmCipherID} {
|
||||
for _, cipher := range []string{chacha20Poly1305ID, gcm128CipherID} {
|
||||
checker := &syncChecker{
|
||||
called: make(chan int, 1),
|
||||
}
|
||||
@@ -618,3 +620,93 @@ func TestNoSHA2Support(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiAlgoSignerHandshake(t *testing.T) {
|
||||
algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create multi algorithm signer: %v", err)
|
||||
}
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
return &Permissions{}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(multiAlgoSigner)
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
clientConf := &ClientConfig{
|
||||
User: "test",
|
||||
Auth: []AuthMethod{Password("testpw")},
|
||||
HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
|
||||
HostKeyAlgorithms: []string{KeyAlgoRSASHA512},
|
||||
}
|
||||
|
||||
if _, _, _, err := NewClientConn(c2, "", clientConf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiAlgoSignerNoCommonHostKeyAlgo(t *testing.T) {
|
||||
algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create multi algorithm signer: %v", err)
|
||||
}
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
// ssh-rsa is disabled server side
|
||||
serverConf := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
return &Permissions{}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(multiAlgoSigner)
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
// the client only supports ssh-rsa
|
||||
clientConf := &ClientConfig{
|
||||
User: "test",
|
||||
Auth: []AuthMethod{Password("testpw")},
|
||||
HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
|
||||
HostKeyAlgorithms: []string{KeyAlgoRSA},
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("succeeded connecting with no common hostkey algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickIncompatibleHostKeyAlgo(t *testing.T) {
|
||||
algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create multi algorithm signer: %v", err)
|
||||
}
|
||||
signer := pickHostKey([]Signer{multiAlgoSigner}, KeyAlgoRSA)
|
||||
if signer != nil {
|
||||
t.Fatal("incompatible signer returned")
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -23,6 +23,7 @@ const (
|
||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
|
||||
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
|
||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||
@@ -430,6 +431,17 @@ func init() {
|
||||
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[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||
|
||||
@@ -8,6 +8,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+326
-56
@@ -11,13 +11,16 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
@@ -26,7 +29,6 @@ import (
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
|
||||
)
|
||||
|
||||
@@ -295,6 +297,18 @@ func MarshalAuthorizedKey(key PublicKey) []byte {
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// MarshalPrivateKey returns a PEM block with the private key serialized in the
|
||||
// OpenSSH format.
|
||||
func MarshalPrivateKey(key crypto.PrivateKey, comment string) (*pem.Block, error) {
|
||||
return marshalOpenSSHPrivateKey(key, comment, unencryptedOpenSSHMarshaler)
|
||||
}
|
||||
|
||||
// MarshalPrivateKeyWithPassphrase returns a PEM block holding the encrypted
|
||||
// private key serialized in the OpenSSH format.
|
||||
func MarshalPrivateKeyWithPassphrase(key crypto.PrivateKey, comment string, passphrase []byte) (*pem.Block, error) {
|
||||
return marshalOpenSSHPrivateKey(key, comment, passphraseProtectedOpenSSHMarshaler(passphrase))
|
||||
}
|
||||
|
||||
// PublicKey represents a public key using an unspecified algorithm.
|
||||
//
|
||||
// Some PublicKeys provided by this package also implement CryptoPublicKey.
|
||||
@@ -321,7 +335,7 @@ type CryptoPublicKey interface {
|
||||
|
||||
// A Signer can create signatures that verify against a public key.
|
||||
//
|
||||
// Some Signers provided by this package also implement AlgorithmSigner.
|
||||
// Some Signers provided by this package also implement MultiAlgorithmSigner.
|
||||
type Signer interface {
|
||||
// PublicKey returns the associated PublicKey.
|
||||
PublicKey() PublicKey
|
||||
@@ -336,9 +350,9 @@ type Signer interface {
|
||||
// An AlgorithmSigner is a Signer that also supports specifying an algorithm to
|
||||
// use for signing.
|
||||
//
|
||||
// An AlgorithmSigner can't advertise the algorithms it supports, so it should
|
||||
// be prepared to be invoked with every algorithm supported by the public key
|
||||
// format.
|
||||
// An AlgorithmSigner can't advertise the algorithms it supports, unless it also
|
||||
// implements MultiAlgorithmSigner, so it should be prepared to be invoked with
|
||||
// every algorithm supported by the public key format.
|
||||
type AlgorithmSigner interface {
|
||||
Signer
|
||||
|
||||
@@ -349,6 +363,75 @@ type AlgorithmSigner interface {
|
||||
SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error)
|
||||
}
|
||||
|
||||
// MultiAlgorithmSigner is an AlgorithmSigner that also reports the algorithms
|
||||
// supported by that signer.
|
||||
type MultiAlgorithmSigner interface {
|
||||
AlgorithmSigner
|
||||
|
||||
// Algorithms returns the available algorithms in preference order. The list
|
||||
// must not be empty, and it must not include certificate types.
|
||||
Algorithms() []string
|
||||
}
|
||||
|
||||
// NewSignerWithAlgorithms returns a signer restricted to the specified
|
||||
// algorithms. The algorithms must be set in preference order. The list must not
|
||||
// be empty, and it must not include certificate types. An error is returned if
|
||||
// the specified algorithms are incompatible with the public key type.
|
||||
func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (MultiAlgorithmSigner, error) {
|
||||
if len(algorithms) == 0 {
|
||||
return nil, errors.New("ssh: please specify at least one valid signing algorithm")
|
||||
}
|
||||
var signerAlgos []string
|
||||
supportedAlgos := algorithmsForKeyFormat(underlyingAlgo(signer.PublicKey().Type()))
|
||||
if s, ok := signer.(*multiAlgorithmSigner); ok {
|
||||
signerAlgos = s.Algorithms()
|
||||
} else {
|
||||
signerAlgos = supportedAlgos
|
||||
}
|
||||
|
||||
for _, algo := range algorithms {
|
||||
if !contains(supportedAlgos, algo) {
|
||||
return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q",
|
||||
algo, signer.PublicKey().Type())
|
||||
}
|
||||
if !contains(signerAlgos, algo) {
|
||||
return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo)
|
||||
}
|
||||
}
|
||||
return &multiAlgorithmSigner{
|
||||
AlgorithmSigner: signer,
|
||||
supportedAlgorithms: algorithms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type multiAlgorithmSigner struct {
|
||||
AlgorithmSigner
|
||||
supportedAlgorithms []string
|
||||
}
|
||||
|
||||
func (s *multiAlgorithmSigner) Algorithms() []string {
|
||||
return s.supportedAlgorithms
|
||||
}
|
||||
|
||||
func (s *multiAlgorithmSigner) isAlgorithmSupported(algorithm string) bool {
|
||||
if algorithm == "" {
|
||||
algorithm = underlyingAlgo(s.PublicKey().Type())
|
||||
}
|
||||
for _, algo := range s.supportedAlgorithms {
|
||||
if algorithm == algo {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *multiAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
|
||||
if !s.isAlgorithmSupported(algorithm) {
|
||||
return nil, fmt.Errorf("ssh: algorithm %q is not supported: %v", algorithm, s.supportedAlgorithms)
|
||||
}
|
||||
return s.AlgorithmSigner.SignWithAlgorithm(rand, data, algorithm)
|
||||
}
|
||||
|
||||
type rsaPublicKey rsa.PublicKey
|
||||
|
||||
func (r *rsaPublicKey) Type() string {
|
||||
@@ -512,6 +595,10 @@ func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
return k.SignWithAlgorithm(rand, data, k.PublicKey().Type())
|
||||
}
|
||||
|
||||
func (k *dsaPrivateKey) Algorithms() []string {
|
||||
return []string{k.PublicKey().Type()}
|
||||
}
|
||||
|
||||
func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
|
||||
if algorithm != "" && algorithm != k.PublicKey().Type() {
|
||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
||||
@@ -961,13 +1048,16 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
return s.SignWithAlgorithm(rand, data, s.pubKey.Type())
|
||||
}
|
||||
|
||||
func (s *wrappedSigner) Algorithms() []string {
|
||||
return algorithmsForKeyFormat(s.pubKey.Type())
|
||||
}
|
||||
|
||||
func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
|
||||
if algorithm == "" {
|
||||
algorithm = s.pubKey.Type()
|
||||
}
|
||||
|
||||
supportedAlgos := algorithmsForKeyFormat(s.pubKey.Type())
|
||||
if !contains(supportedAlgos, algorithm) {
|
||||
if !contains(s.Algorithms(), algorithm) {
|
||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
|
||||
}
|
||||
|
||||
@@ -1087,9 +1177,9 @@ func (*PassphraseMissingError) Error() string {
|
||||
return "ssh: this private key is passphrase protected"
|
||||
}
|
||||
|
||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
||||
// supports RSA (PKCS#1), PKCS#8, DSA (OpenSSL), and ECDSA private keys. If the
|
||||
// private key is encrypted, it will return a PassphraseMissingError.
|
||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It supports
|
||||
// RSA, DSA, ECDSA, and Ed25519 private keys in PKCS#1, PKCS#8, OpenSSL, and OpenSSH
|
||||
// formats. If the private key is encrypted, it will return a PassphraseMissingError.
|
||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
@@ -1241,28 +1331,106 @@ func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func unencryptedOpenSSHMarshaler(privKeyBlock []byte) ([]byte, string, string, string, error) {
|
||||
key := generateOpenSSHPadding(privKeyBlock, 8)
|
||||
return key, "none", "none", "", nil
|
||||
}
|
||||
|
||||
func passphraseProtectedOpenSSHMarshaler(passphrase []byte) openSSHEncryptFunc {
|
||||
return func(privKeyBlock []byte) ([]byte, string, string, string, error) {
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
opts := struct {
|
||||
Salt []byte
|
||||
Rounds uint32
|
||||
}{salt, 16}
|
||||
|
||||
// Derive key to encrypt the private key block.
|
||||
k, err := bcrypt_pbkdf.Key(passphrase, salt, int(opts.Rounds), 32+aes.BlockSize)
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
// Add padding matching the block size of AES.
|
||||
keyBlock := generateOpenSSHPadding(privKeyBlock, aes.BlockSize)
|
||||
|
||||
// Encrypt the private key using the derived secret.
|
||||
|
||||
dst := make([]byte, len(keyBlock))
|
||||
key, iv := k[:32], k[32:]
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
stream.XORKeyStream(dst, keyBlock)
|
||||
|
||||
return dst, "aes256-ctr", "bcrypt", string(Marshal(opts)), nil
|
||||
}
|
||||
}
|
||||
|
||||
const privateKeyAuthMagic = "openssh-key-v1\x00"
|
||||
|
||||
type openSSHDecryptFunc func(CipherName, KdfName, KdfOpts string, PrivKeyBlock []byte) ([]byte, error)
|
||||
type openSSHEncryptFunc func(PrivKeyBlock []byte) (ProtectedKeyBlock []byte, cipherName, kdfName, kdfOptions string, err error)
|
||||
|
||||
type openSSHEncryptedPrivateKey struct {
|
||||
CipherName string
|
||||
KdfName string
|
||||
KdfOpts string
|
||||
NumKeys uint32
|
||||
PubKey []byte
|
||||
PrivKeyBlock []byte
|
||||
}
|
||||
|
||||
type openSSHPrivateKey struct {
|
||||
Check1 uint32
|
||||
Check2 uint32
|
||||
Keytype string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type openSSHRSAPrivateKey struct {
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type openSSHEd25519PrivateKey struct {
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type openSSHECDSAPrivateKey struct {
|
||||
Curve string
|
||||
Pub []byte
|
||||
D *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt
|
||||
// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used
|
||||
// as the decrypt function to parse an unencrypted private key. See
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
|
||||
func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.PrivateKey, error) {
|
||||
const magic = "openssh-key-v1\x00"
|
||||
if len(key) < len(magic) || string(key[:len(magic)]) != magic {
|
||||
if len(key) < len(privateKeyAuthMagic) || string(key[:len(privateKeyAuthMagic)]) != privateKeyAuthMagic {
|
||||
return nil, errors.New("ssh: invalid openssh private key format")
|
||||
}
|
||||
remaining := key[len(magic):]
|
||||
|
||||
var w struct {
|
||||
CipherName string
|
||||
KdfName string
|
||||
KdfOpts string
|
||||
NumKeys uint32
|
||||
PubKey []byte
|
||||
PrivKeyBlock []byte
|
||||
}
|
||||
remaining := key[len(privateKeyAuthMagic):]
|
||||
|
||||
var w openSSHEncryptedPrivateKey
|
||||
if err := Unmarshal(remaining, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1284,13 +1452,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk1 := struct {
|
||||
Check1 uint32
|
||||
Check2 uint32
|
||||
Keytype string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
var pk1 openSSHPrivateKey
|
||||
if err := Unmarshal(privKeyBlock, &pk1); err != nil || pk1.Check1 != pk1.Check2 {
|
||||
if w.CipherName != "none" {
|
||||
return nil, x509.IncorrectPasswordError
|
||||
@@ -1300,18 +1462,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
||||
|
||||
switch pk1.Keytype {
|
||||
case KeyAlgoRSA:
|
||||
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
|
||||
key := struct {
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
var key openSSHRSAPrivateKey
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1337,13 +1488,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
||||
|
||||
return pk, nil
|
||||
case KeyAlgoED25519:
|
||||
key := struct {
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
var key openSSHEd25519PrivateKey
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1360,14 +1505,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
||||
copy(pk, key.Priv)
|
||||
return &pk, nil
|
||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
||||
key := struct {
|
||||
Curve string
|
||||
Pub []byte
|
||||
D *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
var key openSSHECDSAPrivateKey
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1415,6 +1553,131 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
||||
}
|
||||
}
|
||||
|
||||
func marshalOpenSSHPrivateKey(key crypto.PrivateKey, comment string, encrypt openSSHEncryptFunc) (*pem.Block, error) {
|
||||
var w openSSHEncryptedPrivateKey
|
||||
var pk1 openSSHPrivateKey
|
||||
|
||||
// Random check bytes.
|
||||
var check uint32
|
||||
if err := binary.Read(rand.Reader, binary.BigEndian, &check); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk1.Check1 = check
|
||||
pk1.Check2 = check
|
||||
w.NumKeys = 1
|
||||
|
||||
// Use a []byte directly on ed25519 keys.
|
||||
if k, ok := key.(*ed25519.PrivateKey); ok {
|
||||
key = *k
|
||||
}
|
||||
|
||||
switch k := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
E := new(big.Int).SetInt64(int64(k.PublicKey.E))
|
||||
// Marshal public key:
|
||||
// E and N are in reversed order in the public and private key.
|
||||
pubKey := struct {
|
||||
KeyType string
|
||||
E *big.Int
|
||||
N *big.Int
|
||||
}{
|
||||
KeyAlgoRSA,
|
||||
E, k.PublicKey.N,
|
||||
}
|
||||
w.PubKey = Marshal(pubKey)
|
||||
|
||||
// Marshal private key.
|
||||
key := openSSHRSAPrivateKey{
|
||||
N: k.PublicKey.N,
|
||||
E: E,
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comment: comment,
|
||||
}
|
||||
pk1.Keytype = KeyAlgoRSA
|
||||
pk1.Rest = Marshal(key)
|
||||
case ed25519.PrivateKey:
|
||||
pub := make([]byte, ed25519.PublicKeySize)
|
||||
priv := make([]byte, ed25519.PrivateKeySize)
|
||||
copy(pub, k[32:])
|
||||
copy(priv, k)
|
||||
|
||||
// Marshal public key.
|
||||
pubKey := struct {
|
||||
KeyType string
|
||||
Pub []byte
|
||||
}{
|
||||
KeyAlgoED25519, pub,
|
||||
}
|
||||
w.PubKey = Marshal(pubKey)
|
||||
|
||||
// Marshal private key.
|
||||
key := openSSHEd25519PrivateKey{
|
||||
Pub: pub,
|
||||
Priv: priv,
|
||||
Comment: comment,
|
||||
}
|
||||
pk1.Keytype = KeyAlgoED25519
|
||||
pk1.Rest = Marshal(key)
|
||||
case *ecdsa.PrivateKey:
|
||||
var curve, keyType string
|
||||
switch name := k.Curve.Params().Name; name {
|
||||
case "P-256":
|
||||
curve = "nistp256"
|
||||
keyType = KeyAlgoECDSA256
|
||||
case "P-384":
|
||||
curve = "nistp384"
|
||||
keyType = KeyAlgoECDSA384
|
||||
case "P-521":
|
||||
curve = "nistp521"
|
||||
keyType = KeyAlgoECDSA521
|
||||
default:
|
||||
return nil, errors.New("ssh: unhandled elliptic curve " + name)
|
||||
}
|
||||
|
||||
pub := elliptic.Marshal(k.Curve, k.PublicKey.X, k.PublicKey.Y)
|
||||
|
||||
// Marshal public key.
|
||||
pubKey := struct {
|
||||
KeyType string
|
||||
Curve string
|
||||
Pub []byte
|
||||
}{
|
||||
keyType, curve, pub,
|
||||
}
|
||||
w.PubKey = Marshal(pubKey)
|
||||
|
||||
// Marshal private key.
|
||||
key := openSSHECDSAPrivateKey{
|
||||
Curve: curve,
|
||||
Pub: pub,
|
||||
D: k.D,
|
||||
Comment: comment,
|
||||
}
|
||||
pk1.Keytype = keyType
|
||||
pk1.Rest = Marshal(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
|
||||
}
|
||||
|
||||
var err error
|
||||
// Add padding and encrypt the key if necessary.
|
||||
w.PrivKeyBlock, w.CipherName, w.KdfName, w.KdfOpts, err = encrypt(Marshal(pk1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := Marshal(w)
|
||||
block := &pem.Block{
|
||||
Type: "OPENSSH PRIVATE KEY",
|
||||
Bytes: append([]byte(privateKeyAuthMagic), b...),
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func checkOpenSSHKeyPadding(pad []byte) error {
|
||||
for i, b := range pad {
|
||||
if int(b) != i+1 {
|
||||
@@ -1424,6 +1687,13 @@ func checkOpenSSHKeyPadding(pad []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateOpenSSHPadding(block []byte, blockSize int) []byte {
|
||||
for i, l := 0, len(block); (l+i)%blockSize != 0; i++ {
|
||||
block = append(block, byte(i+1))
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// FingerprintLegacyMD5 returns the user presentation of the key's
|
||||
// fingerprint as described by RFC 4716 section 4.
|
||||
func FingerprintLegacyMD5(pubKey PublicKey) string {
|
||||
|
||||
+103
-4
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
@@ -111,9 +111,9 @@ func TestKeySignVerify(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKeySignWithAlgorithmVerify(t *testing.T) {
|
||||
for _, priv := range testSigners {
|
||||
if algorithmSigner, ok := priv.(AlgorithmSigner); !ok {
|
||||
t.Errorf("Signers constructed by ssh package should always implement the AlgorithmSigner interface: %T", priv)
|
||||
for k, priv := range testSigners {
|
||||
if algorithmSigner, ok := priv.(MultiAlgorithmSigner); !ok {
|
||||
t.Errorf("Signers %q constructed by ssh package should always implement the MultiAlgorithmSigner interface: %T", k, priv)
|
||||
} else {
|
||||
pub := priv.PublicKey()
|
||||
data := []byte("sign me")
|
||||
@@ -281,6 +281,74 @@ func TestMarshalParsePublicKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPrivateKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
}{
|
||||
{"rsa-openssh-format"},
|
||||
{"ed25519"},
|
||||
{"p256-openssh-format"},
|
||||
{"p384-openssh-format"},
|
||||
{"p521-openssh-format"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
expected, ok := testPrivateKeys[tt.name]
|
||||
if !ok {
|
||||
t.Fatalf("cannot find key %s", tt.name)
|
||||
}
|
||||
|
||||
block, err := MarshalPrivateKey(expected, "test@golang.org")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot marshal %s: %v", tt.name, err)
|
||||
}
|
||||
|
||||
key, err := ParseRawPrivateKey(pem.EncodeToMemory(block))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse %s: %v", tt.name, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, key) {
|
||||
t.Errorf("unexpected marshaled key %s", tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPrivateKeyWithPassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
}{
|
||||
{"rsa-openssh-format"},
|
||||
{"ed25519"},
|
||||
{"p256-openssh-format"},
|
||||
{"p384-openssh-format"},
|
||||
{"p521-openssh-format"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
expected, ok := testPrivateKeys[tt.name]
|
||||
if !ok {
|
||||
t.Fatalf("cannot find key %s", tt.name)
|
||||
}
|
||||
|
||||
block, err := MarshalPrivateKeyWithPassphrase(expected, "test@golang.org", []byte("test-passphrase"))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot marshal %s: %v", tt.name, err)
|
||||
}
|
||||
|
||||
key, err := ParseRawPrivateKeyWithPassphrase(pem.EncodeToMemory(block), []byte("test-passphrase"))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse %s: %v", tt.name, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, key) {
|
||||
t.Errorf("unexpected marshaled key %s", tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testAuthResult struct {
|
||||
pubKey PublicKey
|
||||
options []string
|
||||
@@ -616,3 +684,34 @@ func TestSKKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSignerWithAlgos(t *testing.T) {
|
||||
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
_, err := NewSignerWithAlgorithms(algorithSigner, nil)
|
||||
if err == nil {
|
||||
t.Error("signer with algos created with no algorithms")
|
||||
}
|
||||
|
||||
_, err = NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoED25519})
|
||||
if err == nil {
|
||||
t.Error("signer with algos created with invalid algorithms")
|
||||
}
|
||||
|
||||
_, err = NewSignerWithAlgorithms(algorithSigner, []string{CertAlgoRSASHA256v01})
|
||||
if err == nil {
|
||||
t.Error("signer with algos created with certificate algorithms")
|
||||
}
|
||||
|
||||
mas, err := NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
|
||||
if err != nil {
|
||||
t.Errorf("unable to create signer with valid algorithms: %v", err)
|
||||
}
|
||||
|
||||
_, err = NewSignerWithAlgorithms(mas, []string{KeyAlgoRSA})
|
||||
if err == nil {
|
||||
t.Error("signer with algos created with restricted algorithms")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func keyEq(a, b ssh.PublicKey) bool {
|
||||
return bytes.Equal(a.Marshal(), b.Marshal())
|
||||
}
|
||||
|
||||
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
|
||||
// IsHostAuthority can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
||||
h, p, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
)
|
||||
|
||||
@@ -46,9 +47,15 @@ func (t truncatingMAC) Size() int {
|
||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
|
||||
@@ -349,6 +349,20 @@ type userAuthGSSAPIError struct {
|
||||
LanguageTag string
|
||||
}
|
||||
|
||||
// Transport layer OpenSSH extension. See [PROTOCOL], section 1.9
|
||||
const msgPing = 192
|
||||
|
||||
type pingMsg struct {
|
||||
Data string `sshtype:"192"`
|
||||
}
|
||||
|
||||
// Transport layer OpenSSH extension. See [PROTOCOL], section 1.9
|
||||
const msgPong = 193
|
||||
|
||||
type pongMsg struct {
|
||||
Data string `sshtype:"193"`
|
||||
}
|
||||
|
||||
// typeTags returns the possible type bytes for the given reflect.Type, which
|
||||
// should be a struct. The possible values are separated by a '|' character.
|
||||
func typeTags(structType reflect.Type) (tags []byte) {
|
||||
|
||||
@@ -231,6 +231,12 @@ func (m *mux) onePacket() error {
|
||||
return m.handleChannelOpen(packet)
|
||||
case msgGlobalRequest, msgRequestSuccess, msgRequestFailure:
|
||||
return m.handleGlobalPacket(packet)
|
||||
case msgPing:
|
||||
var msg pingMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal ping@openssh.com message: %w", err)
|
||||
}
|
||||
return m.sendMessage(pongMsg(msg))
|
||||
}
|
||||
|
||||
// assume a channel packet.
|
||||
|
||||
+85
-38
@@ -5,6 +5,8 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -29,14 +31,21 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
go func() {
|
||||
newCh, ok := <-s.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("No incoming channel")
|
||||
t.Error("no incoming channel")
|
||||
close(res)
|
||||
return
|
||||
}
|
||||
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()
|
||||
if err != nil {
|
||||
t.Fatalf("Accept %v", err)
|
||||
t.Errorf("accept: %v", err)
|
||||
close(res)
|
||||
return
|
||||
}
|
||||
res <- ch.(*channel)
|
||||
}()
|
||||
@@ -45,8 +54,12 @@ func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
if err != nil {
|
||||
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
|
||||
@@ -74,14 +87,14 @@ func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
||||
go func() {
|
||||
c, err := io.ReadAll(reader)
|
||||
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()
|
||||
}()
|
||||
go func() {
|
||||
c, err := io.ReadAll(reader.Stderr())
|
||||
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()
|
||||
}()
|
||||
@@ -102,11 +115,13 @@ func TestMuxReadWrite(t *testing.T) {
|
||||
go func() {
|
||||
_, err := s.Write([]byte(magic))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
t.Errorf("Write: %v", err)
|
||||
return
|
||||
}
|
||||
_, err = s.Extended(1).Write([]byte(magicExt))
|
||||
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() {
|
||||
ch, ok := <-server.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("Accept")
|
||||
t.Error("cannot accept channel")
|
||||
return
|
||||
}
|
||||
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")
|
||||
}()
|
||||
@@ -294,7 +312,7 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
defer serverPipe.Close()
|
||||
defer client.Close()
|
||||
|
||||
kDone := make(chan struct{})
|
||||
kDone := make(chan error, 1)
|
||||
go func() {
|
||||
// Ignore unknown channel messages that don't want a reply.
|
||||
err := serverPipe.writePacket(Marshal(channelRequestMsg{
|
||||
@@ -304,7 +322,8 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
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
|
||||
@@ -316,44 +335,53 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
packet, err := serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
t.Fatalf("decode failed: %v", err)
|
||||
kDone <- fmt.Errorf("decode failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelRequestFailureMsg:
|
||||
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:
|
||||
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
|
||||
// still processing requests.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgGlobalRequest {
|
||||
t.Fatalf("expected global request")
|
||||
kDone <- errors.New("expected global request")
|
||||
return
|
||||
}
|
||||
|
||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||
Data: []byte{},
|
||||
}))
|
||||
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)
|
||||
@@ -362,7 +390,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
// Wait for the server to send the keepalive message and receive back a
|
||||
// response.
|
||||
select {
|
||||
case <-kDone:
|
||||
case err := <-kDone:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("server never received ack")
|
||||
}
|
||||
@@ -373,7 +404,10 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
|
||||
}
|
||||
|
||||
select {
|
||||
case <-kDone:
|
||||
case err := <-kDone:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("server never shut down")
|
||||
}
|
||||
@@ -385,20 +419,23 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
defer serverPipe.Close()
|
||||
defer client.Close()
|
||||
|
||||
kDone := make(chan struct{})
|
||||
kDone := make(chan error, 1)
|
||||
go func() {
|
||||
// Open the channel.
|
||||
packet, err := serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgChannelOpen {
|
||||
t.Fatalf("expected chan open")
|
||||
kDone <- errors.New("expected chan open")
|
||||
return
|
||||
}
|
||||
|
||||
var openMsg channelOpenMsg
|
||||
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.
|
||||
@@ -409,7 +446,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
MaxPacketSize: channelMaxPacket,
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Close the channel.
|
||||
@@ -417,7 +455,8 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
PeersID: openMsg.PeersID,
|
||||
}))
|
||||
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.
|
||||
@@ -428,43 +467,51 @@ func TestMuxClosedChannel(t *testing.T) {
|
||||
RequestSpecificData: []byte{},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
kDone <- fmt.Errorf("send: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Receive the channel closed response.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgChannelClose {
|
||||
t.Fatalf("expected channel close")
|
||||
kDone <- errors.New("expected channel close")
|
||||
return
|
||||
}
|
||||
|
||||
// Receive the keepalive response failure.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
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
|
||||
// still processing requests.
|
||||
packet, err = serverPipe.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("read packet: %v", err)
|
||||
kDone <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
if packet[0] != msgGlobalRequest {
|
||||
t.Fatalf("expected global request")
|
||||
kDone <- errors.New("expected global request")
|
||||
return
|
||||
}
|
||||
|
||||
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||
Data: []byte{},
|
||||
}))
|
||||
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)
|
||||
|
||||
+30
-2
@@ -370,6 +370,25 @@ func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *c
|
||||
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
|
||||
// sometimes returned by NewServerConn. It appends any authentication
|
||||
// errors that may occur, and is returned if all of the authentication
|
||||
@@ -557,7 +576,16 @@ userAuthLoop:
|
||||
if !ok || len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
// Ensure the declared public key algo is compatible with the
|
||||
// decoded one. This check will ensure we don't accept e.g.
|
||||
// ssh-rsa-cert-v01@openssh.com algorithm with ssh-rsa public
|
||||
// key type. The algorithm and public key type must be
|
||||
// consistent: both must be certificate algorithms, or neither.
|
||||
if !contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
|
||||
authErr = fmt.Errorf("ssh: public key type %q not compatible with selected algorithm %q",
|
||||
pubKey.Type(), algo)
|
||||
break
|
||||
}
|
||||
// Ensure the public key algo and signature algo
|
||||
// are supported. Compare the private key
|
||||
// algorithm name that corresponds to algo with
|
||||
@@ -567,7 +595,7 @@ userAuthLoop:
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
||||
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)
|
||||
break
|
||||
}
|
||||
|
||||
+16
-3
@@ -36,7 +36,8 @@ func dial(handler serverType, t *testing.T) *Client {
|
||||
|
||||
conn, chans, reqs, err := NewServerConn(c1, &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to handshake: %v", err)
|
||||
t.Errorf("Unable to handshake: %v", err)
|
||||
return
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
@@ -647,10 +648,12 @@ func TestSessionID(t *testing.T) {
|
||||
User: "user",
|
||||
}
|
||||
|
||||
srvErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewServerConn(c1, serverConf)
|
||||
srvErrCh <- err
|
||||
if err != nil {
|
||||
t.Fatalf("server handshake: %v", err)
|
||||
return
|
||||
}
|
||||
serverID <- conn.SessionID()
|
||||
go DiscardRequests(reqs)
|
||||
@@ -659,10 +662,12 @@ func TestSessionID(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
cliErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
|
||||
cliErrCh <- err
|
||||
if err != nil {
|
||||
t.Fatalf("client handshake: %v", err)
|
||||
return
|
||||
}
|
||||
clientID <- conn.SessionID()
|
||||
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
|
||||
c := <-clientID
|
||||
if bytes.Compare(s, c) != 0 {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +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 solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
func TestAgentForward(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +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 solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
func TestBannerCallbackAgainstOpenSSH(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
|
||||
clientConf := clientConfig()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +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 solaris
|
||||
|
||||
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
|
||||
func TestCertLogin(t *testing.T) {
|
||||
s := newServer(t)
|
||||
defer s.Shutdown()
|
||||
|
||||
// Use a key different from the default.
|
||||
clientKey := testSigners["dsa"]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && !solaris && !js
|
||||
// +build !windows,!solaris,!js
|
||||
//go:build !windows && !js && !wasip1
|
||||
// +build !windows,!js,!wasip1
|
||||
|
||||
package test
|
||||
|
||||
@@ -24,7 +24,6 @@ type dialTester interface {
|
||||
|
||||
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
sshConn := server.Dial(clientConfig())
|
||||
defer sshConn.Close()
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
// +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 solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -23,7 +23,6 @@ type closeWriter interface {
|
||||
|
||||
func testPortForward(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -120,7 +119,6 @@ func TestPortForwardUnix(t *testing.T) {
|
||||
|
||||
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
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.
|
||||
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
client := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
sshListener, err := client.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
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
|
||||
// is more involved as the fd for that side is dup()ed.
|
||||
server.clientConn.Close()
|
||||
server.lastDialConn.Close()
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Errorf("timeout: listener did not close.")
|
||||
case err := <-quit:
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
err = <-quit
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
||||
|
||||
@@ -77,27 +77,27 @@ func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []stri
|
||||
func TestMultiAuth(t *testing.T) {
|
||||
testCases := []multiAuthTestCase{
|
||||
// Test password,publickey authentication, assert that password callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"password", "publickey"},
|
||||
expectedPasswordCbs: 1,
|
||||
},
|
||||
// Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"keyboard-interactive", "publickey"},
|
||||
expectedKbdIntCbs: 1,
|
||||
},
|
||||
// Test publickey,password authentication, assert that password callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"publickey", "password"},
|
||||
expectedPasswordCbs: 1,
|
||||
},
|
||||
// Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"publickey", "keyboard-interactive"},
|
||||
expectedKbdIntCbs: 1,
|
||||
},
|
||||
// Test password,password authentication, assert that password callback is called 2 times
|
||||
multiAuthTestCase{
|
||||
{
|
||||
authMethods: []string{"password", "password"},
|
||||
expectedPasswordCbs: 2,
|
||||
},
|
||||
@@ -108,7 +108,6 @@ func TestMultiAuth(t *testing.T) {
|
||||
ctx := newMultiAuthTestCtx(t)
|
||||
|
||||
server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
|
||||
defer server.Shutdown()
|
||||
|
||||
clientConfig := clientConfig()
|
||||
server.setTestPassword(clientConfig.User, ctx.password)
|
||||
|
||||
+27
-21
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && !solaris && !js
|
||||
// +build !windows,!solaris,!js
|
||||
//go:build !windows && !js && !wasip1
|
||||
// +build !windows,!js,!wasip1
|
||||
|
||||
package test
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -23,7 +25,6 @@ import (
|
||||
|
||||
func TestRunCommandSuccess(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -40,7 +41,6 @@ func TestRunCommandSuccess(t *testing.T) {
|
||||
|
||||
func TestHostKeyCheck(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
@@ -62,7 +62,6 @@ func TestHostKeyCheck(t *testing.T) {
|
||||
|
||||
func TestRunCommandStdin(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -85,7 +84,6 @@ func TestRunCommandStdin(t *testing.T) {
|
||||
|
||||
func TestRunCommandStdinError(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -109,7 +107,6 @@ func TestRunCommandStdinError(t *testing.T) {
|
||||
|
||||
func TestRunCommandFailed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -126,7 +123,6 @@ func TestRunCommandFailed(t *testing.T) {
|
||||
|
||||
func TestRunCommandWeClosed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -146,7 +142,6 @@ func TestRunCommandWeClosed(t *testing.T) {
|
||||
|
||||
func TestFuncLargeRead(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -178,7 +173,6 @@ func TestFuncLargeRead(t *testing.T) {
|
||||
|
||||
func TestKeyChange(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
conf.HostKeyCallback = hostDB.Check
|
||||
@@ -225,7 +219,6 @@ func TestValidTerminalMode(t *testing.T) {
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -255,15 +248,31 @@ func TestValidTerminalMode(t *testing.T) {
|
||||
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
|
||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
||||
buf := new(strings.Builder)
|
||||
if _, err := io.Copy(buf, stdout); err != nil {
|
||||
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 ") {
|
||||
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)
|
||||
}
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
@@ -322,7 +330,6 @@ func TestWindowChange(t *testing.T) {
|
||||
|
||||
func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.Ciphers = []string{cipher}
|
||||
// Don't fail if sshd doesn't have the cipher.
|
||||
@@ -381,7 +388,6 @@ func TestMACs(t *testing.T) {
|
||||
for _, mac := range macOrder {
|
||||
t.Run(mac, func(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.MACs = []string{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
|
||||
// here manually.
|
||||
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 {
|
||||
t.Run(kex, func(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
// Don't fail if sshd doesn't have the kex.
|
||||
conf.KeyExchanges = append([]string{kex}, kexOrder...)
|
||||
@@ -442,8 +450,6 @@ func TestClientAuthAlgorithms(t *testing.T) {
|
||||
} else {
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9
|
||||
// +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 solaris
|
||||
|
||||
package test
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/crypto/internal/testenv"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
@@ -67,17 +68,13 @@ var configTmpl = map[string]*template.Template{
|
||||
|
||||
type server struct {
|
||||
t *testing.T
|
||||
cleanup func() // executed during Shutdown
|
||||
configfile string
|
||||
cmd *exec.Cmd
|
||||
output bytes.Buffer // holds stderr from sshd process
|
||||
|
||||
testUser string // test username for sshd
|
||||
testPasswd string // test password for sshd
|
||||
sshdTestPwSo string // dynamic library to inject a custom password into sshd
|
||||
|
||||
// Client half of the network connection.
|
||||
clientConn net.Conn
|
||||
lastDialConn net.Conn
|
||||
}
|
||||
|
||||
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.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
|
||||
cmd := testenv.Command(s.t, sshd, "-f", s.configfile, "-i", "-e")
|
||||
f, err := c2.File()
|
||||
if err != nil {
|
||||
s.t.Fatalf("UnixConn.File: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
s.cmd.Stdin = f
|
||||
s.cmd.Stdout = f
|
||||
s.cmd.Stderr = &s.output
|
||||
cmd.Stdin = f
|
||||
cmd.Stdout = f
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
|
||||
if s.sshdTestPwSo != "" {
|
||||
if s.testUser == "" {
|
||||
@@ -210,18 +207,29 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
||||
if s.testPasswd == "" {
|
||||
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("TEST_USER=%s", s.testUser),
|
||||
fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
|
||||
}
|
||||
|
||||
if err := s.cmd.Start(); err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
if err := cmd.Start(); err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
conn, err := s.TryDial(config)
|
||||
if err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
s.t.Fatalf("ssh.Client: %v", err)
|
||||
}
|
||||
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) {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
@@ -351,15 +341,15 @@ func newServerForConfig(t *testing.T, config string, configVars map[string]strin
|
||||
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
}
|
||||
writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
|
||||
t.Cleanup(func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
return &server{
|
||||
t: t,
|
||||
configfile: f.Name(),
|
||||
cleanup: func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ import (
|
||||
const debugTransport = false
|
||||
|
||||
const (
|
||||
gcmCipherID = "aes128-gcm@openssh.com"
|
||||
gcm128CipherID = "aes128-gcm@openssh.com"
|
||||
gcm256CipherID = "aes256-gcm@openssh.com"
|
||||
aes128cbcID = "aes128-cbc"
|
||||
tripledescbcID = "3des-cbc"
|
||||
)
|
||||
|
||||
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