Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e98487292d | ||
|
|
183630ada7 | ||
|
|
a9e447dde7 | ||
|
|
64c3993f5c | ||
|
|
5fe8145aca | ||
|
|
043e94c17a | ||
|
|
0d502d7cd6 | ||
|
|
0ff60057bb | ||
|
|
8e447d8cc5 | ||
|
|
22c9997952 | ||
|
|
d0b316056b | ||
|
|
16222386f4 | ||
|
|
a4e984136a | ||
|
|
3ef8056490 | ||
|
|
a8cc953517 | ||
|
|
7d6d3f5d4a | ||
|
|
1faeef9713 | ||
|
|
00fd4ff485 | ||
|
|
018c28f8a1 | ||
|
|
9cd0187c09 | ||
|
|
c6a20f9984 | ||
|
|
776e461a4e | ||
|
|
ebe92624d1 |
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.6.0
|
||||
golang.org/x/sys v0.5.0
|
||||
golang.org/x/term v0.5.0
|
||||
golang.org/x/net v0.10.0 // tagx:ignore
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/term v0.10.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.7.0 // indirect
|
||||
require golang.org/x/text v0.11.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.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||
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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.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.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.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
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
"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
|
||||
|
||||
+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{}
|
||||
|
||||
+6
-5
@@ -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",
|
||||
}
|
||||
@@ -85,7 +85,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-512-etm@openssh.com", "hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
@@ -168,7 +168,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 +178,8 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
|
||||
}
|
||||
|
||||
var aeadCiphers = map[string]bool{
|
||||
gcmCipherID: true,
|
||||
gcm128CipherID: true,
|
||||
gcm256CipherID: true,
|
||||
chacha20Poly1305ID: true,
|
||||
}
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -421,8 +421,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 +562,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),
|
||||
}
|
||||
|
||||
+3
-3
@@ -1087,9 +1087,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 {
|
||||
|
||||
@@ -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)
|
||||
}},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
+24
-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 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 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.
|
||||
@@ -407,7 +413,6 @@ func TestKeyExchanges(t *testing.T) {
|
||||
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 +447,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