Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1c0d99298 | ||
|
|
3c7c86938f | ||
|
|
533fb3f7e4 | ||
|
|
abbc44d451 | ||
|
|
e052873987 | ||
|
|
b61cf853a8 | ||
|
|
9c2cd33e8d | ||
|
|
890731877d | ||
|
|
ffd87b4878 | ||
|
|
4e7a7384ec | ||
|
|
b25012b37b | ||
|
|
6c195c8a97 | ||
|
|
f717e29698 | ||
|
|
e7c36ccb47 | ||
|
|
0fb843a472 | ||
|
|
e3d1254f1e | ||
|
|
a1ce0fee12 | ||
|
|
a749d17980 | ||
|
|
7ee59707ff | ||
|
|
44decbfe70 | ||
|
|
b8a14a8d65 | ||
|
|
9d9d507896 | ||
|
|
fd0b90d21f | ||
|
|
b9e53593a6 | ||
|
|
cc0e4fc1d4 | ||
|
|
a8e9237a21 | ||
|
|
03ca0dcccb | ||
|
|
8400f4a938 | ||
|
|
81c6cb34a8 | ||
|
|
982eaa62df | ||
|
|
159944f128 | ||
|
|
a408498e55 | ||
|
|
cab0f71854 | ||
|
|
2f26647a79 | ||
|
|
e08b06753d | ||
|
|
7d0074ccc6 | ||
|
|
506e022208 | ||
|
|
7dacc380ba | ||
|
|
19acf81bd7 | ||
|
|
3a1c6b4b61 | ||
|
|
f4602e4040 |
@@ -248,10 +248,6 @@ func (m *Manager) TLSConfig() *tls.Config {
|
||||
// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
|
||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
|
||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if m.Prompt == nil {
|
||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
||||
}
|
||||
|
||||
name := hello.ServerName
|
||||
if name == "" {
|
||||
return nil, errors.New("acme/autocert: missing server name")
|
||||
|
||||
@@ -201,7 +201,7 @@ func TestGetCertificate(t *testing.T) {
|
||||
prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
|
||||
man.Prompt = nil
|
||||
},
|
||||
expectError: "Manager.Prompt not set",
|
||||
expectError: "missing Manager.Prompt",
|
||||
},
|
||||
{
|
||||
name: "trailingDot",
|
||||
|
||||
@@ -239,7 +239,8 @@ type discovery struct {
|
||||
}
|
||||
|
||||
type discoveryMeta struct {
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
Terms string `json:"termsOfService,omitempty"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
@@ -281,6 +282,7 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
NewAccount: ca.serverURL("/new-account"),
|
||||
NewOrder: ca.serverURL("/new-order"),
|
||||
Meta: discoveryMeta{
|
||||
Terms: ca.serverURL("/terms"),
|
||||
ExternalAccountRequired: ca.eabRequired,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -180,9 +180,6 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
|
||||
// set internal state to an almost expired cert
|
||||
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -53,6 +53,9 @@ func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tos
|
||||
Contact: acct.Contact,
|
||||
}
|
||||
if c.dir.Terms != "" {
|
||||
if prompt == nil {
|
||||
return nil, errors.New("acme: missing Manager.Prompt to accept server's terms of service")
|
||||
}
|
||||
req.TermsAgreed = prompt(c.dir.Terms)
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -167,7 +167,11 @@ type OrderError struct {
|
||||
}
|
||||
|
||||
func (oe *OrderError) Error() string {
|
||||
return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||
str := fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||
if oe.Problem != nil {
|
||||
str += fmt.Sprintf("; problem: %s", oe.Problem)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// RateLimit reports whether err represents a rate limit error and
|
||||
|
||||
@@ -217,3 +217,19 @@ func TestErrorStringerWithSubproblems(t *testing.T) {
|
||||
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderErrorProblem(t *testing.T) {
|
||||
oe := &OrderError{
|
||||
OrderURL: "url",
|
||||
Status: "invalid",
|
||||
Problem: &Error{
|
||||
StatusCode: 400,
|
||||
ProblemType: "type",
|
||||
Detail: "detail",
|
||||
},
|
||||
}
|
||||
want := "acme: order url status: invalid; problem: 400 type: detail"
|
||||
if got := oe.Error(); got != want {
|
||||
t.Errorf("oe.Error() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ hash.XOF = (*xof)(nil)
|
||||
|
||||
func fromHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,9 @@ type chacha20poly1305 struct {
|
||||
|
||||
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||
func New(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool
|
||||
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
|
||||
|
||||
var (
|
||||
useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
||||
useAVX2 = cpu.X86.HasSSSE3 && cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
||||
)
|
||||
|
||||
// setupState writes a ChaCha20 input matrix to state. See
|
||||
@@ -47,7 +47,7 @@ func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if !cpu.X86.HasSSSE3 {
|
||||
if !useAVX2 {
|
||||
return c.sealGeneric(dst, nonce, plaintext, additionalData)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if !cpu.X86.HasSSSE3 {
|
||||
if !useAVX2 {
|
||||
return c.openGeneric(dst, nonce, ciphertext, additionalData)
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +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.25
|
||||
//go:build !go1.26
|
||||
|
||||
package blake2b
|
||||
package chacha20poly1305
|
||||
|
||||
import "hash"
|
||||
|
||||
var _ hash.XOF = (*xof)(nil)
|
||||
func fips140Enforced() bool { return false }
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 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.26
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
import "crypto/fips140"
|
||||
|
||||
func fips140Enforced() bool { return fips140.Enforced() }
|
||||
@@ -22,6 +22,9 @@ type xchacha20poly1305 struct {
|
||||
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
|
||||
// nonces are randomly generated.
|
||||
func NewX(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module golang.org/x/crypto
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.47.0 // tagx:ignore
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/net v0.54.0 // tagx:ignore
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/term v0.43.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.31.0 // indirect
|
||||
require golang.org/x/text v0.37.0 // indirect
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
|
||||
+13
-8
@@ -11,6 +11,7 @@
|
||||
package hkdf
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -24,15 +25,19 @@ import (
|
||||
// Expand invocations and different context values. Most common scenarios,
|
||||
// including the generation of multiple keys, should use New instead.
|
||||
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
||||
if salt == nil {
|
||||
salt = make([]byte, hash().Size())
|
||||
// Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
|
||||
// key (which in HKDF is the salt). The only possible error is FIPS 140
|
||||
// enforcement of the hash, which had to panic under this API anyway. We
|
||||
// don't use the stdlib Expand, because it switched to returning a []byte
|
||||
// instead of an io.Reader, and Expand uses the HMAC key as a key.
|
||||
out, err := hkdf.Extract(hash, secret, salt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
extractor := hmac.New(hash, salt)
|
||||
extractor.Write(secret)
|
||||
return extractor.Sum(nil)
|
||||
return out
|
||||
}
|
||||
|
||||
type hkdf struct {
|
||||
type hkdfReader struct {
|
||||
expander hash.Hash
|
||||
size int
|
||||
|
||||
@@ -43,7 +48,7 @@ type hkdf struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (f *hkdf) Read(p []byte) (int, error) {
|
||||
func (f *hkdfReader) Read(p []byte) (int, error) {
|
||||
// Check whether enough data can be generated
|
||||
need := len(p)
|
||||
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
||||
@@ -84,7 +89,7 @@ func (f *hkdf) Read(p []byte) (int, error) {
|
||||
// 3.3. Most common scenarios will want to use New instead.
|
||||
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
||||
expander := hmac.New(hash, pseudorandomKey)
|
||||
return &hkdf{expander, expander.Size(), info, 1, nil, nil}
|
||||
return &hkdfReader{expander, expander.Size(), info, 1, nil, nil}
|
||||
}
|
||||
|
||||
// New returns a Reader, from which keys can be read, using the given hash,
|
||||
|
||||
+13
-60
@@ -2,24 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
// Package pbkdf2 implements the key derivation function PBKDF2 as defined in
|
||||
// RFC 8018 (PKCS #5 v2.1).
|
||||
//
|
||||
// This package is a wrapper for the PBKDF2 implementation in the
|
||||
// [crypto/pbkdf2] package. It is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package pbkdf2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/pbkdf2"
|
||||
"hash"
|
||||
)
|
||||
|
||||
@@ -27,51 +20,11 @@ import (
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
out, err := pbkdf2.Key(h, string(password), salt, iter, keyLen)
|
||||
if err != nil {
|
||||
// FIPS 140 enforcement, or an invalid key length.
|
||||
panic(err)
|
||||
}
|
||||
return dk[:keyLen]
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -196,6 +196,9 @@ func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) {
|
||||
if N <= 1 || N&(N-1) != 0 {
|
||||
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
|
||||
}
|
||||
if r <= 0 || p <= 0 {
|
||||
return nil, errors.New("scrypt: parameters must be > 0")
|
||||
}
|
||||
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
|
||||
return nil, errors.New("scrypt: parameters are too large")
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ var bad = []testVector{
|
||||
{"p", "s", 1, 1, 1, nil}, // N == 1
|
||||
{"p", "s", 7, 8, 1, nil}, // N is not power of 2
|
||||
{"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large
|
||||
{"p", "s", 2, 0, 1, nil}, // r too small
|
||||
{"p", "s", 2, 1, 0, nil}, // p too small
|
||||
{"p", "s", 2, -1, 1, nil}, // r is negative
|
||||
{"p", "s", 2, 1, -1, nil}, // p is negative
|
||||
}
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
|
||||
@@ -663,6 +663,13 @@ func (c *client) Add(key AddedKey) error {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
for _, ext := range key.ConstraintExtensions {
|
||||
constraints = append(constraints, ssh.Marshal(constrainExtensionAgentMsg{
|
||||
ExtensionName: ext.ExtensionName,
|
||||
ExtensionDetails: ext.ExtensionDetails,
|
||||
})...)
|
||||
}
|
||||
|
||||
cert := key.Certificate
|
||||
if cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
|
||||
@@ -40,7 +40,11 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-s")
|
||||
cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior.
|
||||
cmd.Env = []string{
|
||||
// ssh-agent creates ~/.ssh and ~/.ssh/agent;
|
||||
// ensure a writeable home directory.
|
||||
"HOME=" + t.TempDir(),
|
||||
} // Do not let the user's environment influence ssh-agent behavior.
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
@@ -590,3 +594,63 @@ func TestAgentExtensions(t *testing.T) {
|
||||
t.Fatal("should have gotten agent extension failure")
|
||||
}
|
||||
}
|
||||
|
||||
// capturingAgent records the AddedKey observed on the server side of the
|
||||
// agent protocol. It forwards to a keyring with constraint fields stripped,
|
||||
// so the keyring's own rejection of unsupported constraints does not
|
||||
// interfere with what this test is measuring: the client-side wire
|
||||
// serialization of ConstraintExtensions.
|
||||
type capturingAgent struct {
|
||||
Agent
|
||||
lastAdd AddedKey
|
||||
}
|
||||
|
||||
func newCapturingAgent() *capturingAgent {
|
||||
return &capturingAgent{Agent: NewKeyring()}
|
||||
}
|
||||
|
||||
func (a *capturingAgent) Add(key AddedKey) error {
|
||||
a.lastAdd = key
|
||||
stripped := key
|
||||
stripped.ConstraintExtensions = nil
|
||||
stripped.ConfirmBeforeUse = false
|
||||
return a.Agent.Add(stripped)
|
||||
}
|
||||
|
||||
// TestAddConstraintExtensionsWireFormat verifies that client.Add serializes
|
||||
// ConstraintExtensions into the SSH_AGENTC_ADD_IDENTITY payload and the
|
||||
// server deserializes them back into the AddedKey delivered to the backend.
|
||||
// Regressions in the client marshal loop (missing, swapped fields, wrong
|
||||
// framing) would be invisible to a keyring-based rejection test, which
|
||||
// signals only "extensions were present", not "the right ones arrived".
|
||||
func TestAddConstraintExtensionsWireFormat(t *testing.T) {
|
||||
capturing := newCapturingAgent()
|
||||
client, cleanup := startAgent(t, capturing)
|
||||
defer cleanup()
|
||||
|
||||
constraints := []ConstraintExtension{
|
||||
{ExtensionName: "ext-one@example.com", ExtensionDetails: []byte("details-one")},
|
||||
{ExtensionName: "ext-two@example.com", ExtensionDetails: []byte("\x00\x01\x02\xff")},
|
||||
}
|
||||
|
||||
if err := client.Add(AddedKey{
|
||||
PrivateKey: testPrivateKeys["rsa"],
|
||||
Comment: "wire-format-test",
|
||||
ConstraintExtensions: constraints,
|
||||
}); err != nil {
|
||||
t.Fatalf("client.Add: %v", err)
|
||||
}
|
||||
|
||||
got := capturing.lastAdd.ConstraintExtensions
|
||||
if len(got) != len(constraints) {
|
||||
t.Fatalf("server received %d extensions, want %d", len(got), len(constraints))
|
||||
}
|
||||
for i, want := range constraints {
|
||||
if got[i].ExtensionName != want.ExtensionName {
|
||||
t.Errorf("extension[%d] name: got %q, want %q", i, got[i].ExtensionName, want.ExtensionName)
|
||||
}
|
||||
if !bytes.Equal(got[i].ExtensionDetails, want.ExtensionDetails) {
|
||||
t.Errorf("extension[%d] details: got %x, want %x", i, got[i].ExtensionDetails, want.ExtensionDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-5
@@ -32,8 +32,10 @@ type keyring struct {
|
||||
|
||||
var errLocked = errors.New("agent: locked")
|
||||
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe for
|
||||
// concurrent use by multiple goroutines.
|
||||
//
|
||||
// The returned Agent only supports the "lifetime" constraint.
|
||||
func NewKeyring() Agent {
|
||||
return &keyring{}
|
||||
}
|
||||
@@ -143,15 +145,26 @@ func (r *keyring) List() ([]*Key, error) {
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the keyring. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
// Add adds a private key to the keyring. If a certificate is given, that
|
||||
// certificate is added as public key.
|
||||
//
|
||||
// Add returns an error if key contains ConstraintExtensions or
|
||||
// ConfirmBeforeUse.
|
||||
func (r *keyring) Add(key AddedKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
return errors.New("agent: confirm before use constraint is not supported")
|
||||
}
|
||||
|
||||
if len(key.ConstraintExtensions) > 0 {
|
||||
return errors.New("agent: constraint extensions are present but not supported")
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -120,3 +120,42 @@ func TestAddDuplicateKey(t *testing.T) {
|
||||
t.Fatal("key with the updated comment not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddKeyWithConstraints(t *testing.T) {
|
||||
// Verifies the keyring refuses keys carrying constraint extensions it
|
||||
// cannot enforce.
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
constraints := []ConstraintExtension{
|
||||
{
|
||||
ExtensionName: "extension1",
|
||||
ExtensionDetails: []byte("details1"),
|
||||
},
|
||||
}
|
||||
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
err := agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
ConstraintExtensions: constraints,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("adding a key with unsupported constraints succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddKeyWithConfirmBeforeUse(t *testing.T) {
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
err := agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
ConfirmBeforeUse: true,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("adding a key with confirm before use constraint succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -36,7 +36,7 @@ func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||
return []byte{agentFailure}
|
||||
}
|
||||
|
||||
if err == nil && rep == nil {
|
||||
if rep == nil {
|
||||
return []byte{agentSuccess}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,9 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
|
||||
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||
@@ -336,6 +339,9 @@ func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
|
||||
@@ -271,3 +271,35 @@ func TestParseConstraints(t *testing.T) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEd25519KeyShortPanic(t *testing.T) {
|
||||
msg := ssh.Marshal(ed25519KeyMsg{
|
||||
Type: ssh.KeyAlgoED25519,
|
||||
Pub: []byte{1, 2, 3},
|
||||
Priv: []byte{1, 2, 3, 4, 5},
|
||||
})
|
||||
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- ServeAgent(NewKeyring(), a) }()
|
||||
|
||||
c := NewClient(b)
|
||||
_, err = c.(*client).call(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keys, err := c.List()
|
||||
if err != nil {
|
||||
t.Fatalf("agent died: %v", err)
|
||||
}
|
||||
if len(keys) != 0 {
|
||||
t.Error("short ed25519 key was accepted into keyring")
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -348,6 +348,9 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
||||
if cert.CertType != HostCert {
|
||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||
}
|
||||
if c.IsHostAuthority == nil {
|
||||
return errors.New("ssh: cannot verify certificate, IsHostAuthority not set")
|
||||
}
|
||||
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||
}
|
||||
@@ -375,6 +378,9 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
||||
if cert.CertType != UserCert {
|
||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||
}
|
||||
if c.IsUserAuthority == nil {
|
||||
return nil, errors.New("ssh: cannot verify certificate, IsUserAuthority not set")
|
||||
}
|
||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
@@ -438,7 +444,17 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||
return fmt.Errorf("ssh: cert has expired")
|
||||
}
|
||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||
// Match OpenSSH: the SK user-presence flag is never enforced on a
|
||||
// certificate's CA signature. OpenSSH calls sshkey_verify with
|
||||
// detailsp==NULL in sshkey.c:cert_parse, so the UP/UV flags are
|
||||
// not even extracted. The UP bit on a CA signature reflects the
|
||||
// CA operator's presence at signing time, which has no bearing on
|
||||
// whether the user being authenticated is present now; enforcing
|
||||
// it here would only break interop with certificates issued by
|
||||
// non-interactive SK CAs. skKeyWithoutUP is a no-op for non-SK
|
||||
// keys (the common case).
|
||||
caKey := skKeyWithoutUP(cert.SignatureKey)
|
||||
if err := caKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||
}
|
||||
|
||||
|
||||
+99
-4
@@ -9,8 +9,10 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -128,6 +130,103 @@ func TestValidateCert(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// signSKCert builds an SK-ECDSA signature over cert.bytesForSigning()
|
||||
// using caKey as the simulated hardware token. The SK signing flags
|
||||
// byte (UP bit and others) is caller-controlled so tests can exercise
|
||||
// the user-presence enforcement paths. caApp is the SK application
|
||||
// string (typically "ssh:").
|
||||
func signSKCert(t *testing.T, cert *Certificate, caKey *ecdsa.PrivateKey, caApp string, flags byte) {
|
||||
t.Helper()
|
||||
cert.SignatureKey = &skECDSAPublicKey{
|
||||
application: caApp,
|
||||
PublicKey: caKey.PublicKey,
|
||||
}
|
||||
cert.Nonce = make([]byte, 32)
|
||||
if _, err := rand.Read(cert.Nonce); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(caApp))
|
||||
appDigest := h.Sum(nil)
|
||||
h.Reset()
|
||||
h.Write(cert.bytesForSigning())
|
||||
dataDigest := h.Sum(nil)
|
||||
|
||||
var counter uint32 = 1
|
||||
blob := struct {
|
||||
ApplicationDigest []byte `ssh:"rest"`
|
||||
Flags byte
|
||||
Counter uint32
|
||||
MessageDigest []byte `ssh:"rest"`
|
||||
}{appDigest, flags, counter, dataDigest}
|
||||
h.Reset()
|
||||
h.Write(Marshal(blob))
|
||||
digest := h.Sum(nil)
|
||||
|
||||
r, s, err := ecdsa.Sign(rand.Reader, caKey, digest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert.Signature = &Signature{
|
||||
Format: KeyAlgoSKECDSA256,
|
||||
Blob: Marshal(struct{ R, S *big.Int }{r, s}),
|
||||
Rest: Marshal(struct {
|
||||
Flags byte
|
||||
Counter uint32
|
||||
}{flags, counter}),
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckCertSKAuthorityNoUPRequired pins the OpenSSH-parity
|
||||
// behavior of CertChecker.CheckCert for SK CA signatures: a
|
||||
// certificate signed by an SK CA that produced a UP=0 signature is
|
||||
// accepted, because the UP bit on a CA signature has no bearing on
|
||||
// the current user authentication (see sshkey.c:cert_parse, which
|
||||
// passes detailsp==NULL to sshkey_verify). Without this, certs issued
|
||||
// by non-interactive SK CAs would fail to verify.
|
||||
func TestCheckCertSKAuthorityNoUPRequired(t *testing.T) {
|
||||
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userPub, err := NewPublicKey(&userKey.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mkCert := func(flags byte) *Certificate {
|
||||
c := &Certificate{
|
||||
CertType: UserCert,
|
||||
Key: userPub,
|
||||
ValidBefore: CertTimeInfinity,
|
||||
ValidPrincipals: []string{"user"},
|
||||
}
|
||||
signSKCert(t, c, caKey, "ssh:", flags)
|
||||
return c
|
||||
}
|
||||
|
||||
checker := CertChecker{
|
||||
IsUserAuthority: func(k PublicKey) bool {
|
||||
caSK := &skECDSAPublicKey{application: "ssh:", PublicKey: caKey.PublicKey}
|
||||
return bytes.Equal(k.Marshal(), caSK.Marshal())
|
||||
},
|
||||
}
|
||||
|
||||
// Both UP=0 and UP=1 CA signatures verify: UP is not enforced
|
||||
// here, matching OpenSSH.
|
||||
if err := checker.CheckCert("user", mkCert(0)); err != nil {
|
||||
t.Errorf("UP=0 CA signature must verify (OpenSSH parity): %v", err)
|
||||
}
|
||||
if err := checker.CheckCert("user", mkCert(flagUserPresence)); err != nil {
|
||||
t.Errorf("UP=1 CA signature must verify: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCertTime(t *testing.T) {
|
||||
cert := Certificate{
|
||||
ValidPrincipals: []string{"user"},
|
||||
@@ -330,10 +429,6 @@ func TestCertTypes(t *testing.T) {
|
||||
go NewServerConn(c1, conf)
|
||||
|
||||
priv := m.signer
|
||||
if err != nil {
|
||||
t.Fatalf("error generating ssh pubkey: %v", err)
|
||||
}
|
||||
|
||||
cert := &Certificate{
|
||||
CertType: UserCert,
|
||||
Key: priv.PublicKey(),
|
||||
|
||||
+59
-6
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -131,11 +132,17 @@ func (r RejectionReason) String() string {
|
||||
return fmt.Sprintf("unknown reason %d", int(r))
|
||||
}
|
||||
|
||||
func min(a uint32, b int) uint32 {
|
||||
if a < uint32(b) {
|
||||
return a
|
||||
// minPayloadSize returns min(limit, length) clamped to a uint32. It is used
|
||||
// to compute the size of the next channel data packet from the remaining
|
||||
// payload. The comparison is done in int64 because length is an int — on
|
||||
// 64-bit systems len(data) can exceed 2^32, and a direct uint32(length)
|
||||
// cast would silently truncate to 0 at every multiple of 2^32, causing
|
||||
// WriteExtended's loop to spin without making progress.
|
||||
func minPayloadSize(limit uint32, length int) uint32 {
|
||||
if int64(length) > int64(limit) {
|
||||
return limit
|
||||
}
|
||||
return uint32(b)
|
||||
return uint32(length)
|
||||
}
|
||||
|
||||
type channelDirection uint8
|
||||
@@ -177,6 +184,12 @@ type channel struct {
|
||||
// with WantReply=true outstanding. This lock is held by a
|
||||
// goroutine that has such an outgoing request pending.
|
||||
sentRequestMu sync.Mutex
|
||||
// sentRequestPending is set to true while a SendRequest call with
|
||||
// WantReply=true is in flight. handlePacket uses it as a gate: responses
|
||||
// arriving while no request is pending are dropped to prevent a
|
||||
// misbehaving peer from stalling the mux read loop by filling ch.msg
|
||||
// with unsolicited channelRequestSuccess/Failure messages.
|
||||
sentRequestPending atomic.Bool
|
||||
|
||||
incomingRequests chan *Request
|
||||
|
||||
@@ -251,7 +264,7 @@ func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err e
|
||||
ch.writeMu.Unlock()
|
||||
|
||||
for len(data) > 0 {
|
||||
space := min(ch.maxRemotePayload, len(data))
|
||||
space := minPayloadSize(ch.maxRemotePayload, len(data))
|
||||
if space, err = ch.remoteWin.reserve(space); err != nil {
|
||||
return n, err
|
||||
}
|
||||
@@ -460,6 +473,18 @@ func (ch *channel) handlePacket(packet []byte) error {
|
||||
}
|
||||
|
||||
ch.incomingRequests <- &req
|
||||
case *channelRequestSuccessMsg, *channelRequestFailureMsg:
|
||||
// Drop responses that arrive when no SendRequest is waiting, to
|
||||
// prevent a malicious peer from filling ch.msg and stalling the
|
||||
// mux read loop. The non-blocking send additionally protects the
|
||||
// loop if a well-behaved caller is slow to read.
|
||||
if !ch.sentRequestPending.Load() {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case ch.msg <- msg:
|
||||
default:
|
||||
}
|
||||
default:
|
||||
ch.msg <- msg
|
||||
}
|
||||
@@ -530,7 +555,17 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||
Language: "en",
|
||||
}
|
||||
ch.decided = true
|
||||
return ch.sendMessage(reject)
|
||||
err := ch.sendMessage(reject)
|
||||
|
||||
// Remove the channel from the mux to prevent memory leaks.
|
||||
// Do not call ch.close() here: no goroutine holds a reference to a
|
||||
// rejected channel's internal channels (msg, incomingRequests), so
|
||||
// removing it from chanList is sufficient for GC. Calling close()
|
||||
// would race with the mux loop goroutine (handlePacket or dropAll),
|
||||
// causing a panic from closing an already-closed channel.
|
||||
ch.mux.chanList.remove(ch.localId)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ch *channel) Read(data []byte) (int, error) {
|
||||
@@ -586,6 +621,24 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
|
||||
if wantReply {
|
||||
ch.sentRequestMu.Lock()
|
||||
defer ch.sentRequestMu.Unlock()
|
||||
|
||||
// Open the gate so that responses arriving while this request is in
|
||||
// flight are allowed to reach ch.msg. Responses arriving while no
|
||||
// request is pending are dropped by handlePacket.
|
||||
ch.sentRequestPending.Store(true)
|
||||
defer ch.sentRequestPending.Store(false)
|
||||
|
||||
// Drain any spurious responses that may have been buffered. This
|
||||
// prevents a previously buffered unexpected response from being
|
||||
// consumed instead of the actual response for this request.
|
||||
drain:
|
||||
for {
|
||||
select {
|
||||
case <-ch.msg:
|
||||
default:
|
||||
break drain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg := channelRequestMsg{
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestMinPayloadSize(t *testing.T) {
|
||||
// 4 GiB (2^32). Declared as a var (not a const) so that int(bigPayload)
|
||||
// is a runtime conversion: a constant conversion would fail to compile
|
||||
// on 32-bit platforms with "constant 4294967296 overflows int". On
|
||||
// 32-bit the value truncates to 0 at runtime, but the is64Bit cases
|
||||
// that reference it are skipped by the runtime check below.
|
||||
var bigPayload int64 = 1 << 32
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
maxPayload uint32
|
||||
dataLen int
|
||||
want uint32
|
||||
is64Bit bool // Flag to run only on 64-bit architectures
|
||||
}{
|
||||
{
|
||||
name: "Normal Case - Data fits in payload",
|
||||
maxPayload: 32768,
|
||||
dataLen: 1000,
|
||||
want: 1000,
|
||||
},
|
||||
{
|
||||
name: "Normal Case - Data larger than payload",
|
||||
maxPayload: 32768,
|
||||
dataLen: 50000,
|
||||
want: 32768,
|
||||
},
|
||||
{
|
||||
name: "Boundary Case - Data zero",
|
||||
maxPayload: 32768,
|
||||
dataLen: 0,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "Overflow Case - Data is exactly 4GB (1<<32)",
|
||||
maxPayload: 32768,
|
||||
dataLen: int(bigPayload),
|
||||
want: 32768,
|
||||
is64Bit: true,
|
||||
},
|
||||
{
|
||||
name: "Overflow Case - Data is 4GB + small amount",
|
||||
maxPayload: 32768,
|
||||
dataLen: int(bigPayload + 100),
|
||||
want: 32768,
|
||||
is64Bit: true,
|
||||
},
|
||||
}
|
||||
|
||||
is64Bit := bits.UintSize == 64
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.is64Bit && !is64Bit {
|
||||
t.Skip("Skipping test requiring 64-bit int")
|
||||
}
|
||||
got := minPayloadSize(tt.maxPayload, tt.dataLen)
|
||||
if got != tt.want {
|
||||
t.Errorf("minPayloadSize(%d, %d) = %d; want %d", tt.maxPayload, tt.dataLen, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteExtendedNoInfiniteLoopOnLargeWrite is an end-to-end regression
|
||||
// test for the integer-overflow bug in WriteExtended. Before the fix, a
|
||||
// write whose len(data) was a multiple of 2^32 caused minPayloadSize to
|
||||
// return 0; WriteExtended then spun forever, reserving 0 bytes per
|
||||
// iteration and never advancing the data slice.
|
||||
//
|
||||
// We exercise the real WriteExtended path with a slice whose declared
|
||||
// length is exactly 2^32. Allocating 4 GiB is unnecessary: each iteration
|
||||
// only reads up to maxRemotePayload bytes from the head of the slice, and
|
||||
// the loop blocks in remoteWin.reserve() once the channel window is
|
||||
// exhausted — before the slice base advances past the underlying buffer.
|
||||
//
|
||||
// With the fix, the loop blocks in reserve(); we detect that via
|
||||
// waitWriterBlocked(), then close the window to let WriteExtended return.
|
||||
// With the bug, the loop never blocks and the test times out.
|
||||
//
|
||||
//go:nocheckptr
|
||||
func TestWriteExtendedNoInfiniteLoopOnLargeWrite(t *testing.T) {
|
||||
if bits.UintSize < 64 {
|
||||
t.Skip("test requires 64-bit int to construct a slice with len >= 2^32")
|
||||
}
|
||||
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
// Sized to hold the full pre-update remote window so that no iteration
|
||||
// reads past the backing buffer before reserve() blocks.
|
||||
backing := make([]byte, channelWindowSize)
|
||||
var bigLen int64 = 1 << 32
|
||||
bigSlice := unsafe.Slice(&backing[0], int(bigLen))
|
||||
|
||||
done := make(chan int, 1)
|
||||
go func() {
|
||||
n, _ := writer.Write(bigSlice)
|
||||
done <- n
|
||||
}()
|
||||
|
||||
blocked := make(chan struct{})
|
||||
go func() {
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
close(blocked)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-blocked:
|
||||
// Good — the loop made progress and is now blocked in reserve().
|
||||
// Close the window to let WriteExtended return.
|
||||
writer.remoteWin.close()
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("WriteExtended did not block in reserve within 2s — minPayloadSize likely returned 0 (integer overflow regression)")
|
||||
}
|
||||
|
||||
select {
|
||||
case n := <-done:
|
||||
if n == 0 {
|
||||
t.Fatalf("WriteExtended returned n=0; expected progress")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("WriteExtended did not return after closing the window")
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -407,7 +407,7 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
|
||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||
}
|
||||
|
||||
if int(padding+1) >= len(plain) {
|
||||
if int(padding)+1 >= len(plain) {
|
||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||
}
|
||||
plain = plain[1 : length-uint32(padding)]
|
||||
@@ -586,7 +586,7 @@ func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader
|
||||
|
||||
// Length of encrypted portion of the packet (header, payload, padding).
|
||||
// Enforce minimum padding and packet size.
|
||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize)
|
||||
// Enforce block size.
|
||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||
|
||||
|
||||
+7
-3
@@ -274,10 +274,14 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
||||
}
|
||||
|
||||
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
||||
// Iterate over the signer's algorithms first to preserve its preference order.
|
||||
supportedKeyAlgos := algorithmsForKeyFormat(keyFormat)
|
||||
var keyAlgos []string
|
||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
|
||||
keyAlgos = append(keyAlgos, algo)
|
||||
for _, signerAlgo := range as.Algorithms() {
|
||||
if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool {
|
||||
return underlyingAlgo(algo) == signerAlgo
|
||||
}); idx >= 0 {
|
||||
keyAlgos = append(keyAlgos, supportedKeyAlgos[idx])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1159,6 +1159,52 @@ func TestPickSignatureAlgorithm(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickSignatureAlgorithmRespectsSignerPreference(t *testing.T) {
|
||||
algoSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
t.Fatalf("rsa test signer does not implement the AlgorithmSigner interface")
|
||||
}
|
||||
|
||||
serverExtensions := map[string][]byte{
|
||||
"server-sig-algs": []byte(KeyAlgoRSASHA256 + "," + KeyAlgoRSASHA512),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
signerPrefs []string
|
||||
expectedAlgo string
|
||||
}{
|
||||
{
|
||||
name: "Signer prefers SHA512 then SHA256",
|
||||
signerPrefs: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||
expectedAlgo: KeyAlgoRSASHA512,
|
||||
},
|
||||
{
|
||||
name: "Signer prefers SHA256 then SHA512",
|
||||
signerPrefs: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
|
||||
expectedAlgo: KeyAlgoRSASHA256,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
orderedSigner, err := NewSignerWithAlgorithms(algoSigner, tc.signerPrefs)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ordered signer: %v", err)
|
||||
}
|
||||
|
||||
_, selectedAlgo, err := pickSignatureAlgorithm(orderedSigner, serverExtensions)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if selectedAlgo != tc.expectedAlgo {
|
||||
t.Errorf("Algorithm mismatch; got %q want %q", selectedAlgo, tc.expectedAlgo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
+58
@@ -469,6 +469,12 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 8192 bits is also the maximum RSA key size accepted by crypto/tls for
|
||||
// signature verification:
|
||||
// https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
|
||||
if w.N.BitLen() > 8192 {
|
||||
return nil, nil, errors.New("ssh: rsa modulus too large")
|
||||
}
|
||||
if w.E.BitLen() > 24 {
|
||||
return nil, nil, errors.New("ssh: exponent too large")
|
||||
}
|
||||
@@ -574,6 +580,24 @@ func checkDSAParams(param *dsa.Parameters) error {
|
||||
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
||||
}
|
||||
|
||||
// FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
|
||||
// this to prevent DoS attacks where an attacker sends a huge Q which makes
|
||||
// verification slow.
|
||||
if l := param.Q.BitLen(); l != 160 {
|
||||
return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
|
||||
}
|
||||
|
||||
// The generator G is an element of the group, so it must be strictly less
|
||||
// than the modulus P.
|
||||
if param.G.Cmp(param.P) >= 0 {
|
||||
return errors.New("ssh: DSA generator larger than modulus")
|
||||
}
|
||||
|
||||
// G must be positive.
|
||||
if param.G.Sign() <= 0 {
|
||||
return errors.New("ssh: DSA generator must be positive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -596,6 +620,14 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// The public value Y must be a non-zero element of the group, i.e.
|
||||
// strictly between 0 and P. crypto/dsa.Verify does not range-check Y,
|
||||
// so we reject out-of-range values here to prevent a maliciously
|
||||
// oversized Y from slowing verification.
|
||||
if w.Y.Sign() <= 0 || w.Y.Cmp(w.P) >= 0 {
|
||||
return nil, nil, errors.New("ssh: DSA public value Y out of range")
|
||||
}
|
||||
|
||||
key := &dsaPublicKey{
|
||||
Parameters: param,
|
||||
Y: w.Y,
|
||||
@@ -869,11 +901,25 @@ type skFields struct {
|
||||
Counter uint32
|
||||
}
|
||||
|
||||
// flagUserPresence is the "user present" bit (UP) in the SK signature
|
||||
// flags, matching the FIDO CTAP2 authenticatorData UP flag. See
|
||||
// openssh/PROTOCOL.u2f.
|
||||
const flagUserPresence = 0x01
|
||||
|
||||
// errSKMissingUserPresence is returned by SK key Verify methods when
|
||||
// the signature does not assert user presence and the key was not
|
||||
// marked as no-touch-required.
|
||||
var errSKMissingUserPresence = errors.New("ssh: signature missing required user presence flag")
|
||||
|
||||
type skECDSAPublicKey struct {
|
||||
// application is a URL-like string, typically "ssh:" for SSH.
|
||||
// see openssh/PROTOCOL.u2f for details.
|
||||
application string
|
||||
ecdsa.PublicKey
|
||||
// noTouchRequired, when true, disables the default user-presence
|
||||
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||
// key, never on an instance shared across authentication attempts.
|
||||
noTouchRequired bool
|
||||
}
|
||||
|
||||
func (k *skECDSAPublicKey) Type() string {
|
||||
@@ -959,6 +1005,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||
return errSKMissingUserPresence
|
||||
}
|
||||
|
||||
blob := struct {
|
||||
ApplicationDigest []byte `ssh:"rest"`
|
||||
Flags byte
|
||||
@@ -992,6 +1042,10 @@ type skEd25519PublicKey struct {
|
||||
// see openssh/PROTOCOL.u2f for details.
|
||||
application string
|
||||
ed25519.PublicKey
|
||||
// noTouchRequired, when true, disables the default user-presence
|
||||
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||
// key, never on an instance shared across authentication attempts.
|
||||
noTouchRequired bool
|
||||
}
|
||||
|
||||
func (k *skEd25519PublicKey) Type() string {
|
||||
@@ -1066,6 +1120,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||
return errSKMissingUserPresence
|
||||
}
|
||||
|
||||
blob := struct {
|
||||
ApplicationDigest []byte `ssh:"rest"`
|
||||
Flags byte
|
||||
|
||||
+384
-12
@@ -12,6 +12,7 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -216,6 +218,20 @@ func TestParseRSAPrivateKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSAModulusTooLarge(t *testing.T) {
|
||||
rsa16384 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAIAQC0vyqtnpzMZ8Th6gv1t3+7dDVd2X+7MwjqKe08/wrKZaIuCAHX7jglC8FdHfiOcPhLHAreSJLZGXiSzCTUExbp9Zdt7tHluKMQxZCnbk02V89ggc4KpptQqfcmjMNgrEPF4PGJBe3eSYu9m7A1ptm0buWxaKtA17O1c2q2CVNYmuajSUAfi8+AZcdyRAX8eqga3u77DEj4O+CCuNA908NYv9Pc7SbaCwCgX3FXh7MvlaYcruTk529psNk1SK6uwBNak3CLRrXUBo5yTO7cCO0gmlvd8SwtY5mPaDLLCJa+Ed2t9OCt8HCPrdkH3aiQBTP3k/Iofy3NRv+/1lenJP1qWB2rnyagoj+osrLc6m8PRd7GBje7KdO6nyY1D+q1MvX4zV1qNQfD+38N6PixVX1MGKw72sD+oLCDjHKS24BHfnfaE7f8n+Alp2mxnSz9tYqsN3JXrbbwblzH8YFOz9MMKkTY5lQNQFoKdTHoPVXBqcMlGQGgC/kkH9TH110MDE3L+Z0i9kG53QLcHNVC0cJzo6X77PQF1WoS33Ssf1s21kUB6fe/5G2L2Tuy635ingtnQMUazwJMli+y/3U7QqqcuJzITbLw/zozRzbqFRE4HHOyGkJSptraH4/3cjmYro8JL20A7EUDiXxmn+z+eoxaBWaEP2Rs5LH0ocrwYmBmSUIrPy4e5ESlRRVuv+prNUB1pYGnFZUI9HRRFxRFg+SxXZZp4mq8jzroRLZFAqKY1/eDgsCGoV1HG3QW1t0jGNkv8BL/Zpz7Hhmt0lRr7rVN+PJaTQx8NNUJZ/l7nMXp/+7aTbkyI8PiNyemko0+joDrkBWdcK0vi8Du6k3oQRBUsJ0aM0iPxMWQq/oYyHqpf6revycYTUdOGeckGWXuXLNRIZQ/PsKuw/teqDaBkkMKaQH2oXWMsacL6WDu5VYUQidOtaTgHX8AKVHF/vobTQfw7HgPKlyRN41dAX+YIjRABYoqU7M2MYPuoPYbqzTz7iLZyFP8FCJ3dRQBTWSeer/s+Ih6Ev1EsznVmAw9SKb5q8QCSYk5mMugUTDVETOQo10CGAF6094/PcJRXgag/uWs4k+13c+/HaeNzrHQyqzWnf9bFFHIvs7Wqov9k+nWv/TwSGplkyBk4bHfhEZNbl+b6T8sZXsv8mTT+INzKT4DCVZ5a/G+NQ6Xo26Aa6Se5DOdzdXOLZ7yIhOi+olip8AOXIIFLb7Vee1+q7Ri6ScUJEKxdEvec5EceXEtWs0U4AnuHgiH0ewJSX9yUp8T4ImCzu00JhTMOH2nXU3PJuGgZgrDifNWXGpFruIBjHWRKpESpy+9HPGoNoxSO/hzSfuM5LKvwX+I96cMK5OC+fKsBh59cMpU4YnqFNh59ZP/kaMDxAXgrVku5s9oB3Rgc7t1rfm6dqycBiwn4QnYJpk7M+gzAbrF/nty8y84ajNdhINXgMgvv3JLC5YTqznBPm3koHSOEF8f4xtCZtzjNN6jNjX8gEvxoRpC9Z+X15c8XBSFyDMBoi0FMDor619XJKgTxNiOYmeH/DLhKEb8MIaug8IFu34FKbG81IsWD+zwA1A5xqXEVlgsfEOcML+Oe2rulkpwQBWnsmY+f/Z80004Mytreo/ME2TDuPJPOi15D0j0CbwG7xzE8qrn0EomBE9mrwH3uuH7df3lzx1HiLAmh2s4MO5R64AoWeAPQW9lWZPzp+2dNgk+0qWpoEsMN+r91yWxUZHgeoC+GJKs13LItH32sGInyUrquMYQjEh44fAEFrMREcS4A7l+875GUmWC6i2MSLyvtAzuPS2IV0t9GU0ooH8cfG5JUeDnsJcVanJTR+XuXPetM+cDyGFTGri3lsCndE9maLgs9iDHtJxzKUBUUakaIPLsXtZD51cS4jhSbbHuzgC21BRtnBhCE2phVYbz4Tj0t90wBmemBaP2eVwv9p4s/JJAHEELvV7k+Gro4FozOoC2WBdqdTPDFB00la08O6ADBdjd2el2pRA7HCTG6v3qwA6RQJfm4UHOXaFYhhiNngxHB4VHZMvpd+YMEqNmtYOb4lzWwI13iHU0Rh0Sj4IaY9EwEy/KjR5dc2DjWGj11SFFCP1uG24fccF+LhtdMNDItI1mBxfaeRelYlsCZwtbbVLnuVQ5izuTzXa48x7CBaHnD3i09BZCuQITLX4d7KkD6CLWWTHrc5onLFJKxRF/p5AEFoqtN3vP9CsLXSYNKxFV5UbxAY5TnAoLFCCo0WbgvtAV77jWSbru9Oq/7ORN9smog4zNuYBZE1uXjxM2xszDduOym7+CxwpJMc7XPknt2XwiANaf2QANMMMH6lmnjTH1RVR7oH4+ts4xVcsdiO/QOlMp+Th9/KIMYyUevR18vHbs+88uxzIG28/58xLZTs6rZc71g9mGw9Q21ugL2sfTc7e0VDMnAmdg+92s2RXQMvkmx+oRc+IsFqgOZzjYcTmnJxZaXsoIsydDVcTalgmhK6/dD0grPLaGgaeXMEw2hN8p8seAHrGRW8+6WBD63NBYaAG0/zwuGOCHUo/BeG9bz39Hsz9Yvs5KvJiLhmM5K+8RlnxIY329REqdFZVyuXyy0NpDueFQelnd0j47Quc7GJGX3QycJiKpLolMtDnpnjgYOvdfycM+JEMZGwpLsBBE8R6vJ3RVczT6DdMtpVQ4l7kOzsPSYlp4qAv5fiqUboyv5eP7G7MOD/qSUwBnMS1p9Vm4Wr8B9w=="
|
||||
_, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384))
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
|
||||
}
|
||||
|
||||
expectedError := "rsa modulus too large"
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseECPrivateKey(t *testing.T) {
|
||||
key := testPrivateKeys["ecdsa"]
|
||||
|
||||
@@ -272,18 +288,18 @@ func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeysWithUnsupportedCiphers(t *testing.T) {
|
||||
for _, tt := range testdata.UnsupportedCipherData {
|
||||
t.Run(tt.Name, func(t *testing.T){
|
||||
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
|
||||
if err == nil {
|
||||
t.Fatalf("expected 'unknown cipher' error for %q, got nil", tt.Name)
|
||||
// If this cipher is now supported, remove it from testdata.UnsupportedCipherData
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown cipher") {
|
||||
t.Errorf("wanted 'unknown cipher' error, got %v", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, tt := range testdata.UnsupportedCipherData {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
|
||||
if err == nil {
|
||||
t.Fatalf("expected 'unknown cipher' error for %q, got nil", tt.Name)
|
||||
// If this cipher is now supported, remove it from testdata.UnsupportedCipherData
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown cipher") {
|
||||
t.Errorf("wanted 'unknown cipher' error, got %v", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
|
||||
@@ -356,6 +372,72 @@ func TestMarshalParsePublicKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDSAHugeQ(t *testing.T) {
|
||||
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
||||
Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
|
||||
// G and Y: Dummy values, just needs to be < P to pass that specific check
|
||||
G := big.NewInt(2)
|
||||
Y := big.NewInt(5)
|
||||
|
||||
rawKey := struct {
|
||||
P, Q, G, Y *big.Int
|
||||
}{
|
||||
P: P,
|
||||
Q: Q,
|
||||
G: G,
|
||||
Y: Y,
|
||||
}
|
||||
|
||||
inputBytes := Marshal(&rawKey)
|
||||
|
||||
_, _, err := parseDSA(inputBytes)
|
||||
if err == nil {
|
||||
t.Fatal("parseDSA accepted a DSA key with large Q")
|
||||
}
|
||||
|
||||
expectedError := "ssh: unsupported DSA sub-prime size"
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDSAYOutOfRange(t *testing.T) {
|
||||
// Valid 1024/160 parameters (values don't need to be a real DSA group,
|
||||
// they only need to pass the checkDSAParams bit-length checks and the
|
||||
// G < P / G > 0 checks).
|
||||
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
||||
P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
|
||||
Q := new(big.Int).Lsh(big.NewInt(1), 159)
|
||||
Q.SetBit(Q, 0, 1)
|
||||
G := big.NewInt(2)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
Y *big.Int
|
||||
}{
|
||||
{"Y_zero", big.NewInt(0)},
|
||||
{"Y_negative", big.NewInt(-1)},
|
||||
{"Y_equals_P", new(big.Int).Set(P)},
|
||||
{"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
|
||||
{"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rawKey := struct {
|
||||
P, Q, G, Y *big.Int
|
||||
}{P: P, Q: Q, G: G, Y: tc.Y}
|
||||
|
||||
_, _, err := parseDSA(Marshal(&rawKey))
|
||||
if err == nil {
|
||||
t.Fatalf("parseDSA accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
|
||||
}
|
||||
expectedError := "DSA public value Y out of range"
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPrivateKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -760,6 +842,296 @@ func TestSKKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// skTestHarness builds SK-formatted signatures over a fixed payload
|
||||
// using a caller-supplied signing function, letting tests vary the UP
|
||||
// flag byte without duplicating the wire-format scaffolding.
|
||||
type skTestHarness struct {
|
||||
format string
|
||||
application string
|
||||
data []byte
|
||||
// sign takes the SHA-256 digest of the marshalled SK blob and
|
||||
// returns the value to embed in Signature.Blob. For ECDSA keys the
|
||||
// helper feeds the digest to ecdsa.Sign; for ed25519 the helper
|
||||
// feeds the raw marshalled blob to ed25519.Sign.
|
||||
signDigest func(digest []byte) []byte
|
||||
signBlob func(blob []byte) []byte
|
||||
}
|
||||
|
||||
func (h skTestHarness) sign(t *testing.T, flags byte) *Signature {
|
||||
t.Helper()
|
||||
hsh := sha256.New()
|
||||
hsh.Write([]byte(h.application))
|
||||
appDigest := hsh.Sum(nil)
|
||||
|
||||
hsh.Reset()
|
||||
hsh.Write(h.data)
|
||||
dataDigest := hsh.Sum(nil)
|
||||
|
||||
var counter uint32 = 1
|
||||
blob := struct {
|
||||
ApplicationDigest []byte `ssh:"rest"`
|
||||
Flags byte
|
||||
Counter uint32
|
||||
MessageDigest []byte `ssh:"rest"`
|
||||
}{appDigest, flags, counter, dataDigest}
|
||||
marshalled := Marshal(blob)
|
||||
|
||||
var sigBlob []byte
|
||||
if h.signDigest != nil {
|
||||
hsh.Reset()
|
||||
hsh.Write(marshalled)
|
||||
sigBlob = h.signDigest(hsh.Sum(nil))
|
||||
} else {
|
||||
sigBlob = h.signBlob(marshalled)
|
||||
}
|
||||
|
||||
return &Signature{
|
||||
Format: h.format,
|
||||
Blob: sigBlob,
|
||||
Rest: Marshal(struct {
|
||||
Flags byte
|
||||
Counter uint32
|
||||
}{flags, counter}),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSKUserPresence(t *testing.T) {
|
||||
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := skTestHarness{
|
||||
format: "sk-ecdsa-sha2-nistp256@openssh.com",
|
||||
application: "ssh:",
|
||||
data: []byte("test data"),
|
||||
signDigest: func(digest []byte) []byte {
|
||||
r, s, err := ecdsa.Sign(rand.Reader, ecKey, digest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return Marshal(struct{ R, S *big.Int }{r, s})
|
||||
},
|
||||
}
|
||||
|
||||
pk := &skECDSAPublicKey{
|
||||
application: h.application,
|
||||
PublicKey: ecKey.PublicKey,
|
||||
}
|
||||
|
||||
// Valid signature with UP=1 should pass.
|
||||
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
|
||||
t.Errorf("Verify failed with UP=1: %v", err)
|
||||
}
|
||||
|
||||
// Valid signature with UP=0 should fail with the user-presence sentinel.
|
||||
sigNoUP := h.sign(t, 0)
|
||||
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
|
||||
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
|
||||
}
|
||||
|
||||
// UV set but UP clear must still fail: we only waive UP, never UV-only.
|
||||
if err := pk.Verify(h.data, h.sign(t, 0x04)); !errors.Is(err, errSKMissingUserPresence) {
|
||||
t.Errorf("UV-only (flags=0x04): expected errSKMissingUserPresence, got: %v", err)
|
||||
}
|
||||
|
||||
// With noTouchRequired, UP=0 passes; UP=1+UV=1 also passes.
|
||||
pk.noTouchRequired = true
|
||||
if err := pk.Verify(h.data, sigNoUP); err != nil {
|
||||
t.Errorf("Verify with noTouchRequired failed: %v", err)
|
||||
}
|
||||
if err := pk.Verify(h.data, h.sign(t, flagUserPresence|0x04)); err != nil {
|
||||
t.Errorf("Verify UP|UV with noTouchRequired failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSKKeyWithoutUP(t *testing.T) {
|
||||
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
edPub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
skEC := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
|
||||
skED := &skEd25519PublicKey{PublicKey: edPub}
|
||||
certEC := &Certificate{Key: &skECDSAPublicKey{PublicKey: ecKey.PublicKey}}
|
||||
certED := &Certificate{Key: &skEd25519PublicKey{PublicKey: edPub}}
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsaPub, err := NewPublicKey(&rsaKey.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certRSA := &Certificate{Key: rsaPub}
|
||||
|
||||
// SK raw keys: return a clone with the flag set, original untouched.
|
||||
gotEC := skKeyWithoutUP(skEC)
|
||||
if gotEC == skEC {
|
||||
t.Error("skECDSAPublicKey: expected a clone, got the original pointer")
|
||||
}
|
||||
if !gotEC.(*skECDSAPublicKey).noTouchRequired {
|
||||
t.Error("skECDSAPublicKey clone: noTouchRequired not set")
|
||||
}
|
||||
if skEC.noTouchRequired {
|
||||
t.Error("skECDSAPublicKey: original mutated")
|
||||
}
|
||||
|
||||
gotED := skKeyWithoutUP(skED)
|
||||
if gotED == skED {
|
||||
t.Error("skEd25519PublicKey: expected a clone, got the original pointer")
|
||||
}
|
||||
if !gotED.(*skEd25519PublicKey).noTouchRequired {
|
||||
t.Error("skEd25519PublicKey clone: noTouchRequired not set")
|
||||
}
|
||||
if skED.noTouchRequired {
|
||||
t.Error("skEd25519PublicKey: original mutated")
|
||||
}
|
||||
|
||||
// Certificate wrapping SK: return a clone of the cert with a cloned
|
||||
// SK key inside. Neither the original cert nor the original inner
|
||||
// key must be mutated.
|
||||
originalInnerEC := certEC.Key
|
||||
gotCertEC := skKeyWithoutUP(certEC)
|
||||
if gotCertEC == certEC {
|
||||
t.Error("*Certificate(SK ecdsa): expected a clone, got the original pointer")
|
||||
}
|
||||
if got := gotCertEC.(*Certificate).Key.(*skECDSAPublicKey); !got.noTouchRequired {
|
||||
t.Error("*Certificate(SK ecdsa): inner clone missing noTouchRequired")
|
||||
}
|
||||
if certEC.Key != originalInnerEC {
|
||||
t.Error("*Certificate(SK ecdsa): original cert's Key pointer mutated")
|
||||
}
|
||||
if originalInnerEC.(*skECDSAPublicKey).noTouchRequired {
|
||||
t.Error("*Certificate(SK ecdsa): original inner key mutated")
|
||||
}
|
||||
|
||||
gotCertED := skKeyWithoutUP(certED)
|
||||
if gotCertED == certED {
|
||||
t.Error("*Certificate(SK ed25519): expected a clone, got the original pointer")
|
||||
}
|
||||
if got := gotCertED.(*Certificate).Key.(*skEd25519PublicKey); !got.noTouchRequired {
|
||||
t.Error("*Certificate(SK ed25519): inner clone missing noTouchRequired")
|
||||
}
|
||||
|
||||
// Non-SK key inside a cert: return original unchanged (nothing to clone).
|
||||
if got := skKeyWithoutUP(certRSA); got != certRSA {
|
||||
t.Error("*Certificate(RSA): expected the original pointer back")
|
||||
}
|
||||
|
||||
// Plain non-SK key: return original unchanged.
|
||||
if got := skKeyWithoutUP(rsaPub); got != rsaPub {
|
||||
t.Error("rsaPublicKey: expected the original pointer back")
|
||||
}
|
||||
|
||||
// Pathological: *Certificate whose Key is itself a *Certificate.
|
||||
// The SSH cert format forbids this and parseCert rejects it, but
|
||||
// a Go caller can still construct such a value. skKeyWithoutUP
|
||||
// must not recurse into it (or panic); it returns the input
|
||||
// unchanged. This also defends against a hypothetical cycle built
|
||||
// from hand-constructed Certificate pointers.
|
||||
nestedCert := &Certificate{Key: &Certificate{Key: skEC}}
|
||||
if got := skKeyWithoutUP(nestedCert); got != nestedCert {
|
||||
t.Error("*Certificate wrapping *Certificate: expected the original pointer back")
|
||||
}
|
||||
selfCycle := &Certificate{}
|
||||
selfCycle.Key = selfCycle
|
||||
if got := skKeyWithoutUP(selfCycle); got != selfCycle {
|
||||
t.Error("self-referential *Certificate: expected the original pointer back")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoTouchAllowed(t *testing.T) {
|
||||
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sk := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
|
||||
certNoExt := &Certificate{Key: sk}
|
||||
certOptOut := &Certificate{
|
||||
Key: sk,
|
||||
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": ""}},
|
||||
}
|
||||
// Non-empty value: OpenSSH writes "" but any value must be accepted
|
||||
// because the check is presence-only.
|
||||
certOptOutNonEmpty := &Certificate{
|
||||
Key: sk,
|
||||
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": "yes"}},
|
||||
}
|
||||
// no-touch-required belongs in Extensions, never CriticalOptions.
|
||||
// Putting it in CriticalOptions must NOT be treated as opt-out.
|
||||
certCritOnly := &Certificate{
|
||||
Key: sk,
|
||||
Permissions: Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}},
|
||||
}
|
||||
|
||||
permsEmpty := &Permissions{}
|
||||
permsOptOut := &Permissions{Extensions: map[string]string{"no-touch-required": ""}}
|
||||
permsCritOnly := &Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
pub PublicKey
|
||||
perms *Permissions
|
||||
want bool
|
||||
}{
|
||||
{"nil perms, no cert", sk, nil, false},
|
||||
{"empty perms, no cert", sk, permsEmpty, false},
|
||||
{"perms opt-out, raw key", sk, permsOptOut, true},
|
||||
{"nil perms, cert opt-out", certOptOut, nil, true},
|
||||
{"nil perms, cert opt-out non-empty value", certOptOutNonEmpty, nil, true},
|
||||
{"nil perms, cert no ext", certNoExt, nil, false},
|
||||
{"perms opt-out, cert no ext", certNoExt, permsOptOut, true},
|
||||
{"empty perms, cert opt-out", certOptOut, permsEmpty, true},
|
||||
// Negative controls: CriticalOptions must not waive UP.
|
||||
{"critical-options only, raw key", sk, permsCritOnly, false},
|
||||
{"critical-options only, cert", certCritOnly, nil, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := noTouchAllowed(tc.pub, tc.perms); got != tc.want {
|
||||
t.Errorf("noTouchAllowed = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSKUserPresenceEd25519(t *testing.T) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := skTestHarness{
|
||||
format: "sk-ssh-ed25519@openssh.com",
|
||||
application: "ssh:",
|
||||
data: []byte("test data"),
|
||||
signBlob: func(blob []byte) []byte {
|
||||
return ed25519.Sign(priv, blob)
|
||||
},
|
||||
}
|
||||
|
||||
pk := &skEd25519PublicKey{
|
||||
application: h.application,
|
||||
PublicKey: pub,
|
||||
}
|
||||
|
||||
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
|
||||
t.Errorf("Verify failed with UP=1: %v", err)
|
||||
}
|
||||
sigNoUP := h.sign(t, 0)
|
||||
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
|
||||
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
|
||||
}
|
||||
pk.noTouchRequired = true
|
||||
if err := pk.Verify(h.data, sigNoUP); err != nil {
|
||||
t.Errorf("Verify with noTouchRequired failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSignerWithAlgos(t *testing.T) {
|
||||
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||
if !ok {
|
||||
|
||||
@@ -160,8 +160,13 @@ func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool
|
||||
|
||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||
_, ok := db.revoked[string(key.Marshal())]
|
||||
return ok
|
||||
if _, ok := db.revoked[string(key.Marshal())]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const markerCert = "@cert-authority"
|
||||
|
||||
@@ -6,6 +6,8 @@ package knownhosts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
@@ -272,7 +274,45 @@ func TestWildcardMatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): test coverage for certificates.
|
||||
func TestRevokedCA(t *testing.T) {
|
||||
_, caPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
caSigner, err := ssh.NewSignerFromKey(caPriv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
caKey := caSigner.PublicKey()
|
||||
|
||||
_, hostPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostKey, err := ssh.NewPublicKey(hostPriv.Public())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cert := &ssh.Certificate{
|
||||
CertType: ssh.HostCert,
|
||||
Key: hostKey,
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
ValidPrincipals: []string{"server.org"},
|
||||
}
|
||||
if err := cert.SignCert(rand.Reader, caSigner); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
caLine := "ssh-ed25519 " + serialize(caKey)[len("ssh-ed25519 "):]
|
||||
knownHostsData := "@revoked server.org " + caLine + "\n" +
|
||||
"@cert-authority server.org " + caLine + "\n"
|
||||
db := testDB(t, knownHostsData)
|
||||
|
||||
if !db.IsRevoked(cert) {
|
||||
t.Error("IsRevoked returned false for certificate signed by revoked CA")
|
||||
}
|
||||
}
|
||||
|
||||
const testHostname = "hostname"
|
||||
|
||||
|
||||
+32
-4
@@ -91,9 +91,10 @@ type mux struct {
|
||||
|
||||
incomingChannels chan NewChannel
|
||||
|
||||
globalSentMu sync.Mutex
|
||||
globalResponses chan interface{}
|
||||
incomingRequests chan *Request
|
||||
globalSentMu sync.Mutex
|
||||
globalSentPending atomic.Bool
|
||||
globalResponses chan interface{}
|
||||
incomingRequests chan *Request
|
||||
|
||||
errCond *sync.Cond
|
||||
err error
|
||||
@@ -141,6 +142,24 @@ func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []
|
||||
if wantReply {
|
||||
m.globalSentMu.Lock()
|
||||
defer m.globalSentMu.Unlock()
|
||||
|
||||
// Open the gate so that responses arriving while this request is in
|
||||
// flight are allowed to reach globalResponses. Any response arriving
|
||||
// while no request is pending is dropped by handleGlobalPacket.
|
||||
m.globalSentPending.Store(true)
|
||||
defer m.globalSentPending.Store(false)
|
||||
|
||||
// Drain any spurious responses that may have been buffered. This prevents
|
||||
// a previously buffered unexpected response from being consumed instead
|
||||
// of the actual response for this request.
|
||||
drain:
|
||||
for {
|
||||
select {
|
||||
case <-m.globalResponses:
|
||||
default:
|
||||
break drain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.sendMessage(globalRequestMsg{
|
||||
@@ -267,7 +286,16 @@ func (m *mux) handleGlobalPacket(packet []byte) error {
|
||||
mux: m,
|
||||
}
|
||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||
m.globalResponses <- msg
|
||||
// Drop responses that arrive when no SendRequest is waiting, to
|
||||
// prevent a malicious peer from staging responses for a future
|
||||
// caller.
|
||||
if !m.globalSentPending.Load() {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case m.globalResponses <- msg:
|
||||
default:
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("not a global message %#v", msg))
|
||||
}
|
||||
|
||||
+529
@@ -825,6 +825,50 @@ func TestMuxChannelWindowDeferredUpdates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelRejectRemovesFromMux(t *testing.T) {
|
||||
serverMux, clientMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
t.Cleanup(wg.Wait)
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
// The server waits for the channel creation request
|
||||
newCh, ok := <-serverMux.incomingChannels
|
||||
if !ok {
|
||||
t.Error("failed to accept channel")
|
||||
return
|
||||
}
|
||||
ch := newCh.(*channel)
|
||||
|
||||
if serverMux.chanList.getChan(ch.localId) == nil {
|
||||
t.Errorf("channel %d is not in the chanList before Reject", ch.localId)
|
||||
}
|
||||
|
||||
if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil {
|
||||
t.Errorf("Reject failed: %v", err)
|
||||
}
|
||||
|
||||
if serverMux.chanList.getChan(ch.localId) != nil {
|
||||
t.Errorf("channel %d is still in the chanList after Reject", ch.localId)
|
||||
}
|
||||
}()
|
||||
|
||||
_, _, err := clientMux.OpenChannel("test_leak", nil)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error (channel rejected), but got nil")
|
||||
}
|
||||
|
||||
if _, ok := err.(*OpenChannelError); !ok {
|
||||
t.Errorf("expected *OpenChannelError, got: %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't ship code with debug=true.
|
||||
func TestDebug(t *testing.T) {
|
||||
if debugMux {
|
||||
@@ -837,3 +881,488 @@ func TestDebug(t *testing.T) {
|
||||
t.Error("transport debug switched on")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxUnexpectedGlobalResponsesDiscarded(t *testing.T) {
|
||||
clientPipe, serverPipe := memPipe()
|
||||
client := newMux(clientPipe)
|
||||
defer serverPipe.Close()
|
||||
defer client.Close()
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
// Send multiple unexpected global responses, this should not block the
|
||||
// globalResponses channel.
|
||||
for i := range 5 {
|
||||
err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
|
||||
Data: []byte{byte(i)},
|
||||
}))
|
||||
if err != nil {
|
||||
done <- fmt.Errorf("send success msg %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for i := range 5 {
|
||||
err := serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||
Data: []byte{byte(i)},
|
||||
}))
|
||||
if err != nil {
|
||||
done <- fmt.Errorf("send failure msg %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now send a global request and wait for the response. This
|
||||
// verifies the mux is still processing packets.
|
||||
err := serverPipe.writePacket(Marshal(globalRequestMsg{
|
||||
Type: "keepalive@golang.org",
|
||||
WantReply: true,
|
||||
Data: nil,
|
||||
}))
|
||||
if err != nil {
|
||||
done <- fmt.Errorf("send global request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
packet, err := serverPipe.readPacket()
|
||||
if err != nil {
|
||||
done <- fmt.Errorf("read packet: %w", err)
|
||||
return
|
||||
}
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
done <- fmt.Errorf("decode: %w", err)
|
||||
return
|
||||
}
|
||||
switch decoded.(type) {
|
||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||
// Expected response
|
||||
default:
|
||||
done <- fmt.Errorf("unexpected packet type: %T", decoded)
|
||||
return
|
||||
}
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
// Handle the incoming request from the server and reply
|
||||
req, ok := <-client.incomingRequests
|
||||
if !ok {
|
||||
t.Fatal("incomingRequests channel closed unexpectedly")
|
||||
}
|
||||
if req.Type != "keepalive@golang.org" {
|
||||
t.Fatalf("unexpected request type: %s", req.Type)
|
||||
}
|
||||
if err := req.Reply(true, nil); err != nil {
|
||||
t.Fatalf("Reply: %v", err)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxConcurrentGlobalRequests(t *testing.T) {
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
const numRequests = 50
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for r := range serverMux.incomingRequests {
|
||||
if r.WantReply {
|
||||
replyData := append([]byte("reply:"), r.Payload...)
|
||||
r.Reply(true, replyData)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var clientWg sync.WaitGroup
|
||||
clientWg.Add(numRequests)
|
||||
|
||||
errCh := make(chan error, numRequests)
|
||||
|
||||
for i := range numRequests {
|
||||
go func(id int) {
|
||||
defer clientWg.Done()
|
||||
|
||||
payloadStr := fmt.Sprintf("req-%d", id)
|
||||
payload := []byte(payloadStr)
|
||||
|
||||
// This call blocks until the globalSentMu is acquired.
|
||||
// The mutex ensures that even with many concurrent attempts,
|
||||
// the "drain" and "send" logic happens atomically per request.
|
||||
ok, data, err := clientMux.SendRequest("echo", true, payload)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("req %d error: %v", id, err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
errCh <- fmt.Errorf("req %d failed (want success)", id)
|
||||
return
|
||||
}
|
||||
|
||||
expected := "reply:" + payloadStr
|
||||
if string(data) != expected {
|
||||
errCh <- fmt.Errorf("req %d mismatch: got %q, want %q", id, string(data), expected)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
clientWg.Wait()
|
||||
close(errCh)
|
||||
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
clientMux.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestMuxGlobalResponseDroppedWhenIdle(t *testing.T) {
|
||||
clientPipe, serverPipe := memPipe()
|
||||
clientMux := newMux(clientPipe)
|
||||
defer serverPipe.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
// Send a spurious response while no SendRequest is pending.
|
||||
if err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
|
||||
Data: []byte("spurious"),
|
||||
})); err != nil {
|
||||
errCh <- fmt.Errorf("send spurious: %w", err)
|
||||
return
|
||||
}
|
||||
// Follow with a global request; once the client observes this on
|
||||
// incomingRequests, the mux loop has necessarily processed (and
|
||||
// dropped) the prior spurious response.
|
||||
if err := serverPipe.writePacket(Marshal(globalRequestMsg{
|
||||
Type: "sync@example.com",
|
||||
WantReply: false,
|
||||
})); err != nil {
|
||||
errCh <- fmt.Errorf("send sync request: %w", err)
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, ok := <-clientMux.incomingRequests
|
||||
if !ok {
|
||||
t.Fatal("incomingRequests closed unexpectedly")
|
||||
}
|
||||
if req.Type != "sync@example.com" {
|
||||
t.Fatalf("unexpected sync request type %q", req.Type)
|
||||
}
|
||||
|
||||
// The spurious response preceded the sync request, so by now the mux
|
||||
// loop has processed it. The pending-gate must have caused it to be
|
||||
// dropped rather than buffered.
|
||||
if n := len(clientMux.globalResponses); n != 0 {
|
||||
t.Fatalf("globalResponses buffer should be empty after idle drop, has %d entries", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxStaleResponseDrained(t *testing.T) {
|
||||
// Simulate a stale response sitting in globalResponses (e.g. a response
|
||||
// that slipped in through the pending-gate on a prior SendRequest that
|
||||
// exited without consuming it). The drain step in the next SendRequest
|
||||
// must discard it so the caller receives the correct reply.
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
clientMux.globalResponses <- &globalRequestSuccessMsg{Data: []byte("stale")}
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for req := range serverMux.incomingRequests {
|
||||
if req.WantReply {
|
||||
req.Reply(true, append([]byte("reply:"), req.Payload...))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ok, data, err := clientMux.SendRequest("test", true, []byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected success response")
|
||||
}
|
||||
if string(data) != "reply:hello" {
|
||||
t.Fatalf("got %q, want %q (drain did not remove stale response)", data, "reply:hello")
|
||||
}
|
||||
|
||||
clientMux.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestMuxGlobalResponseAcceptedWhilePending(t *testing.T) {
|
||||
// Positive control: when a SendRequest is actually pending, the
|
||||
// response must be delivered (the gate is open).
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for req := range serverMux.incomingRequests {
|
||||
if req.WantReply {
|
||||
req.Reply(true, []byte("pong"))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ok, data, err := clientMux.SendRequest("ping", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
if !ok || string(data) != "pong" {
|
||||
t.Fatalf("unexpected response: ok=%v data=%q", ok, data)
|
||||
}
|
||||
|
||||
clientMux.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
|
||||
// A malicious peer that spams channelRequestSuccess/Failure messages
|
||||
// for an open, idle channel must not be able to stall the mux read
|
||||
// loop by filling ch.msg. After the flood, the channel must still be
|
||||
// usable: a subsequent legitimate SendRequest receives its reply.
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
serverRes := make(chan *channel, 1)
|
||||
go func() {
|
||||
newCh, ok := <-serverMux.incomingChannels
|
||||
if !ok {
|
||||
close(serverRes)
|
||||
return
|
||||
}
|
||||
c, _, err := newCh.Accept()
|
||||
if err != nil {
|
||||
close(serverRes)
|
||||
return
|
||||
}
|
||||
serverRes <- c.(*channel)
|
||||
}()
|
||||
|
||||
clientCh, err := clientMux.openChannel("chan", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("openChannel: %v", err)
|
||||
}
|
||||
serverCh := <-serverRes
|
||||
if serverCh == nil {
|
||||
t.Fatal("server did not accept channel")
|
||||
}
|
||||
|
||||
// Spam many unsolicited success/failure responses. More than chanSize
|
||||
// to ensure ch.msg would overflow without the pending-gate.
|
||||
const spam = chanSize * 4
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
for i := range spam {
|
||||
if err := serverCh.ackRequest(i%2 == 0); err != nil {
|
||||
done <- fmt.Errorf("ackRequest %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Echo any legitimate request back.
|
||||
for req := range serverCh.incomingRequests {
|
||||
if req.WantReply {
|
||||
if err := req.Reply(true, append([]byte("reply:"), req.Payload...)); err != nil {
|
||||
done <- fmt.Errorf("reply: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
// If the flood had wedged the mux loop, this SendRequest would never
|
||||
// receive a reply.
|
||||
ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected success reply")
|
||||
}
|
||||
|
||||
// Clean up so the server goroutine can exit.
|
||||
clientCh.Close()
|
||||
serverCh.Close()
|
||||
if err := <-done; err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelConcurrentRequests(t *testing.T) {
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for req := range writer.incomingRequests {
|
||||
if req.WantReply {
|
||||
req.Reply(true, append([]byte("reply:"), req.Payload...))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
const numRequests = 50
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numRequests)
|
||||
errCh := make(chan error, numRequests)
|
||||
|
||||
for i := 0; i < numRequests; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
payload := []byte(fmt.Sprintf("req-%d", id))
|
||||
ok, err := reader.SendRequest("echo", true, payload)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("req %d: %v", id, err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
errCh <- fmt.Errorf("req %d: expected success", id)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestChannelResponseDroppedWhenIdle(t *testing.T) {
|
||||
// A spurious response arriving while no SendRequest is pending must
|
||||
// be dropped rather than buffered in ch.msg.
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
// Server sends an unsolicited reply, then a request so we can
|
||||
// synchronise: once the client observes the request, the mux loop has
|
||||
// necessarily processed (and dropped) the prior spurious reply.
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
if err := writer.ackRequest(true); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
if _, err := writer.SendRequest("sync", false, nil); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
req := <-reader.incomingRequests
|
||||
if req.Type != "sync" {
|
||||
t.Fatalf("unexpected request type %q", req.Type)
|
||||
}
|
||||
|
||||
if n := len(reader.msg); n != 0 {
|
||||
t.Fatalf("ch.msg should be empty after idle drop, has %d entries", n)
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelStaleResponseDrained(t *testing.T) {
|
||||
// Simulate a stale response sitting in ch.msg (e.g. a response that
|
||||
// slipped through the pending-gate on a prior SendRequest that exited
|
||||
// without consuming it). The drain step in the next SendRequest must
|
||||
// discard it so the caller receives the correct reply.
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
reader.msg <- &channelRequestSuccessMsg{PeersID: reader.remoteId}
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for req := range writer.incomingRequests {
|
||||
if req.WantReply {
|
||||
req.Reply(false, append([]byte("nack:"), req.Payload...))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ok, err := reader.SendRequest("test", true, []byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
// If the stale success had been consumed, ok would be true.
|
||||
if ok {
|
||||
t.Fatal("got stale success response; drain did not remove it")
|
||||
}
|
||||
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestChannelResponseAcceptedWhilePending(t *testing.T) {
|
||||
// Positive control: when a SendRequest is actually pending, the
|
||||
// response must be delivered (the gate is open).
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
for req := range writer.incomingRequests {
|
||||
if req.WantReply {
|
||||
req.Reply(true, nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ok, err := reader.SendRequest("ping", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected success")
|
||||
}
|
||||
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
+114
-13
@@ -34,15 +34,20 @@ type Permissions struct {
|
||||
// or not supported.
|
||||
CriticalOptions map[string]string
|
||||
|
||||
// Extensions are extra functionality that the server may
|
||||
// offer on authenticated connections. Lack of support for an
|
||||
// extension does not preclude authenticating a user. Common
|
||||
// extensions are "permit-agent-forwarding",
|
||||
// "permit-X11-forwarding". The Go SSH library currently does
|
||||
// not act on any extension, and it is up to server
|
||||
// implementations to honor them. Extensions can be used to
|
||||
// pass data from the authentication callbacks to the server
|
||||
// application layer.
|
||||
// Extensions are extra functionality that the server may offer on
|
||||
// authenticated connections. Lack of support for an extension does not
|
||||
// preclude authenticating a user. Common extensions are
|
||||
// "permit-agent-forwarding", "permit-X11-forwarding". In general the Go
|
||||
// SSH library does not act on extensions and it is up to server
|
||||
// implementations to honor them; extensions can also be used to pass data
|
||||
// from the authentication callbacks to the server application layer.
|
||||
//
|
||||
// The one extension acted upon by this library is "no-touch-required",
|
||||
// which applies only to security-key public keys
|
||||
// (sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com).
|
||||
// When present, it waives the default requirement that SK signatures
|
||||
// assert user presence (i.e. a physical touch of the authenticator)
|
||||
// during signature verification.
|
||||
Extensions map[string]string
|
||||
|
||||
// ExtraData allows to store user defined data.
|
||||
@@ -84,6 +89,79 @@ type ServerPreAuthConn interface {
|
||||
SendAuthBanner(string) error
|
||||
}
|
||||
|
||||
// noTouchRequiredExtension is the extension name used by OpenSSH in
|
||||
// authorized_keys options and certificate extensions to mark keys
|
||||
// whose signatures do not need to assert user presence (touch). See
|
||||
// ssh-keygen(1) and sshd(8).
|
||||
const noTouchRequiredExtension = "no-touch-required"
|
||||
|
||||
// noTouchAllowed reports whether the user presence requirement on
|
||||
// SK signatures should be waived for this authentication attempt. The
|
||||
// requirement is waived when the "no-touch-required" extension is
|
||||
// present either in the Permissions returned by the auth callback
|
||||
// (authorized_keys-level opt-out) or in the certificate's own
|
||||
// Extensions (CA-level opt-out), matching OpenSSH behavior. OpenSSH
|
||||
// reads the per-key opt-out only from cert Extensions and
|
||||
// authorized_keys options (never from CriticalOptions); we follow the
|
||||
// same rule.
|
||||
func noTouchAllowed(pubKey PublicKey, perms *Permissions) bool {
|
||||
if perms != nil {
|
||||
if _, ok := perms.Extensions[noTouchRequiredExtension]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if cert, ok := pubKey.(*Certificate); ok {
|
||||
if _, ok := cert.Extensions[noTouchRequiredExtension]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// skKeyWithoutUP returns a PublicKey equivalent to pubKey but whose
|
||||
// Verify accepts SK signatures with the user-presence flag clear. If
|
||||
// pubKey is not (and does not wrap) an SK key, pubKey is returned
|
||||
// unchanged. The returned value never mutates pubKey: for SK keys a
|
||||
// shallow copy is made so that the noTouchRequired flag is set only on
|
||||
// the clone.
|
||||
//
|
||||
// The implementation is iterative rather than recursive. When pubKey
|
||||
// is a *Certificate we unwrap exactly one level to look at the inner
|
||||
// key. The SSH cert format forbids Certificate.Key from being another
|
||||
// Certificate (parseCert rejects it), but nothing stops callers from
|
||||
// constructing such a value directly in Go; a recursive descent could
|
||||
// otherwise be driven to unbounded depth by a hand-crafted or cyclic
|
||||
// Certificate. A malformed input of that shape simply returns
|
||||
// unchanged here.
|
||||
func skKeyWithoutUP(pubKey PublicKey) PublicKey {
|
||||
cert, isCert := pubKey.(*Certificate)
|
||||
target := pubKey
|
||||
if isCert {
|
||||
target = cert.Key
|
||||
}
|
||||
var cloned PublicKey
|
||||
switch k := target.(type) {
|
||||
case *skECDSAPublicKey:
|
||||
c := *k
|
||||
c.noTouchRequired = true
|
||||
cloned = &c
|
||||
case *skEd25519PublicKey:
|
||||
c := *k
|
||||
c.noTouchRequired = true
|
||||
cloned = &c
|
||||
default:
|
||||
// Not an SK key (or a pathological *Certificate wrapping
|
||||
// another *Certificate): pubKey is already usable for Verify.
|
||||
return pubKey
|
||||
}
|
||||
if !isCert {
|
||||
return cloned
|
||||
}
|
||||
c := *cert
|
||||
c.Key = cloned
|
||||
return &c
|
||||
}
|
||||
|
||||
// ServerConfig holds server specific configuration data.
|
||||
type ServerConfig struct {
|
||||
// Config contains configuration shared between client and server.
|
||||
@@ -242,8 +320,10 @@ func (c *pubKeyCache) add(candidate cachedPubKey) {
|
||||
type ServerConn struct {
|
||||
Conn
|
||||
|
||||
// If the succeeding authentication callback returned a
|
||||
// non-nil Permissions pointer, it is stored here.
|
||||
// If the succeeding authentication callback returned a non-nil Permissions
|
||||
// pointer, it is stored here. These are the permissions from the final,
|
||||
// successful authentication method. Permissions returned by callbacks that
|
||||
// return PartialSuccessError are not preserved and must be nil.
|
||||
Permissions *Permissions
|
||||
}
|
||||
|
||||
@@ -737,8 +817,15 @@ userAuthLoop:
|
||||
}
|
||||
|
||||
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
|
||||
|
||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
||||
// pubKey is reused below for VerifiedPublicKeyCallback and
|
||||
// must remain the key as presented by the client; derive a
|
||||
// separate value for Verify that carries any applicable
|
||||
// no-touch-required opt-out.
|
||||
pubKeyForVerify := pubKey
|
||||
if noTouchAllowed(pubKey, candidate.perms) {
|
||||
pubKeyForVerify = skKeyWithoutUP(pubKey)
|
||||
}
|
||||
if err := pubKeyForVerify.Verify(signedData, sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -750,6 +837,13 @@ userAuthLoop:
|
||||
// considered verified and the callback must not run.
|
||||
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
||||
}
|
||||
if authErr == nil && perms != nil && perms.CriticalOptions != nil {
|
||||
if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" {
|
||||
if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil {
|
||||
authErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "gssapi-with-mic":
|
||||
if authConfig.GSSAPIWithMICConfig == nil {
|
||||
@@ -824,6 +918,13 @@ userAuthLoop:
|
||||
var failureMsg userAuthFailureMsg
|
||||
|
||||
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
||||
// Permissions are not preserved between authentication steps. To
|
||||
// avoid confusion about the final state of the connection, we
|
||||
// disallow returning non-nil Permissions combined with
|
||||
// PartialSuccessError.
|
||||
if perms != nil {
|
||||
return nil, errors.New("ssh: permissions must be nil when returning PartialSuccessError")
|
||||
}
|
||||
// After a partial success error we don't allow changing the user
|
||||
// name and execute the NoClientAuthCallback.
|
||||
partialSuccessReturned = true
|
||||
|
||||
@@ -6,9 +6,14 @@ package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -664,6 +669,44 @@ func TestVerifiedPubKeyCallbackError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
return nil, nil
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
return &Permissions{
|
||||
CriticalOptions: map[string]string{
|
||||
sourceAddressCriticalOption: "192.168.99.99",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("client login succeed with VerifiedPublicKeyCallback returning mismatching source-address")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
@@ -808,6 +851,60 @@ func TestVerifiedPublicKeyCallbackOnly(t *testing.T) {
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestPartialSuccessWithNonNilPerms(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
// Intentionally return non-nil Permissions along with a
|
||||
// PartialSuccessError. Since permissions are reset between
|
||||
// authentication steps, this constitutes invalid library usage
|
||||
// and the server is expected to reject the connection.
|
||||
return &Permissions{Extensions: map[string]string{"permit-port-forwarding": ""}}, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("authentication succeeded unexpectedly; server should have rejected non-nil Permissions combined with PartialSuccessError")
|
||||
}
|
||||
}
|
||||
|
||||
type markerConn struct {
|
||||
closed uint32
|
||||
used uint32
|
||||
@@ -850,3 +947,126 @@ func (*markerConn) RemoteAddr() net.Addr { return nil }
|
||||
func (*markerConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (*markerConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (*markerConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
// skTestSigner is a Signer that produces SK-ECDSA signatures over
|
||||
// user-auth data, simulating a FIDO/U2F authenticator. The flags
|
||||
// byte (UP and other bits) is caller-controlled so tests can exercise
|
||||
// the server's user-presence enforcement and its opt-out paths. This
|
||||
// is test-only: the real Signer is hardware-backed.
|
||||
type skTestSigner struct {
|
||||
priv *ecdsa.PrivateKey
|
||||
pub PublicKey
|
||||
flags byte
|
||||
application string
|
||||
}
|
||||
|
||||
func (s *skTestSigner) PublicKey() PublicKey { return s.pub }
|
||||
|
||||
func (s *skTestSigner) Sign(r io.Reader, data []byte) (*Signature, error) {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(s.application))
|
||||
appDigest := h.Sum(nil)
|
||||
h.Reset()
|
||||
h.Write(data)
|
||||
dataDigest := h.Sum(nil)
|
||||
var counter uint32 = 1
|
||||
blob := struct {
|
||||
ApplicationDigest []byte `ssh:"rest"`
|
||||
Flags byte
|
||||
Counter uint32
|
||||
MessageDigest []byte `ssh:"rest"`
|
||||
}{appDigest, s.flags, counter, dataDigest}
|
||||
h.Reset()
|
||||
h.Write(Marshal(blob))
|
||||
digest := h.Sum(nil)
|
||||
x, y, err := ecdsa.Sign(r, s.priv, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Signature{
|
||||
Format: KeyAlgoSKECDSA256,
|
||||
Blob: Marshal(struct{ R, S *big.Int }{x, y}),
|
||||
Rest: Marshal(struct {
|
||||
Flags byte
|
||||
Counter uint32
|
||||
}{s.flags, counter}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TestServerAuthSKUserPresence drives the full userAuthLoop with an SK
|
||||
// public-key client and verifies the server's wiring of the UP check
|
||||
// and its two opt-out paths: the per-key Permissions.Extensions route
|
||||
// and (via cert) the cert-level route. It also confirms that non-SK
|
||||
// clients are unaffected by the new code path.
|
||||
func TestServerAuthSKUserPresence(t *testing.T) {
|
||||
userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
skPub := &skECDSAPublicKey{application: "ssh:", PublicKey: userKey.PublicKey}
|
||||
|
||||
runAuth := func(t *testing.T, signer Signer, perms *Permissions) error {
|
||||
t.Helper()
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(ConnMetadata, PublicKey) (*Permissions, error) {
|
||||
return perms, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
|
||||
serverErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, _, err := NewServerConn(c1, serverConf)
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
clientConf := &ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{PublicKeys(signer)},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
_, _, _, clientErr := NewClientConn(c2, "", clientConf)
|
||||
<-serverErr
|
||||
return clientErr
|
||||
}
|
||||
|
||||
optOut := &Permissions{Extensions: map[string]string{noTouchRequiredExtension: ""}}
|
||||
|
||||
t.Run("UP=1, default perms accepts", func(t *testing.T) {
|
||||
s := &skTestSigner{priv: userKey, pub: skPub, flags: flagUserPresence, application: "ssh:"}
|
||||
if err := runAuth(t, s, nil); err != nil {
|
||||
t.Errorf("expected auth to succeed: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("UP=0, default perms rejects", func(t *testing.T) {
|
||||
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||
if err := runAuth(t, s, nil); err == nil {
|
||||
t.Error("expected auth to fail with UP=0")
|
||||
}
|
||||
})
|
||||
t.Run("UP=0, perms opt-out accepts", func(t *testing.T) {
|
||||
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||
if err := runAuth(t, s, optOut); err != nil {
|
||||
t.Errorf("expected auth to succeed with opt-out: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("UP=0, perms CriticalOptions does NOT opt out", func(t *testing.T) {
|
||||
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||
critOnly := &Permissions{CriticalOptions: map[string]string{noTouchRequiredExtension: ""}}
|
||||
if err := runAuth(t, s, critOnly); err == nil {
|
||||
t.Error("no-touch-required in CriticalOptions must not waive UP")
|
||||
}
|
||||
})
|
||||
t.Run("non-SK RSA signer unaffected", func(t *testing.T) {
|
||||
if err := runAuth(t, testSigners["rsa"], nil); err != nil {
|
||||
t.Errorf("plain RSA auth must still work: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,9 +24,8 @@ func TestAgentForward(t *testing.T) {
|
||||
t.Fatalf("Error adding key: %s", err)
|
||||
}
|
||||
if err := keyring.Add(agent.AddedKey{
|
||||
PrivateKey: testPrivateKeys["ecdsa"],
|
||||
ConfirmBeforeUse: true,
|
||||
LifetimeSecs: 3600,
|
||||
PrivateKey: testPrivateKeys["ecdsa"],
|
||||
LifetimeSecs: 3600,
|
||||
}); err != nil {
|
||||
t.Fatalf("Error adding key with constraints: %s", err)
|
||||
}
|
||||
|
||||
Binary file not shown.
+122
-291
@@ -21,877 +21,708 @@ var unparsedCertificates = []unparsedCertificate{
|
||||
certStartOff: 2633,
|
||||
certLength: 1471,
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Commercial,O=AffirmTrust,C=US",
|
||||
sha256Hash: "0376ab1d54c5f9803ce4b2e201a0ee7eef7b57b636e8a93c9b8d4860c96f5fa7",
|
||||
certStartOff: 4104,
|
||||
certLength: 848,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Networking,O=AffirmTrust,C=US",
|
||||
sha256Hash: "0a81ec5a929777f145904af38d5d509f66b5e2c58fcdb531058b0e17f3f0b41b",
|
||||
certStartOff: 4952,
|
||||
certLength: 848,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Premium ECC,O=AffirmTrust,C=US",
|
||||
sha256Hash: "bd71fdf6da97e4cf62d1647add2581b07d79adf8397eb4ecba9c5e8488821423",
|
||||
certStartOff: 5800,
|
||||
certLength: 514,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Premium,O=AffirmTrust,C=US",
|
||||
sha256Hash: "70a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a",
|
||||
certStartOff: 6314,
|
||||
certLength: 1354,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 1,O=Amazon,C=US",
|
||||
sha256Hash: "8ecde6884f3d87b1125ba31ac3fcb13d7016de7f57cc904fe1cb97c6ae98196e",
|
||||
certStartOff: 7668,
|
||||
certStartOff: 4104,
|
||||
certLength: 837,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 2,O=Amazon,C=US",
|
||||
sha256Hash: "1ba5b2aa8c65401a82960118f80bec4f62304d83cec4713a19c39c011ea46db4",
|
||||
certStartOff: 8505,
|
||||
certStartOff: 4941,
|
||||
certLength: 1349,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 3,O=Amazon,C=US",
|
||||
sha256Hash: "18ce6cfe7bf14e60b2e347b8dfe868cb31d02ebb3ada271569f50343b46db3a4",
|
||||
certStartOff: 9854,
|
||||
certStartOff: 6290,
|
||||
certLength: 442,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 4,O=Amazon,C=US",
|
||||
sha256Hash: "e35d28419ed02025cfa69038cd623962458da5c695fbdea3c22b0bfb25897092",
|
||||
certStartOff: 10296,
|
||||
certStartOff: 6732,
|
||||
certLength: 502,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot 2011,O=Atos,C=DE",
|
||||
sha256Hash: "f356bea244b7a91eb35d53ca9ad7864ace018e2d35d5f8f96ddf68a6f41aa474",
|
||||
certStartOff: 10798,
|
||||
certStartOff: 7234,
|
||||
certLength: 891,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot Root CA ECC TLS 2021,O=Atos,C=DE",
|
||||
sha256Hash: "b2fae53e14ccd7ab9212064701ae279c1d8988facb775fa8a008914e663988a8",
|
||||
certStartOff: 11689,
|
||||
certStartOff: 8125,
|
||||
certLength: 537,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot Root CA RSA TLS 2021,O=Atos,C=DE",
|
||||
sha256Hash: "81a9088ea59fb364c548a6f85559099b6f0405efbf18e5324ec9f457ba00112f",
|
||||
certStartOff: 12226,
|
||||
certStartOff: 8662,
|
||||
certLength: 1384,
|
||||
},
|
||||
{
|
||||
cn: "CN=Autoridad de Certificacion Firmaprofesional CIF A62634068,C=ES",
|
||||
sha256Hash: "57de0583efd2b26e0361da99da9df4648def7ee8441c3b728afa9bcde0f9b26a",
|
||||
certStartOff: 13610,
|
||||
certStartOff: 10046,
|
||||
certLength: 1560,
|
||||
},
|
||||
{
|
||||
cn: "CN=BJCA Global Root CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||
sha256Hash: "f3896f88fe7c0a882766a7fa6ad2749fb57a7f3e98fb769c1fa7b09c2c44d5ae",
|
||||
certStartOff: 15170,
|
||||
certStartOff: 11606,
|
||||
certLength: 1400,
|
||||
},
|
||||
{
|
||||
cn: "CN=BJCA Global Root CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||
sha256Hash: "574df6931e278039667b720afdc1600fc27eb66dd3092979fb73856487212882",
|
||||
certStartOff: 16570,
|
||||
certStartOff: 13006,
|
||||
certLength: 553,
|
||||
},
|
||||
{
|
||||
cn: "CN=Buypass Class 2 Root CA,O=Buypass AS-983163327,C=NO",
|
||||
sha256Hash: "9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48",
|
||||
certStartOff: 17123,
|
||||
certStartOff: 13559,
|
||||
certLength: 1373,
|
||||
},
|
||||
{
|
||||
cn: "CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO",
|
||||
sha256Hash: "edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d",
|
||||
certStartOff: 18496,
|
||||
certStartOff: 14932,
|
||||
certLength: 1373,
|
||||
},
|
||||
{
|
||||
cn: "CN=CA Disig Root R2,O=Disig a.s.,L=Bratislava,C=SK",
|
||||
sha256Hash: "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
|
||||
certStartOff: 19869,
|
||||
certStartOff: 16305,
|
||||
certLength: 1389,
|
||||
},
|
||||
{
|
||||
cn: "CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN",
|
||||
sha256Hash: "5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd",
|
||||
certStartOff: 21258,
|
||||
certStartOff: 17694,
|
||||
certLength: 1425,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "0c2cd63df7806fa399ede809116b575bf87989f06518f9808c860503178baf66",
|
||||
certStartOff: 22683,
|
||||
certLength: 1057,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7",
|
||||
certStartOff: 23740,
|
||||
certStartOff: 19119,
|
||||
certLength: 653,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234",
|
||||
certStartOff: 24393,
|
||||
certStartOff: 19772,
|
||||
certLength: 1500,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certainly Root E1,O=Certainly,C=US",
|
||||
sha256Hash: "b4585f22e4ac756a4e8612a1361c5d9d031a93fd84febb778fa3068b0fc42dc2",
|
||||
certStartOff: 25893,
|
||||
certStartOff: 21272,
|
||||
certLength: 507,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certainly Root R1,O=Certainly,C=US",
|
||||
sha256Hash: "77b82cd8644c4305f7acc5cb156b45675004033d51c60c6202a8e0c33467d3a0",
|
||||
certStartOff: 26400,
|
||||
certStartOff: 21779,
|
||||
certLength: 1355,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certigna Root CA,OU=0002 48146308100036,O=Dhimyotis,C=FR",
|
||||
sha256Hash: "d48d3d23eedb50a459e55197601c27774b9d7b18c94d5a059511a10250b93168",
|
||||
certStartOff: 27755,
|
||||
certStartOff: 23134,
|
||||
certLength: 1631,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certigna,O=Dhimyotis,C=FR",
|
||||
sha256Hash: "e3b6a2db2ed7ce48842f7ac53241c7b71d54144bfb40c11f3f1d0b42f5eea12d",
|
||||
certStartOff: 29386,
|
||||
certLength: 940,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||
sha256Hash: "6b328085625318aa50d173c98d8bda09d57e27413d114cf787a0f5d06c030cf6",
|
||||
certStartOff: 30326,
|
||||
certStartOff: 24765,
|
||||
certLength: 617,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||
sha256Hash: "b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804",
|
||||
certStartOff: 30943,
|
||||
certStartOff: 25382,
|
||||
certLength: 1494,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||
sha256Hash: "5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e",
|
||||
certStartOff: 32437,
|
||||
certStartOff: 26876,
|
||||
certLength: 959,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||
sha256Hash: "fe7696573855773e37a95e7ad4d9cc96c30157c15d31765ba9b15704e1ae78fd",
|
||||
certStartOff: 33396,
|
||||
certStartOff: 27835,
|
||||
certLength: 1476,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US",
|
||||
sha256Hash: "11437cda7bb45e41365f45b39a38986b0de00def348e0c7bb0873633800bc38b",
|
||||
certStartOff: 34872,
|
||||
certLength: 545,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US",
|
||||
sha256Hash: "2ffb7f813bbbb3c89ab4e8162d0f16d71509a830cc9d73c262e5140875d1ad4a",
|
||||
certStartOff: 35417,
|
||||
certLength: 544,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US",
|
||||
sha256Hash: "02bdf96e2a45dd9bf18fc7e1dbdf21a0379ba3c9c2610344cfd8d606fec1ed81",
|
||||
certStartOff: 35961,
|
||||
certLength: 1392,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US",
|
||||
sha256Hash: "ffe943d793424b4f7c440c1c3d648d5363f34b82dc87aa7a9f118fc5dee101f1",
|
||||
certStartOff: 37353,
|
||||
certLength: 1392,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST BR Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "e59aaa816009c22bff5b25bad37df306f049797c1f81d85ab089e657bd8f0044",
|
||||
certStartOff: 38745,
|
||||
certStartOff: 29311,
|
||||
certLength: 735,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST BR Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "0552e6f83fdf65e8fa9670e666df28a4e21340b510cbe52566f97c4fb94b2bd1",
|
||||
certStartOff: 39480,
|
||||
certStartOff: 30046,
|
||||
certLength: 1453,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST EV Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "08170d1aa36453901a2f959245e347db0c8d37abaabc56b81aa100dc958970db",
|
||||
certStartOff: 40933,
|
||||
certStartOff: 31499,
|
||||
certLength: 735,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST EV Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "8e8221b2e7d4007836a1672f0dcc299c33bc07d316f132fa1a206d587150f1ce",
|
||||
certStartOff: 41668,
|
||||
certStartOff: 32234,
|
||||
certLength: 1453,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST Root Class 3 CA 2 2009,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1",
|
||||
certStartOff: 43121,
|
||||
certStartOff: 33687,
|
||||
certLength: 1079,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881",
|
||||
certStartOff: 44200,
|
||||
certStartOff: 34766,
|
||||
certLength: 1095,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "3e9099b5015e8f486c00bcea9d111ee721faba355a89bcf1df69561e3dc6325c",
|
||||
certStartOff: 45295,
|
||||
certLength: 955,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185",
|
||||
certStartOff: 46250,
|
||||
certStartOff: 35861,
|
||||
certLength: 922,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2",
|
||||
certStartOff: 47172,
|
||||
certStartOff: 36783,
|
||||
certLength: 586,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161",
|
||||
certStartOff: 47758,
|
||||
certLength: 947,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f",
|
||||
certStartOff: 48705,
|
||||
certStartOff: 37369,
|
||||
certLength: 914,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0",
|
||||
certStartOff: 49619,
|
||||
certStartOff: 38283,
|
||||
certLength: 579,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf",
|
||||
certStartOff: 50198,
|
||||
certLength: 969,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||
sha256Hash: "018e13f0772532cf809bd1b17281867283fc48c6e13be9c69812854a490c1b05",
|
||||
certStartOff: 51167,
|
||||
certStartOff: 38862,
|
||||
certLength: 541,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||
sha256Hash: "371a00dc0533b3721a7eeb40e8419e70799d2b0a0f2c1d80693165f7cec4ad75",
|
||||
certStartOff: 51708,
|
||||
certStartOff: 39403,
|
||||
certLength: 1386,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
|
||||
certStartOff: 53094,
|
||||
certStartOff: 40789,
|
||||
certLength: 1428,
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority - EC1,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "02ed0eb28c14da45165c566791700d6451d7fb56f0b2ab1d3b8eb070e56edff5",
|
||||
certStartOff: 54522,
|
||||
certLength: 765,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority - G2,OU=See www.entrust.net/legal-terms+OU=(c) 2009 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "43df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f339",
|
||||
certStartOff: 55287,
|
||||
certLength: 1090,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority,OU=www.entrust.net/CPS is incorporated by reference+OU=(c) 2006 Entrust\\, Inc.,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "73c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c",
|
||||
certStartOff: 56377,
|
||||
certStartOff: 42217,
|
||||
certLength: 1173,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=FIRMAPROFESIONAL CA ROOT-A WEB,O=Firmaprofesional SA,C=ES,2.5.4.97=#130f56415445532d413632363334303638",
|
||||
sha256Hash: "bef256daf26e9c69bdec1602359798f3caf71821a03e018257c53c65617f3d4a",
|
||||
certStartOff: 57550,
|
||||
certLength: 638,
|
||||
},
|
||||
{
|
||||
cn: "CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN",
|
||||
sha256Hash: "bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893",
|
||||
certStartOff: 58188,
|
||||
certStartOff: 43390,
|
||||
certLength: 1420,
|
||||
},
|
||||
{
|
||||
cn: "CN=GLOBALTRUST 2020,O=e-commerce monitoring GmbH,C=AT",
|
||||
sha256Hash: "9a296a5182d1d451a2e37f439b74daafa267523329f90f9a0d2007c334e23c9a",
|
||||
certStartOff: 59608,
|
||||
certLength: 1414,
|
||||
distrustAfter: "2024-06-30T00:00:00Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R1,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf",
|
||||
certStartOff: 61022,
|
||||
certLength: 1371,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R2,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "8d25cd97229dbf70356bda4eb3cc734031e24cf00fafcfd32dc76eb5841c7ea8",
|
||||
certStartOff: 62393,
|
||||
certStartOff: 44810,
|
||||
certLength: 1371,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R3,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "34d8a73ee208d9bcdb0d956520934b4e40e69482596e8b6f73c8426b010a6f48",
|
||||
certStartOff: 63764,
|
||||
certStartOff: 46181,
|
||||
certLength: 525,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R4,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "349dfa4058c5e263123b398ae795573c4e1313c83fe68f93556cd5e8031b3c7d",
|
||||
certStartOff: 64289,
|
||||
certStartOff: 46706,
|
||||
certLength: 525,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE",
|
||||
sha256Hash: "cbb9c44d84b8043e1050ea31a69f514955d7bfd2e2c6b49301019ad61d9f5058",
|
||||
certStartOff: 64814,
|
||||
certStartOff: 47231,
|
||||
certLength: 527,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE",
|
||||
sha256Hash: "4fa3126d8d3a11d1c4855a4f807cbad6cf919d3a5a88b03bea2c6372d93c40c9",
|
||||
certStartOff: 65341,
|
||||
certStartOff: 47758,
|
||||
certLength: 1374,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R4,O=GlobalSign",
|
||||
sha256Hash: "b085d70b964f191a73e4af0d54ae7a0e07aafdaf9b71dd0862138ab7325a24a2",
|
||||
certStartOff: 66715,
|
||||
certStartOff: 49132,
|
||||
certLength: 480,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign",
|
||||
sha256Hash: "179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924",
|
||||
certStartOff: 67195,
|
||||
certStartOff: 49612,
|
||||
certLength: 546,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign",
|
||||
sha256Hash: "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b",
|
||||
certStartOff: 67741,
|
||||
certStartOff: 50158,
|
||||
certLength: 867,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign",
|
||||
sha256Hash: "2cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69",
|
||||
certStartOff: 68608,
|
||||
certStartOff: 51025,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda",
|
||||
certStartOff: 70023,
|
||||
certStartOff: 52440,
|
||||
certLength: 969,
|
||||
},
|
||||
{
|
||||
cn: "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||
sha256Hash: "3f99cc474acfce4dfed58794665e478d1547739f2e780f1bb4ca9b133097d401",
|
||||
certStartOff: 70992,
|
||||
certStartOff: 53409,
|
||||
certLength: 600,
|
||||
},
|
||||
{
|
||||
cn: "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||
sha256Hash: "d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d",
|
||||
certStartOff: 71592,
|
||||
certStartOff: 54009,
|
||||
certLength: 1448,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hellenic Academic and Research Institutions ECC RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||
sha256Hash: "44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833",
|
||||
certStartOff: 73040,
|
||||
certStartOff: 55457,
|
||||
certLength: 711,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hellenic Academic and Research Institutions RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||
sha256Hash: "a040929a02ce53b4acf4f2ffc6981ce4496f755e6d45fe0b2a692bcd52523f36",
|
||||
certStartOff: 73751,
|
||||
certStartOff: 56168,
|
||||
certLength: 1551,
|
||||
},
|
||||
{
|
||||
cn: "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
||||
sha256Hash: "f015ce3cc239bfef064be9f1d2c417e1a0264a0a94be1f0c8d121864eb6949cc",
|
||||
certStartOff: 75302,
|
||||
certStartOff: 57719,
|
||||
certLength: 1390,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK",
|
||||
sha256Hash: "5a2fc03f0c83b090bbfa40604b0988446c7636183df9846e17101a447fb8efd6",
|
||||
certStartOff: 76692,
|
||||
certStartOff: 59109,
|
||||
certLength: 1491,
|
||||
},
|
||||
{
|
||||
cn: "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
|
||||
sha256Hash: "96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6",
|
||||
certStartOff: 78183,
|
||||
certStartOff: 60600,
|
||||
certLength: 1391,
|
||||
},
|
||||
{
|
||||
cn: "CN=ISRG Root X2,O=Internet Security Research Group,C=US",
|
||||
sha256Hash: "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470",
|
||||
certStartOff: 79574,
|
||||
certStartOff: 61991,
|
||||
certLength: 543,
|
||||
},
|
||||
{
|
||||
cn: "CN=IdenTrust Commercial Root CA 1,O=IdenTrust,C=US",
|
||||
sha256Hash: "5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae",
|
||||
certStartOff: 80117,
|
||||
certStartOff: 62534,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=IdenTrust Public Sector Root CA 1,O=IdenTrust,C=US",
|
||||
sha256Hash: "30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f",
|
||||
certStartOff: 81497,
|
||||
certStartOff: 63914,
|
||||
certLength: 1386,
|
||||
},
|
||||
{
|
||||
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
|
||||
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
|
||||
certStartOff: 82883,
|
||||
certStartOff: 65300,
|
||||
certLength: 1525,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU,1.2.840.113549.1.9.1=#0c10696e666f40652d737a69676e6f2e6875",
|
||||
sha256Hash: "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
|
||||
certStartOff: 84408,
|
||||
certStartOff: 66825,
|
||||
certLength: 1038,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsoft ECC Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||
sha256Hash: "358df39d764af9e1b766e9c972df352ee15cfac227af6ad1d70e8e4a6edcba02",
|
||||
certStartOff: 85446,
|
||||
certStartOff: 67863,
|
||||
certLength: 605,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsoft RSA Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||
sha256Hash: "c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0",
|
||||
certStartOff: 86051,
|
||||
certStartOff: 68468,
|
||||
certLength: 1452,
|
||||
},
|
||||
{
|
||||
cn: "CN=NAVER Global Root Certification Authority,O=NAVER BUSINESS PLATFORM Corp.,C=KR",
|
||||
sha256Hash: "88f438dcf8ffd1fa8f429115ffe5f82ae1e06e0c70c375faad717b34a49e7265",
|
||||
certStartOff: 87503,
|
||||
certStartOff: 69920,
|
||||
certLength: 1446,
|
||||
},
|
||||
{
|
||||
cn: "CN=NetLock Arany (Class Gold) Főtanúsítvány,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU",
|
||||
sha256Hash: "6c61dac3a2def031506be036d2a6fe401994fbd13df9c8d466599274c446ec98",
|
||||
certStartOff: 88949,
|
||||
certStartOff: 71366,
|
||||
certLength: 1049,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE Server Root ECC G1,O=OISTE Foundation,C=CH",
|
||||
sha256Hash: "eec997c0c30f216f7e3b8b307d2bae42412d753fc8219dafd1520b2572850f49",
|
||||
certStartOff: 89998,
|
||||
certStartOff: 72415,
|
||||
certLength: 569,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH",
|
||||
sha256Hash: "9ae36232a5189ffddb353dfd26520c015395d22777dac59db57b98c089a651e6",
|
||||
certStartOff: 90567,
|
||||
certStartOff: 72984,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||
sha256Hash: "6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6",
|
||||
certStartOff: 91982,
|
||||
certStartOff: 74399,
|
||||
certLength: 953,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE WISeKey Global Root GC CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||
sha256Hash: "8560f91c3624daba9570b5fea0dbe36ff11a8323be9486854fb3f34a5571198d",
|
||||
certStartOff: 92935,
|
||||
certStartOff: 75352,
|
||||
certLength: 621,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 1 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74",
|
||||
certStartOff: 93556,
|
||||
certStartOff: 75973,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840",
|
||||
certStartOff: 94936,
|
||||
certStartOff: 77353,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 2,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "85a0dd7dd720adb7ff05f83d542b209dc7ff4528f7d677b18389fea5e5c49e86",
|
||||
certStartOff: 96316,
|
||||
certLength: 1467,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 3 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46",
|
||||
certStartOff: 97783,
|
||||
certStartOff: 78733,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "18f1fc7f205df8adddeb7fe007dd57e3af375a9c4d8d73546bf4f1fed1e18d35",
|
||||
certStartOff: 99163,
|
||||
certLength: 1697,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "22a2c1f7bded704cc1e701b5f408c310880fe956b5de2a4a44f99c873a25a7c8",
|
||||
certStartOff: 100860,
|
||||
certStartOff: 80113,
|
||||
certLength: 664,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "2e7bf16cc22485a7bbe2aa8696750761b0ae39be3b2fe9d0cc6d4ef73491425c",
|
||||
certStartOff: 101524,
|
||||
certStartOff: 80777,
|
||||
certLength: 1519,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "3417bb06cc6007da1b961c920b8ab4ce3fad820e4aa30b9acbc4a74ebdcebc65",
|
||||
certStartOff: 103043,
|
||||
certStartOff: 82296,
|
||||
certLength: 657,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com Root Certification Authority RSA,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69",
|
||||
certStartOff: 103700,
|
||||
certStartOff: 82953,
|
||||
certLength: 1505,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US",
|
||||
sha256Hash: "c32ffd9f46f936d16c3673990959434b9ad60aafbb9e7cf33654f144cc1ba143",
|
||||
certStartOff: 105205,
|
||||
certStartOff: 84458,
|
||||
certLength: 574,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US",
|
||||
sha256Hash: "8faf7d2e2cb4709bb8e0b33666bf75a5dd45b5de480f8ea8d4bfe6bebc17f2ed",
|
||||
certStartOff: 105779,
|
||||
certStartOff: 85032,
|
||||
certLength: 1421,
|
||||
},
|
||||
{
|
||||
cn: "CN=SZAFIR ROOT CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL",
|
||||
sha256Hash: "a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe",
|
||||
certStartOff: 107200,
|
||||
certStartOff: 86453,
|
||||
certLength: 886,
|
||||
},
|
||||
{
|
||||
cn: "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB",
|
||||
sha256Hash: "c90f26f0fb1b4018b22227519b5ca2b53e2ca5b3be5cf18efe1bef47380c5383",
|
||||
certStartOff: 108086,
|
||||
certStartOff: 87339,
|
||||
certLength: 574,
|
||||
},
|
||||
{
|
||||
cn: "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB",
|
||||
sha256Hash: "7bb647a62aeeac88bf257aa522d01ffea395e0ab45c73f93f65654ec38f25a06",
|
||||
certStartOff: 108660,
|
||||
certStartOff: 87913,
|
||||
certLength: 1422,
|
||||
},
|
||||
{
|
||||
cn: "CN=Secure Global CA,O=SecureTrust Corporation,C=US",
|
||||
sha256Hash: "4200f5043ac8590ebb527d209ed1503029fbcbd41ca1b506ec27f15ade7dac69",
|
||||
certStartOff: 110082,
|
||||
certLength: 960,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA12,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "3f034bb5704d44b2d08545a02057de93ebf3905fce721acbc730c06ddaee904e",
|
||||
certStartOff: 111042,
|
||||
certStartOff: 89335,
|
||||
certLength: 886,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA14,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "4b009c1034494f9ab56bba3ba1d62731fc4d20d8955adcec10a925607261e338",
|
||||
certStartOff: 111928,
|
||||
certStartOff: 90221,
|
||||
certLength: 1398,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA15,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "e778f0f095fe843729cd1a0082179e5314a9c291442805e1fb1d8fb6b8886c3a",
|
||||
certStartOff: 113326,
|
||||
certStartOff: 91619,
|
||||
certLength: 551,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureTrust CA,O=SecureTrust Corporation,C=US",
|
||||
sha256Hash: "f1c1b50ae5a20dd8030ec9f6bc24823dd367b5255759b4e71b61fce9f7375d73",
|
||||
certStartOff: 113877,
|
||||
certLength: 956,
|
||||
},
|
||||
{
|
||||
cn: "CN=Security Communication ECC RootCA1,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||
sha256Hash: "e74fbda55bd564c473a36b441aa799c8a68e077440e8288b9fa1e50e4bbaca11",
|
||||
certStartOff: 114833,
|
||||
certStartOff: 92170,
|
||||
certLength: 572,
|
||||
},
|
||||
{
|
||||
cn: "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5",
|
||||
certStartOff: 115405,
|
||||
certStartOff: 92742,
|
||||
certLength: 993,
|
||||
},
|
||||
{
|
||||
cn: "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
|
||||
certStartOff: 116398,
|
||||
certStartOff: 93735,
|
||||
certLength: 1011,
|
||||
},
|
||||
{
|
||||
cn: "CN=SwissSign Gold CA - G2,O=SwissSign AG,C=CH",
|
||||
sha256Hash: "62dd0be9b9f50a163ea0f8e75c053b1eca57ea55c8688f647c6881f2c8357b95",
|
||||
certStartOff: 117409,
|
||||
certLength: 1470,
|
||||
},
|
||||
{
|
||||
cn: "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH",
|
||||
sha256Hash: "193144f431e0fddb740717d4de926a571133884b4360d30e272913cbe660ce41",
|
||||
certStartOff: 118879,
|
||||
certStartOff: 94746,
|
||||
certLength: 1431,
|
||||
},
|
||||
{
|
||||
cn: "CN=T-TeleSec GlobalRoot Class 2,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||
sha256Hash: "91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552",
|
||||
certStartOff: 120310,
|
||||
certStartOff: 96177,
|
||||
certLength: 967,
|
||||
},
|
||||
{
|
||||
cn: "CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||
sha256Hash: "fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd",
|
||||
certStartOff: 121277,
|
||||
certStartOff: 97144,
|
||||
certLength: 967,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558",
|
||||
certStartOff: 122244,
|
||||
certStartOff: 98111,
|
||||
certLength: 1425,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b",
|
||||
certStartOff: 123669,
|
||||
certStartOff: 99536,
|
||||
certLength: 1349,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44",
|
||||
certStartOff: 125018,
|
||||
certStartOff: 100885,
|
||||
certLength: 895,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telekom Security TLS ECC Root 2020,O=Deutsche Telekom Security GmbH,C=DE",
|
||||
sha256Hash: "578af4ded0853f4e5998db4aeaf9cbea8d945f60b620a38d1a3c13b2bc7ba8e1",
|
||||
certStartOff: 125913,
|
||||
certStartOff: 101780,
|
||||
certLength: 582,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telekom Security TLS RSA Root 2023,O=Deutsche Telekom Security GmbH,C=DE",
|
||||
sha256Hash: "efc65cadbb59adb6efe84da22311b35624b71b3b1ea0da8b6655174ec8978646",
|
||||
certStartOff: 126495,
|
||||
certStartOff: 102362,
|
||||
certLength: 1463,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI",
|
||||
sha256Hash: "242b69742fcb1e5b2abf98898b94572187544e5b4d9911786573621f6a74b82c",
|
||||
certStartOff: 127958,
|
||||
certStartOff: 103825,
|
||||
certLength: 1400,
|
||||
},
|
||||
{
|
||||
cn: "CN=TeliaSonera Root CA v1,O=TeliaSonera",
|
||||
sha256Hash: "dd6936fe21f8f077c123a1a521c12224f72255b73e03a7260693e8a24b0fa389",
|
||||
certStartOff: 129358,
|
||||
certLength: 1340,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia Global Root CA G3,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "e0d3226aeb1163c2e48ff9be3b50b4c6431be7bb1eacc5c36b5d5ec509039a08",
|
||||
certStartOff: 130698,
|
||||
certStartOff: 105225,
|
||||
certLength: 1449,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia Global Root CA G4,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "be4b56cb5056c0136a526df444508daa36a0b54f42e4ac38f72af470e479654c",
|
||||
certStartOff: 132147,
|
||||
certStartOff: 106674,
|
||||
certLength: 601,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia TLS ECC Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "c0076b9ef0531fb1a656d67c4ebe97cd5dbaa41ef44598acc2489878c92d8711",
|
||||
certStartOff: 132748,
|
||||
certStartOff: 107275,
|
||||
certLength: 565,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia TLS RSA Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "06c08d7dafd876971eb1124fe67f847ec0c7a158d3ea53cbe940e2ea9791f4c3",
|
||||
certStartOff: 133313,
|
||||
certStartOff: 107840,
|
||||
certLength: 1412,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "97552015f5ddfc3c8788c006944555408894450084f100867086bc1a2bb58dc8",
|
||||
certStartOff: 134725,
|
||||
certLength: 1502,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global ECC P256 Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "945bbc825ea554f489d1fd51a73ddf2ea624ac7019a05205225c22a78ccfa8b4",
|
||||
certStartOff: 136227,
|
||||
certLength: 612,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global ECC P384 Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "55903859c8c0c3ebb8759ece4e2557225ff5758bbd38ebd48276601e1bd58097",
|
||||
certStartOff: 136839,
|
||||
certLength: 673,
|
||||
},
|
||||
{
|
||||
cn: "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN",
|
||||
sha256Hash: "2e44102ab58cb85419451c8e19d9acf3662cafbc614b6a53960a30f7d0e2eb41",
|
||||
certStartOff: 137512,
|
||||
certStartOff: 109252,
|
||||
certLength: 1463,
|
||||
},
|
||||
{
|
||||
cn: "CN=UCA Extended Validation Root,O=UniTrust,C=CN",
|
||||
sha256Hash: "d43af9b35473755c9684fc06d7d8cb70ee5c28e773fb294eb41ee71722924d24",
|
||||
certStartOff: 138975,
|
||||
certStartOff: 110715,
|
||||
certLength: 1374,
|
||||
},
|
||||
{
|
||||
cn: "CN=UCA Global G2 Root,O=UniTrust,C=CN",
|
||||
sha256Hash: "9bea11c976fe014764c1be56a6f914b5a560317abd9988393382e5161aa0493c",
|
||||
certStartOff: 140349,
|
||||
certStartOff: 112089,
|
||||
certLength: 1354,
|
||||
},
|
||||
{
|
||||
cn: "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||
sha256Hash: "4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a",
|
||||
certStartOff: 141703,
|
||||
certStartOff: 113443,
|
||||
certLength: 659,
|
||||
},
|
||||
{
|
||||
cn: "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||
sha256Hash: "e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2",
|
||||
certStartOff: 142362,
|
||||
certStartOff: 114102,
|
||||
certLength: 1506,
|
||||
},
|
||||
{
|
||||
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
|
||||
sha256Hash: "beb00b30839b9bc32c32e4447905950641f26421b15ed089198b518ae2ea1b99",
|
||||
certStartOff: 143868,
|
||||
certStartOff: 115608,
|
||||
certLength: 580,
|
||||
},
|
||||
{
|
||||
cn: "CN=e-Szigno TLS Root CA 2023,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
|
||||
sha256Hash: "b49141502d00663d740f2e7ec340c52800962666121a36d09cf7dd2b90384fb4",
|
||||
certStartOff: 116188,
|
||||
certLength: 723,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign ECC Root CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||
sha256Hash: "bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3",
|
||||
certStartOff: 144448,
|
||||
certStartOff: 116911,
|
||||
certLength: 559,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign ECC Root CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||
sha256Hash: "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
|
||||
certStartOff: 145007,
|
||||
certStartOff: 117470,
|
||||
certLength: 594,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign Root CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||
sha256Hash: "125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f",
|
||||
certStartOff: 145601,
|
||||
certStartOff: 118064,
|
||||
certLength: 887,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign Root CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||
sha256Hash: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367",
|
||||
certStartOff: 146488,
|
||||
certStartOff: 118951,
|
||||
certLength: 920,
|
||||
},
|
||||
{
|
||||
cn: "CN=vTrus ECC Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||
sha256Hash: "30fbba2c32238e2a98547af97931e550428b9b3f1c8eeb6633dcfa86c5b27dd3",
|
||||
certStartOff: 147408,
|
||||
certStartOff: 119871,
|
||||
certLength: 531,
|
||||
},
|
||||
{
|
||||
cn: "CN=vTrus Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||
sha256Hash: "8a71de6559336f426c26e53880d00d88a18da4c6a91f0dcb6194e206c5c96387",
|
||||
certStartOff: 147939,
|
||||
certStartOff: 120402,
|
||||
certLength: 1370,
|
||||
},
|
||||
{
|
||||
cn: "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES",
|
||||
sha256Hash: "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
|
||||
certStartOff: 149309,
|
||||
certStartOff: 121772,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "OU=Security Communication RootCA2,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||
sha256Hash: "513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6",
|
||||
certStartOff: 150724,
|
||||
certStartOff: 123187,
|
||||
certLength: 891,
|
||||
},
|
||||
{
|
||||
cn: "OU=certSIGN ROOT CA G2,O=CERTSIGN SA,C=RO",
|
||||
sha256Hash: "657cfe2fa73faa38462571f332a2363a46fce7020951710702cdfbb6eeda3305",
|
||||
certStartOff: 151615,
|
||||
certStartOff: 124078,
|
||||
certLength: 1355,
|
||||
},
|
||||
{
|
||||
cn: "OU=certSIGN ROOT CA,O=certSIGN,C=RO",
|
||||
sha256Hash: "eaa962c4fa4a6bafebe415196d351ccd888d4f53f3fa8ae6d7c466a94e6042bb",
|
||||
certStartOff: 152970,
|
||||
certLength: 828,
|
||||
},
|
||||
{
|
||||
cn: "OU=ePKI Root Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
||||
sha256Hash: "c0a6f4dc63a24bfdcf54ef2a6a082a0a72de35803e2ff5ff527ae5d87206dfd5",
|
||||
certStartOff: 153798,
|
||||
certStartOff: 125433,
|
||||
certLength: 1460,
|
||||
distrustAfter: "2025-04-15T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "SERIALNUMBER=G63287510,CN=ANF Secure Server Root CA,OU=ANF CA Raiz,O=ANF Autoridad de Certificacion,C=ES",
|
||||
sha256Hash: "fb8fec759169b9106b1e511644c618c51304373f6c0643088d8beffd1b997599",
|
||||
certStartOff: 155258,
|
||||
certStartOff: 126893,
|
||||
certLength: 1523,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ package bundle
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -19,7 +21,7 @@ func TestBundle(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
if unparsed.cn != cert.Subject.String() {
|
||||
if !subjectsEqual(unparsed.cn, cert.Subject.String()) {
|
||||
t.Errorf("unparsedCertificates[%v].cn = %q; want = %q", i, unparsed.cn, cert.Subject.String())
|
||||
}
|
||||
|
||||
@@ -30,3 +32,34 @@ func TestBundle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// subjectsEqual reports whether two RFC 2253 DN strings match.
|
||||
//
|
||||
// It tolerates the rendering difference introduced in Go 1.27, where
|
||||
// string-typed attribute values for OIDs outside attributeTypeNames are
|
||||
// rendered as strings rather than hex-encoded DER (see Go CL 773800).
|
||||
//
|
||||
// This can be removed when Go 1.25/1.26 are no longer supported.
|
||||
func subjectsEqual(a, b string) bool {
|
||||
return a == b || normalizeHexValues(a) == normalizeHexValues(b)
|
||||
}
|
||||
|
||||
// normalizeHexValues rewrites any "oid=#hex" to the equivalent "oid=value"
|
||||
// rendering produced by Go 1.27+.
|
||||
func normalizeHexValues(s string) string {
|
||||
return hexAttrRE.ReplaceAllStringFunc(s, func(match string) string {
|
||||
m := hexAttrRE.FindStringSubmatch(match)
|
||||
der, err := hex.DecodeString(m[2])
|
||||
if err != nil {
|
||||
return match
|
||||
}
|
||||
var v string
|
||||
if rest, err := asn1.Unmarshal(der, &v); err != nil || len(rest) != 0 {
|
||||
return match
|
||||
}
|
||||
return m[1] + "=" + v
|
||||
})
|
||||
}
|
||||
|
||||
// hexAttrRE matches a "oid=#hex" attribute value in an RFC 2253 DN string.
|
||||
var hexAttrRE = regexp.MustCompile(`([\d.]+)=#([[:xdigit:]]+)`)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module golang.org/x/crypto/x509roots/fallback
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
Reference in New Issue
Block a user