Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86efde54dc | ||
|
|
a6cdac6084 | ||
|
|
39dc44e69c | ||
|
|
afebf4cb4e | ||
|
|
89f4e9bb5b | ||
|
|
71488c48c2 | ||
|
|
82adefa711 | ||
|
|
c757c9851f | ||
|
|
593c81af8a | ||
|
|
46efc8bc62 | ||
|
|
e0a54fdc4b | ||
|
|
f44d03d253 | ||
|
|
5ed494470b | ||
|
|
b07833c067 | ||
|
|
d701c51f7e | ||
|
|
999d053994 | ||
|
|
90f76b8ffe | ||
|
|
b53964a1ca | ||
|
|
626e40fc98 | ||
|
|
31914c699b | ||
|
|
f2135b814c | ||
|
|
ff03dafdb0 | ||
|
|
10b54ffa51 | ||
|
|
0f039f3a4a | ||
|
|
b27e1504e3 | ||
|
|
c1a3b97d70 | ||
|
|
b9229ecc70 | ||
|
|
e557b08ec2 | ||
|
|
4044611da8 | ||
|
|
e8ad5cb7aa | ||
|
|
fe1af3a418 | ||
|
|
243e02a382 |
+1
-1
@@ -734,7 +734,7 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
|||||||
if !acmeExts[0].Critical {
|
if !acmeExts[0].Critical {
|
||||||
t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
|
t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
|
||||||
}
|
}
|
||||||
if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
|
if !bytes.Equal(acmeExts[0].Value, extValue) {
|
||||||
t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
|
t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-8
@@ -103,9 +103,7 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, ur
|
|||||||
}
|
}
|
||||||
payload = base64.RawURLEncoding.EncodeToString(cs)
|
payload = base64.RawURLEncoding.EncodeToString(cs)
|
||||||
}
|
}
|
||||||
hash := sha.New()
|
sig, err := jwsSign(key, sha, []byte(phead+"."+payload))
|
||||||
hash.Write([]byte(phead + "." + payload))
|
|
||||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -195,14 +193,13 @@ func jwkEncode(pub crypto.PublicKey) (string, error) {
|
|||||||
return "", ErrUnsupportedKey
|
return "", ErrUnsupportedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// jwsSign signs the digest using the given key.
|
// jwsSign signs the payload using the given key.
|
||||||
// The hash is unused for ECDSA keys.
|
func jwsSign(key crypto.Signer, hash crypto.Hash, payload []byte) ([]byte, error) {
|
||||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
|
||||||
switch pub := key.Public().(type) {
|
switch pub := key.Public().(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
return key.Sign(rand.Reader, digest, hash)
|
return crypto.SignMessage(key, rand.Reader, payload, hash)
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
sigASN1, err := key.Sign(rand.Reader, digest, hash)
|
sigASN1, err := crypto.SignMessage(key, rand.Reader, payload, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-5
@@ -14,6 +14,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -353,6 +354,22 @@ func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, e
|
|||||||
return s.sig, nil
|
return s.sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type customTestMessageSigner struct {
|
||||||
|
customTestSigner
|
||||||
|
msg []byte
|
||||||
|
opts crypto.SignerOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *customTestMessageSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
|
||||||
|
return nil, errors.New("customTestSigner: Sign() called instead of SignMessage()")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *customTestMessageSigner) SignMessage(_ io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
s.msg = msg
|
||||||
|
s.opts = opts
|
||||||
|
return s.sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestJWSEncodeJSONCustom(t *testing.T) {
|
func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||||
claims := struct{ Msg string }{"hello"}
|
claims := struct{ Msg string }{"hello"}
|
||||||
const (
|
const (
|
||||||
@@ -391,24 +408,33 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
alg, phead string
|
name, phead string
|
||||||
pub crypto.PublicKey
|
pub crypto.PublicKey
|
||||||
stdsig, jwsig string
|
stdsig, jwsig string
|
||||||
|
msgSigner bool
|
||||||
}{
|
}{
|
||||||
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
|
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig, false},
|
||||||
{"RS256", rs256phead, testKey.Public(), testsig, testsig},
|
{"RS256", rs256phead, testKey.Public(), testsig, testsig, false},
|
||||||
|
{"ES256-MessageSigner", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig, true},
|
||||||
|
{"RS256-MessageSigner", rs256phead, testKey.Public(), testsig, testsig, true},
|
||||||
}
|
}
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.alg, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
|
stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("couldn't decode test vector: %v", err)
|
t.Errorf("couldn't decode test vector: %v", err)
|
||||||
}
|
}
|
||||||
signer := &customTestSigner{
|
cs := customTestSigner{
|
||||||
sig: stdsig,
|
sig: stdsig,
|
||||||
pub: tc.pub,
|
pub: tc.pub,
|
||||||
}
|
}
|
||||||
|
var signer crypto.Signer = &cs
|
||||||
|
var ms *customTestMessageSigner
|
||||||
|
if tc.msgSigner {
|
||||||
|
ms = &customTestMessageSigner{customTestSigner: cs}
|
||||||
|
signer = ms
|
||||||
|
}
|
||||||
|
|
||||||
b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
|
b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -427,6 +453,14 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
|||||||
if j.Sig != tc.jwsig {
|
if j.Sig != tc.jwsig {
|
||||||
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
|
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
|
||||||
}
|
}
|
||||||
|
if ms != nil {
|
||||||
|
if want := tc.phead + "." + payload; string(ms.msg) != want {
|
||||||
|
t.Errorf("msg = %q; want %q", ms.msg, want)
|
||||||
|
}
|
||||||
|
if ms.opts != crypto.SHA256 {
|
||||||
|
t.Errorf("opts = %v; want %v", ms.opts, crypto.SHA256)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -383,7 +383,7 @@ func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
|
|||||||
order, err = client.WaitOrder(ctx, order.URI)
|
order, err = client.WaitOrder(ctx, order.URI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var orderErr *acme.OrderError
|
var orderErr *acme.OrderError
|
||||||
if errors.Is(err, orderErr) {
|
if errors.As(err, &orderErr) {
|
||||||
t.Fatalf("failed to wait for order %s: %s: %s", orderURL, err, orderErr.Problem)
|
t.Fatalf("failed to wait for order %s: %s: %s", orderURL, err, orderErr.Problem)
|
||||||
} else {
|
} else {
|
||||||
t.Fatalf("failed to wait for order %s: %s", orderURL, err)
|
t.Fatalf("failed to wait for order %s: %s", orderURL, err)
|
||||||
|
|||||||
+5
-4
@@ -114,14 +114,15 @@ type Error struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
var sb strings.Builder
|
||||||
|
fmt.Fprintf(&sb, "%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||||
if len(e.Subproblems) > 0 {
|
if len(e.Subproblems) > 0 {
|
||||||
str += fmt.Sprintf("; subproblems:")
|
sb.WriteString("; subproblems:")
|
||||||
for _, sp := range e.Subproblems {
|
for _, sp := range e.Subproblems {
|
||||||
str += fmt.Sprintf("\n\t%s", sp)
|
fmt.Fprintf(&sb, "\n\t%s", sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizationError indicates that an authorization for an identifier
|
// AuthorizationError indicates that an authorization for an identifier
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
module golang.org/x/crypto
|
module golang.org/x/crypto
|
||||||
|
|
||||||
go 1.25.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/net v0.56.0 // tagx:ignore
|
golang.org/x/net v0.57.0 // tagx:ignore
|
||||||
golang.org/x/sys v0.47.0
|
golang.org/x/sys v0.47.0
|
||||||
golang.org/x/term v0.45.0
|
golang.org/x/term v0.45.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.40.0 // indirect
|
require golang.org/x/text v0.41.0 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !s390x) || !gc || purego
|
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !riscv64 && !s390x) || !gc || purego
|
||||||
|
|
||||||
package poly1305
|
package poly1305
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le)
|
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le || riscv64)
|
||||||
|
|
||||||
package poly1305
|
package poly1305
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build gc && !purego
|
||||||
|
|
||||||
|
#define LOAD64U(base, offset, t0, t1, t2, t3, dst) \
|
||||||
|
MOVBU (offset+0*1)(base), t0; \
|
||||||
|
MOVBU (offset+1*1)(base), t1; \
|
||||||
|
MOVBU (offset+2*1)(base), t2; \
|
||||||
|
MOVBU (offset+3*1)(base), t3; \
|
||||||
|
SLL $8, t1; \
|
||||||
|
SLL $16, t2; \
|
||||||
|
SLL $24, t3; \
|
||||||
|
OR t1, t0; \
|
||||||
|
OR t3, t2; \
|
||||||
|
OR t2, t0, dst; \
|
||||||
|
MOVBU (offset+4*1)(base), t0; \
|
||||||
|
MOVBU (offset+5*1)(base), t1; \
|
||||||
|
MOVBU (offset+6*1)(base), t2; \
|
||||||
|
MOVBU (offset+7*1)(base), t3; \
|
||||||
|
SLL $32, t0; \
|
||||||
|
SLL $40, t1; \
|
||||||
|
SLL $48, t2; \
|
||||||
|
SLL $56, t3; \
|
||||||
|
OR t1, t0; \
|
||||||
|
OR t3, t2; \
|
||||||
|
OR t2, t0; \
|
||||||
|
OR t0, dst
|
||||||
|
|
||||||
|
// func update(state *macState, msg []byte)
|
||||||
|
TEXT ·update(SB), $0-32
|
||||||
|
MOV state+0(FP), X5
|
||||||
|
MOV msg_base+8(FP), X6
|
||||||
|
MOV msg_len+16(FP), X7
|
||||||
|
|
||||||
|
MOV $16, X8
|
||||||
|
|
||||||
|
AND $7, X6, X28
|
||||||
|
|
||||||
|
MOV (0*8)(X5), X9 // h0
|
||||||
|
MOV (1*8)(X5), X10 // h1
|
||||||
|
MOV (2*8)(X5), X11 // h2
|
||||||
|
MOV (3*8)(X5), X12 // r0
|
||||||
|
MOV (4*8)(X5), X13 // r1
|
||||||
|
|
||||||
|
BLT X7, X8, tail
|
||||||
|
|
||||||
|
loop:
|
||||||
|
BEQZ X28, aligned_load
|
||||||
|
|
||||||
|
LOAD64U(X6, 0*8, X16, X18, X19, X20, X15) // msg[0:8]
|
||||||
|
LOAD64U(X6, 1*8, X16, X18, X19, X20, X17) // msg[8:16]
|
||||||
|
JMP block
|
||||||
|
|
||||||
|
aligned_load:
|
||||||
|
MOV (0*8)(X6), X15 // msg[0:8]
|
||||||
|
MOV (1*8)(X6), X17 // msg[8:16]
|
||||||
|
|
||||||
|
block:
|
||||||
|
ADD X15, X9 // h0 (x1 + y1 = z1', if z1' < x1 then z1' overflow)
|
||||||
|
SLTU X15, X9, X19 // h0.carry
|
||||||
|
ADD X17, X10, X22
|
||||||
|
SLTU X17, X22, X23
|
||||||
|
ADD X22, X19, X10 // h1
|
||||||
|
SLTU X22, X10, X19
|
||||||
|
OR X23, X19 // h1.carry
|
||||||
|
ADD $1, X19
|
||||||
|
ADD X19, X11 // h2
|
||||||
|
|
||||||
|
ADD $16, X6 // msg = msg[16:]
|
||||||
|
|
||||||
|
multiply:
|
||||||
|
MULHU X9, X12, X16 // h0r0.hi
|
||||||
|
MUL X9, X12, X15 // h0r0.lo
|
||||||
|
MULHU X10, X12, X17 // h1r0.hi
|
||||||
|
MUL X10, X12, X14 // h1r0.lo
|
||||||
|
ADD X14, X16
|
||||||
|
SLTU X14, X16, X19
|
||||||
|
ADD X19, X17
|
||||||
|
MUL X11, X12, X20
|
||||||
|
ADD X17, X20
|
||||||
|
MULHU X9, X13, X17 // h0r1.hi
|
||||||
|
MUL X9, X13, X14 // h0r1.lo
|
||||||
|
ADD X14, X16
|
||||||
|
SLTU X14, X16, X19
|
||||||
|
ADD X19, X17
|
||||||
|
MOV X17, X9
|
||||||
|
MUL X11, X13, X21 // h2r1
|
||||||
|
MULHU X10, X13, X17 // h1r1.hi
|
||||||
|
MUL X10, X13, X14 // h1r1.lo
|
||||||
|
ADD X14, X20
|
||||||
|
ADD X17, X21, X22
|
||||||
|
SLTU X14, X20, X19
|
||||||
|
ADD X22, X19, X21
|
||||||
|
ADD X9, X20
|
||||||
|
SLTU X9, X20, X19
|
||||||
|
ADD X19, X21
|
||||||
|
AND $3, X20, X11
|
||||||
|
AND $-4, X20, X18
|
||||||
|
ADD X18, X15, X9
|
||||||
|
ADD X21, X16, X22
|
||||||
|
SLTU X18, X9, X19
|
||||||
|
SLTU X21, X22, X23
|
||||||
|
ADD X22, X19, X10
|
||||||
|
SLTU X22, X10, X19
|
||||||
|
OR X19, X23, X19
|
||||||
|
ADD X19, X11
|
||||||
|
SLL $62, X21, X22
|
||||||
|
SRL $2, X20, X23
|
||||||
|
SRL $2, X21, X21
|
||||||
|
OR X22, X23, X20
|
||||||
|
ADD X20, X9, X9
|
||||||
|
ADD X21, X10, X22
|
||||||
|
SLTU X20, X9, X19
|
||||||
|
SLTU X21, X22, X23
|
||||||
|
ADD X22, X19, X10
|
||||||
|
SLTU X22, X10, X19
|
||||||
|
OR X19, X23, X19
|
||||||
|
ADD X19, X11, X11
|
||||||
|
|
||||||
|
SUB $16, X7, X7
|
||||||
|
BGE X7, X8, loop
|
||||||
|
|
||||||
|
tail:
|
||||||
|
BEQ X7, X0, done
|
||||||
|
MOV $1, X15
|
||||||
|
MOV $0, X16
|
||||||
|
ADD X7, X6, X6
|
||||||
|
|
||||||
|
flush_buffer:
|
||||||
|
MOVBU -1(X6), X20
|
||||||
|
SRL $56, X15, X19
|
||||||
|
SLL $8, X16, X23
|
||||||
|
SLL $8, X15, X15
|
||||||
|
OR X19, X23, X16
|
||||||
|
XOR X20, X15
|
||||||
|
SUB $1, X7, X7
|
||||||
|
SUB $1, X6, X6
|
||||||
|
BNE X7, X0, flush_buffer
|
||||||
|
|
||||||
|
ADD X15, X9
|
||||||
|
SLTU X15, X9, X19
|
||||||
|
ADD X16, X10, X22
|
||||||
|
SLTU X16, X22, X23
|
||||||
|
ADD X22, X19, X10
|
||||||
|
SLTU X22, X10, X19
|
||||||
|
OR X23, X19
|
||||||
|
ADD X19, X11
|
||||||
|
|
||||||
|
MOV $16, X7
|
||||||
|
JMP multiply
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOV X9, (0*8)(X5) // h0
|
||||||
|
MOV X10, (1*8)(X5)
|
||||||
|
MOV X11, (2*8)(X5)
|
||||||
|
RET
|
||||||
+12
-5
@@ -85,7 +85,8 @@ type certID struct {
|
|||||||
|
|
||||||
// https://tools.ietf.org/html/rfc2560#section-4.1.1
|
// https://tools.ietf.org/html/rfc2560#section-4.1.1
|
||||||
type ocspRequest struct {
|
type ocspRequest struct {
|
||||||
TBSRequest tbsRequest
|
TBSRequest tbsRequest
|
||||||
|
OptionalSignature asn1.RawValue `asn1:"explicit,tag:0,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tbsRequest struct {
|
type tbsRequest struct {
|
||||||
@@ -321,10 +322,10 @@ type Request struct {
|
|||||||
func (req *Request) Marshal() ([]byte, error) {
|
func (req *Request) Marshal() ([]byte, error) {
|
||||||
hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
|
hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
|
||||||
if hashAlg == nil {
|
if hashAlg == nil {
|
||||||
return nil, errors.New("Unknown hash algorithm")
|
return nil, errors.New("unknown hash algorithm")
|
||||||
}
|
}
|
||||||
return asn1.Marshal(ocspRequest{
|
return asn1.Marshal(ocspRequest{
|
||||||
tbsRequest{
|
TBSRequest: tbsRequest{
|
||||||
Version: 0,
|
Version: 0,
|
||||||
RequestList: []request{
|
RequestList: []request{
|
||||||
{
|
{
|
||||||
@@ -418,8 +419,10 @@ func (p ParseError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseRequest parses an OCSP request in DER form. It only supports
|
// ParseRequest parses an OCSP request in DER form. It only supports
|
||||||
// requests for a single certificate. Signed requests are not supported.
|
// requests for a single certificate identifier. If a request includes
|
||||||
// If a request includes a signature, it will result in a ParseError.
|
// multiple certificate identifiers, only the first will be included in
|
||||||
|
// the parsed Request. Signed requests are not supported. If a request
|
||||||
|
// includes a signature, it will result in a ParseError.
|
||||||
func ParseRequest(bytes []byte) (*Request, error) {
|
func ParseRequest(bytes []byte) (*Request, error) {
|
||||||
var req ocspRequest
|
var req ocspRequest
|
||||||
rest, err := asn1.Unmarshal(bytes, &req)
|
rest, err := asn1.Unmarshal(bytes, &req)
|
||||||
@@ -430,6 +433,10 @@ func ParseRequest(bytes []byte) (*Request, error) {
|
|||||||
return nil, ParseError("trailing data in OCSP request")
|
return nil, ParseError("trailing data in OCSP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(req.OptionalSignature.FullBytes) > 0 {
|
||||||
|
return nil, ParseError("signed OCSP requests are not supported")
|
||||||
|
}
|
||||||
|
|
||||||
if len(req.TBSRequest.RequestList) == 0 {
|
if len(req.TBSRequest.RequestList) == 0 {
|
||||||
return nil, ParseError("OCSP request contains no request body")
|
return nil, ParseError("OCSP request contains no request body")
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-1
@@ -15,10 +15,15 @@ import (
|
|||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOCSPDecode(t *testing.T) {
|
func TestOCSPDecode(t *testing.T) {
|
||||||
@@ -190,7 +195,7 @@ func TestOCSPRequest(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(expectedBytes, marshaledRequest) != 0 {
|
if !bytes.Equal(expectedBytes, marshaledRequest) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"Marshaled request doesn't match expected: wanted %x, got %x",
|
"Marshaled request doesn't match expected: wanted %x, got %x",
|
||||||
expectedBytes,
|
expectedBytes,
|
||||||
@@ -199,6 +204,127 @@ func TestOCSPRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOCSPRequestErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "signed request",
|
||||||
|
input: testOCSPRequest(1, true),
|
||||||
|
wantErr: "signed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing data",
|
||||||
|
input: append(testOCSPRequest(1, false), 0x00),
|
||||||
|
wantErr: "trailing",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := ParseRequest(tc.input)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"ParseRequest succeeded, want ParseError containing %q",
|
||||||
|
tc.wantErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var parseError ParseError
|
||||||
|
if !errors.As(err, &parseError) {
|
||||||
|
t.Fatalf("ParseRequest returned %T, want ParseError", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(parseError.Error(), tc.wantErr) {
|
||||||
|
t.Fatalf(
|
||||||
|
"ParseRequest error = %q, want it to contain %q",
|
||||||
|
parseError.Error(), tc.wantErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOCSPMultipleCertRequest(t *testing.T) {
|
||||||
|
req, err := ParseRequest(testOCSPRequest(2, false))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseRequest errored for multiple request input: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testOCSPRequest uses the request cert counter as the issuerNameHash,
|
||||||
|
// issuerKeyHash and serial. We expect the first request cert to be used
|
||||||
|
// (counter=1), not the second (counter=2).
|
||||||
|
wantHash := []byte{0x01}
|
||||||
|
if actual := req.IssuerNameHash; !bytes.Equal(actual, wantHash) {
|
||||||
|
t.Errorf(
|
||||||
|
"ParseRequest.IssuerNameHash returned = %q, wanted %q",
|
||||||
|
actual, wantHash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if actual := req.IssuerKeyHash; !bytes.Equal(actual, wantHash) {
|
||||||
|
t.Errorf("ParseRequest.IssuerKeyHash returned = %q, wanted %q",
|
||||||
|
actual, wantHash)
|
||||||
|
}
|
||||||
|
wantSerial := big.NewInt(0x01)
|
||||||
|
if actual := req.SerialNumber; actual.Cmp(wantSerial) != 0 {
|
||||||
|
t.Errorf("ParseRequest.SerialNumber returned = %q, wanted %q",
|
||||||
|
actual, wantHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testOCSPRequest constructs an unsupported OCSP request in DER form for testing.
|
||||||
|
//
|
||||||
|
// We build these by hand using cryptobyte to specifically target shapes that are
|
||||||
|
// not supported by ParseRequest, and can't be created with CreateRequest.
|
||||||
|
//
|
||||||
|
// The resulting requestList will contain numCerts RequestList entries. If
|
||||||
|
// includeSignature is true, the request will carry a signature field (with an
|
||||||
|
// arbitrary signatureAlgorithm and signature).
|
||||||
|
//
|
||||||
|
// See RFC 6960, §4.1.1.
|
||||||
|
func testOCSPRequest(numCerts int, includeSignature bool) []byte {
|
||||||
|
sha1AlgID, err := asn1.Marshal(pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26},
|
||||||
|
Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // OCSPRequest
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // tbsRequest
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // requestList
|
||||||
|
for i := 0; i < numCerts; i++ {
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // Request
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // CertID
|
||||||
|
b.AddBytes(sha1AlgID) // hashAlgorithm
|
||||||
|
b.AddASN1OctetString([]byte{byte(i + 1)}) // issuerNameHash
|
||||||
|
b.AddASN1OctetString([]byte{byte(i + 1)}) // issuerKeyHash
|
||||||
|
b.AddASN1Int64(int64(i + 1)) // serialNumber
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if includeSignature {
|
||||||
|
// optionalSignature [0] EXPLICIT Signature
|
||||||
|
b.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
|
||||||
|
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // Signature
|
||||||
|
b.AddBytes(sha1AlgID) // signatureAlgorithm
|
||||||
|
b.AddASN1BitString([]byte{0x09, 0x0A, 0x0B, 0x0C}) // signature
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
der, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return der
|
||||||
|
}
|
||||||
|
|
||||||
func TestOCSPResponse(t *testing.T) {
|
func TestOCSPResponse(t *testing.T) {
|
||||||
leafCert, _ := hex.DecodeString(leafCertHex)
|
leafCert, _ := hex.DecodeString(leafCertHex)
|
||||||
leaf, err := x509.ParseCertificate(leafCert)
|
leaf, err := x509.ParseCertificate(leafCert)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func (r *keyring) Unlock(passphrase []byte) error {
|
|||||||
if !r.locked {
|
if !r.locked {
|
||||||
return errors.New("agent: not locked")
|
return errors.New("agent: not locked")
|
||||||
}
|
}
|
||||||
if 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
if subtle.ConstantTimeCompare(passphrase, r.passphrase) != 1 {
|
||||||
return fmt.Errorf("agent: incorrect passphrase")
|
return fmt.Errorf("agent: incorrect passphrase")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -246,10 +246,10 @@ func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
|||||||
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
|
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
|
||||||
// |p| and can consume excessive CPU on oversized keys.
|
// |p| and can consume excessive CPU on oversized keys.
|
||||||
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
|
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
|
||||||
if N.BitLen() > 8192 {
|
if N.BitLen() > 16384 {
|
||||||
return errors.New("agent: RSA modulus too large")
|
return errors.New("agent: RSA modulus too large")
|
||||||
}
|
}
|
||||||
if P.BitLen() > 4096 || Q.BitLen() > 4096 {
|
if P.BitLen() > 8192 || Q.BitLen() > 8192 {
|
||||||
return errors.New("agent: RSA prime too large")
|
return errors.New("agent: RSA prime too large")
|
||||||
}
|
}
|
||||||
if E.BitLen() > 24 {
|
if E.BitLen() > 24 {
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ func TestBufferReadwrite(t *testing.T) {
|
|||||||
b.write(alphabet[5:15])
|
b.write(alphabet[5:15])
|
||||||
r, _ = b.Read(make([]byte, 10))
|
r, _ = b.Read(make([]byte, 10))
|
||||||
r2, _ := b.Read(make([]byte, 10))
|
r2, _ := b.Read(make([]byte, 10))
|
||||||
if r != 10 || r2 != 5 || 15 != r+r2 {
|
if r != 10 || r2 != 5 || r+r2 != 15 {
|
||||||
t.Fatal("Expected written == read == 15")
|
t.Fatal("Expected written == read == 15")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-26
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -229,15 +230,20 @@ func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.Reserved = g.Reserved
|
c.Reserved = g.Reserved
|
||||||
|
// Reject a certificate whose signature key is itself a certificate before
|
||||||
|
// parsing it. Certificates signed by certificates are not supported (see
|
||||||
|
// PROTOCOL.certkeys), and rejecting after ParsePublicKey returns would allow
|
||||||
|
// a chain of nested certificates to recurse once per level, exhausting the
|
||||||
|
// goroutine stack.
|
||||||
|
if sigAlgo, _, ok := parseString(g.SignatureKey); !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
} else if _, ok := certKeyAlgoNames[string(sigAlgo)]; ok {
|
||||||
|
return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", sigAlgo)
|
||||||
|
}
|
||||||
k, err := ParsePublicKey(g.SignatureKey)
|
k, err := ParsePublicKey(g.SignatureKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// The Type() function is intended to return only certificate key types, but
|
|
||||||
// we use certKeyAlgoNames anyway for safety, to match [Certificate.Type].
|
|
||||||
if _, ok := certKeyAlgoNames[k.Type()]; ok {
|
|
||||||
return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", k.Type())
|
|
||||||
}
|
|
||||||
c.SignatureKey = k
|
c.SignatureKey = k
|
||||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||||
if !ok || len(rest) > 0 {
|
if !ok || len(rest) > 0 {
|
||||||
@@ -300,8 +306,11 @@ const sourceAddressCriticalOption = "source-address"
|
|||||||
// minimally, the IsAuthority callback should be set.
|
// minimally, the IsAuthority callback should be set.
|
||||||
type CertChecker struct {
|
type CertChecker struct {
|
||||||
// SupportedCriticalOptions lists the CriticalOptions that the
|
// SupportedCriticalOptions lists the CriticalOptions that the
|
||||||
// server application layer understands. These are only used
|
// application layer understands. A certificate carrying a critical
|
||||||
// for user certificates.
|
// option that is not listed here is rejected.
|
||||||
|
// CertChecker.Authenticate additionally accepts the source-address
|
||||||
|
// option, which the server enforces on the Permissions that
|
||||||
|
// Authenticate returns.
|
||||||
SupportedCriticalOptions []string
|
SupportedCriticalOptions []string
|
||||||
|
|
||||||
// IsUserAuthority should return true if the key is recognized as an
|
// IsUserAuthority should return true if the key is recognized as an
|
||||||
@@ -364,8 +373,9 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
|||||||
return c.CheckCert(hostname, cert)
|
return c.CheckCert(hostname, cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate checks a user certificate. Authenticate can be used as
|
// Authenticate checks a user certificate. Authenticate can be used as a value
|
||||||
// a value for ServerConfig.PublicKeyCallback.
|
// for ServerConfig.PublicKeyCallback. The source-address critical option is
|
||||||
|
// allowed, as it will be enforced by the server.
|
||||||
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
||||||
cert, ok := pubKey.(*Certificate)
|
cert, ok := pubKey.(*Certificate)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -384,8 +394,11 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||||
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||||
}
|
}
|
||||||
|
// The source-address critical option is enforced by serverAuthenticate,
|
||||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
// so it is supported regardless of SupportedCriticalOptions
|
||||||
|
cc := *c
|
||||||
|
cc.SupportedCriticalOptions = append(slices.Clip(cc.SupportedCriticalOptions), sourceAddressCriticalOption)
|
||||||
|
if err := cc.CheckCert(conn.User(), cert); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,27 +406,15 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
||||||
// the signature of the certificate.
|
// the signature of the certificate. Critical options that are not listed in
|
||||||
|
// SupportedCriticalOptions are rejected.
|
||||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||||
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
|
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
for opt := range cert.CriticalOptions {
|
for opt := range cert.CriticalOptions {
|
||||||
// sourceAddressCriticalOption will be enforced by
|
if !slices.Contains(c.SupportedCriticalOptions, opt) {
|
||||||
// serverAuthenticate
|
|
||||||
if opt == sourceAddressCriticalOption {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, supp := range c.SupportedCriticalOptions {
|
|
||||||
if supp == opt {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -45,6 +46,34 @@ func TestParseCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseCertNestedSignatureKey(t *testing.T) {
|
||||||
|
signer, err := NewSignerFromKey(testPrivateKeys["ed25519"])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := &Certificate{
|
||||||
|
Key: signer.PublicKey(),
|
||||||
|
CertType: UserCert,
|
||||||
|
ValidBefore: CertTimeInfinity,
|
||||||
|
}
|
||||||
|
if err := cert.SignCert(rand.Reader, signer); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner := *cert
|
||||||
|
cert.SignatureKey = &inner
|
||||||
|
blob := cert.Marshal()
|
||||||
|
|
||||||
|
_, err = ParsePublicKey(blob)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ParsePublicKey: expected error for certificate signed by a certificate, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "invalid for certificates") {
|
||||||
|
t.Errorf("ParsePublicKey: got error %q, want it to mention the signature key is invalid for certificates", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
|
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
|
||||||
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
|
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
|
||||||
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
|
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
|
||||||
@@ -356,6 +385,82 @@ func TestHostKeyCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testConnMeta struct {
|
||||||
|
ConnMetadata
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c testConnMeta) User() string { return c.user }
|
||||||
|
|
||||||
|
func TestCertCriticalOptions(t *testing.T) {
|
||||||
|
appended := make([]string, 2)
|
||||||
|
appended[0], appended[1] = "supported-option", "appended-option"
|
||||||
|
supported := appended[:1]
|
||||||
|
|
||||||
|
checker := &CertChecker{
|
||||||
|
SupportedCriticalOptions: supported,
|
||||||
|
IsHostAuthority: func(p PublicKey, addr string) bool {
|
||||||
|
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||||
|
},
|
||||||
|
IsUserAuthority: func(p PublicKey) bool {
|
||||||
|
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
opts map[string]string
|
||||||
|
// succeed is the expected outcome of CheckHostKey, for a host
|
||||||
|
// certificate, and of CheckCert, for a user certificate.
|
||||||
|
succeed bool
|
||||||
|
// authSucceed is the expected outcome of Authenticate.
|
||||||
|
authSucceed bool
|
||||||
|
}{
|
||||||
|
{name: "no critical options", opts: nil, succeed: true, authSucceed: true},
|
||||||
|
{name: "source-address", opts: map[string]string{sourceAddressCriticalOption: "192.168.1.0/24"}, authSucceed: true},
|
||||||
|
{name: "unknown option", opts: map[string]string{"unknown-option": ""}},
|
||||||
|
{name: "supported option", opts: map[string]string{"supported-option": ""}, succeed: true, authSucceed: true},
|
||||||
|
} {
|
||||||
|
hostCert := &Certificate{
|
||||||
|
ValidPrincipals: []string{"hostname"},
|
||||||
|
Key: testPublicKeys["rsa"],
|
||||||
|
ValidBefore: CertTimeInfinity,
|
||||||
|
CertType: HostCert,
|
||||||
|
Permissions: Permissions{CriticalOptions: test.opts},
|
||||||
|
}
|
||||||
|
if err := hostCert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
|
||||||
|
t.Fatalf("SignCert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checker.CheckHostKey("hostname:22", nil, hostCert); (err == nil) != test.succeed {
|
||||||
|
t.Errorf("CheckHostKey(%s): got %v, want success=%v", test.name, err, test.succeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
userCert := &Certificate{
|
||||||
|
ValidPrincipals: []string{"user"},
|
||||||
|
Key: testPublicKeys["rsa"],
|
||||||
|
ValidBefore: CertTimeInfinity,
|
||||||
|
CertType: UserCert,
|
||||||
|
Permissions: Permissions{CriticalOptions: test.opts},
|
||||||
|
}
|
||||||
|
if err := userCert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
|
||||||
|
t.Fatalf("SignCert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checker.CheckCert("user", userCert); (err == nil) != test.succeed {
|
||||||
|
t.Errorf("CheckCert(%s): got %v, want success=%v", test.name, err, test.succeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := checker.Authenticate(testConnMeta{user: "user"}, userCert); (err == nil) != test.authSucceed {
|
||||||
|
t.Errorf("Authenticate(%s): got %v, want success=%v", test.name, err, test.authSucceed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if appended[1] != "appended-option" {
|
||||||
|
t.Errorf("Authenticate overwrote the caller's slice: got %q, want %q", appended[1], "appended-option")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type legacyRSASigner struct {
|
type legacyRSASigner struct {
|
||||||
Signer
|
Signer
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-26
@@ -173,6 +173,12 @@ type channel struct {
|
|||||||
// (for outbound channels) or received (for inbound channels).
|
// (for outbound channels) or received (for inbound channels).
|
||||||
decided bool
|
decided bool
|
||||||
|
|
||||||
|
// established is set to true once the channel is open and may carry normal
|
||||||
|
// channel traffic: for an outbound channel when the peer's open
|
||||||
|
// confirmation is received, for an inbound channel when the local side
|
||||||
|
// accepts it. It is set and read from different goroutines.
|
||||||
|
established atomic.Bool
|
||||||
|
|
||||||
// direction contains either channelOutbound, for channels created
|
// direction contains either channelOutbound, for channels created
|
||||||
// locally, or channelInbound, for channels created by the peer.
|
// locally, or channelInbound, for channels created by the peer.
|
||||||
direction channelDirection
|
direction channelDirection
|
||||||
@@ -216,6 +222,10 @@ type channel struct {
|
|||||||
// packetPool has a buffer for each extended channel ID to
|
// packetPool has a buffer for each extended channel ID to
|
||||||
// save allocations during writes.
|
// save allocations during writes.
|
||||||
packetPool map[uint32][]byte
|
packetPool map[uint32][]byte
|
||||||
|
|
||||||
|
// closeOnce guards close so it is idempotent: closing the internal Go
|
||||||
|
// channels (msg, incomingRequests) more than once would panic.
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
// writePacket sends a packet. If the packet is a channel close, it updates
|
||||||
@@ -340,7 +350,18 @@ func (ch *channel) handleData(packet []byte) error {
|
|||||||
if extended == 1 {
|
if extended == 1 {
|
||||||
ch.extPending.write(data)
|
ch.extPending.write(data)
|
||||||
} else if extended > 0 {
|
} else if extended > 0 {
|
||||||
// discard other extended data.
|
// RFC 4254, Section 5.2 defines no extended data types other
|
||||||
|
// than stderr (type 1, handled above) and this package provides
|
||||||
|
// no API to read them, so the data is discarded. Credit its
|
||||||
|
// window back immediately: it can never be read, so the
|
||||||
|
// deduction above would otherwise shrink the window permanently.
|
||||||
|
// adjustWindow returns io.EOF if the local side has already
|
||||||
|
// sent a channel close; ignore it like ReadExtended does, since
|
||||||
|
// an error returned here would terminate the mux read loop and
|
||||||
|
// tear down the whole connection.
|
||||||
|
if err := ch.adjustWindow(length); err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ch.pending.write(data)
|
ch.pending.write(data)
|
||||||
}
|
}
|
||||||
@@ -393,17 +414,19 @@ func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *channel) close() {
|
func (c *channel) close() {
|
||||||
c.pending.eof()
|
c.closeOnce.Do(func() {
|
||||||
c.extPending.eof()
|
c.pending.eof()
|
||||||
close(c.msg)
|
c.extPending.eof()
|
||||||
close(c.incomingRequests)
|
close(c.msg)
|
||||||
c.writeMu.Lock()
|
close(c.incomingRequests)
|
||||||
// This is not necessary for a normal channel teardown, but if
|
c.writeMu.Lock()
|
||||||
// there was another error, it is.
|
// This is not necessary for a normal channel teardown, but if
|
||||||
c.sentClose = true
|
// there was another error, it is.
|
||||||
c.writeMu.Unlock()
|
c.sentClose = true
|
||||||
// Unblock writers.
|
c.writeMu.Unlock()
|
||||||
c.remoteWin.close()
|
// Unblock writers.
|
||||||
|
c.remoteWin.close()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseMessageReceived is called when a success or failure message is
|
// responseMessageReceived is called when a success or failure message is
|
||||||
@@ -417,10 +440,20 @@ func (ch *channel) responseMessageReceived() error {
|
|||||||
return errors.New("ssh: duplicate response received for channel")
|
return errors.New("ssh: duplicate response received for channel")
|
||||||
}
|
}
|
||||||
ch.decided = true
|
ch.decided = true
|
||||||
|
ch.established.Store(true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *channel) handlePacket(packet []byte) error {
|
func (ch *channel) handlePacket(packet []byte) error {
|
||||||
|
// Only the open response is expected before the channel is established.
|
||||||
|
if !ch.established.Load() {
|
||||||
|
switch packet[0] {
|
||||||
|
case msgChannelOpenConfirm, msgChannelOpenFailure:
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgChannelData, msgChannelExtendedData:
|
case msgChannelData, msgChannelExtendedData:
|
||||||
return ch.handleData(packet)
|
return ch.handleData(packet)
|
||||||
@@ -486,26 +519,28 @@ func (ch *channel) handlePacket(packet []byte) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ch.msg <- msg
|
// No other message type is expected on an established channel.
|
||||||
|
return fmt.Errorf("ssh: unexpected message type %d on channel %d", packet[0], ch.localId)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
||||||
ch := &channel{
|
ch := &channel{
|
||||||
remoteWin: window{Cond: newCond()},
|
remoteWin: window{Cond: newCond()},
|
||||||
myWindow: channelWindowSize,
|
myWindow: channelWindowSize,
|
||||||
pending: newBuffer(),
|
maxIncomingPayload: channelMaxPacket,
|
||||||
extPending: newBuffer(),
|
pending: newBuffer(),
|
||||||
direction: direction,
|
extPending: newBuffer(),
|
||||||
incomingRequests: make(chan *Request, chanSize),
|
direction: direction,
|
||||||
msg: make(chan interface{}, chanSize),
|
incomingRequests: make(chan *Request, chanSize),
|
||||||
chanType: chanType,
|
msg: make(chan interface{}, chanSize),
|
||||||
extraData: extraData,
|
chanType: chanType,
|
||||||
mux: m,
|
extraData: extraData,
|
||||||
packetPool: make(map[uint32][]byte),
|
mux: m,
|
||||||
|
packetPool: make(map[uint32][]byte),
|
||||||
}
|
}
|
||||||
ch.localId = m.chanList.add(ch)
|
m.chanList.add(ch)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,7 +564,6 @@ func (ch *channel) Accept() (Channel, <-chan *Request, error) {
|
|||||||
if ch.decided {
|
if ch.decided {
|
||||||
return nil, nil, errDecidedAlready
|
return nil, nil, errDecidedAlready
|
||||||
}
|
}
|
||||||
ch.maxIncomingPayload = channelMaxPacket
|
|
||||||
confirm := channelOpenConfirmMsg{
|
confirm := channelOpenConfirmMsg{
|
||||||
PeersID: ch.remoteId,
|
PeersID: ch.remoteId,
|
||||||
MyID: ch.localId,
|
MyID: ch.localId,
|
||||||
@@ -537,6 +571,7 @@ func (ch *channel) Accept() (Channel, <-chan *Request, error) {
|
|||||||
MaxPacketSize: ch.maxIncomingPayload,
|
MaxPacketSize: ch.maxIncomingPayload,
|
||||||
}
|
}
|
||||||
ch.decided = true
|
ch.decided = true
|
||||||
|
ch.established.Store(true)
|
||||||
if err := ch.sendMessage(confirm); err != nil {
|
if err := ch.sendMessage(confirm); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -138,3 +140,111 @@ func TestWriteExtendedNoInfiniteLoopOnLargeWrite(t *testing.T) {
|
|||||||
t.Fatal("WriteExtended did not return after closing the window")
|
t.Fatal("WriteExtended did not return after closing the window")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiscardedExtendedDataReturnsWindowCredit(t *testing.T) {
|
||||||
|
client, _, serverChans := forwardingPair(t)
|
||||||
|
|
||||||
|
clientCh, reqs, err := client.OpenChannel("test", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenChannel: %v", err)
|
||||||
|
}
|
||||||
|
go DiscardRequests(reqs)
|
||||||
|
defer clientCh.Close()
|
||||||
|
|
||||||
|
serverCh := (<-serverChans).(*channel)
|
||||||
|
defer serverCh.Close()
|
||||||
|
|
||||||
|
// Write more than the default channel receive window (2 MiB) of
|
||||||
|
// extended data with a type code other than stderr. The peer
|
||||||
|
// discards such data: if it did not return the window credit the
|
||||||
|
// write would block forever once the window is exhausted.
|
||||||
|
const payload = 4 * 1024 * 1024
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
remaining := payload
|
||||||
|
for remaining > 0 {
|
||||||
|
n := min(len(buf), remaining)
|
||||||
|
nw, err := serverCh.WriteExtended(buf[:n], 2)
|
||||||
|
if err != nil {
|
||||||
|
done <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining -= nw
|
||||||
|
}
|
||||||
|
done <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write of discarded extended data: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatal("write of discarded extended data blocked: window credit is not returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main stream must still be usable.
|
||||||
|
want := []byte("main stream after extended data")
|
||||||
|
if _, err := serverCh.Write(want); err != nil {
|
||||||
|
t.Fatalf("write to main stream: %v", err)
|
||||||
|
}
|
||||||
|
got := make([]byte, len(want))
|
||||||
|
if _, err := io.ReadFull(clientCh, got); err != nil {
|
||||||
|
t.Fatalf("read from main stream: %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != string(want) {
|
||||||
|
t.Fatalf("read %q from main stream, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading the main stream payload above orders the test after all
|
||||||
|
// preceding packets: everything received has now been either read or
|
||||||
|
// discarded, so the full window credit must have been accounted.
|
||||||
|
// Anything less indicates a partial-credit leak that the liveness
|
||||||
|
// check above would not detect.
|
||||||
|
cc := clientCh.(*channel)
|
||||||
|
cc.windowMu.Lock()
|
||||||
|
window := cc.myWindow + cc.myConsumed
|
||||||
|
cc.windowMu.Unlock()
|
||||||
|
if window != channelWindowSize {
|
||||||
|
t.Errorf("myWindow+myConsumed = %d, want %d", window, channelWindowSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscardedExtendedDataAfterClose(t *testing.T) {
|
||||||
|
client, _, serverChans := forwardingPair(t)
|
||||||
|
|
||||||
|
clientCh, reqs, err := client.OpenChannel("test", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenChannel: %v", err)
|
||||||
|
}
|
||||||
|
go DiscardRequests(reqs)
|
||||||
|
serverCh := <-serverChans
|
||||||
|
defer serverCh.Close()
|
||||||
|
|
||||||
|
ch := clientCh.(*channel)
|
||||||
|
// Close the channel locally: sentClose is now set, but the channel
|
||||||
|
// stays in the mux chanList until the peer's close arrives, so
|
||||||
|
// in-flight data packets are still dispatched to handleData.
|
||||||
|
if err := ch.Close(); err != nil {
|
||||||
|
t.Fatalf("Close: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliver extended data packets with an unknown type code, enough to
|
||||||
|
// cross the adjustWindow send threshold of 3*maxIncomingPayload.
|
||||||
|
// The window adjust message cannot be sent after the close and
|
||||||
|
// adjustWindow fails with io.EOF: handleData must swallow that
|
||||||
|
// error, since any error it returns terminates the mux read loop
|
||||||
|
// and tears down every channel on the connection.
|
||||||
|
const packetLen = 32 * 1024
|
||||||
|
packet := make([]byte, 13+packetLen)
|
||||||
|
packet[0] = msgChannelExtendedData
|
||||||
|
binary.BigEndian.PutUint32(packet[1:], ch.localId)
|
||||||
|
binary.BigEndian.PutUint32(packet[5:], 2)
|
||||||
|
binary.BigEndian.PutUint32(packet[9:], packetLen)
|
||||||
|
for range 5 {
|
||||||
|
if err := ch.handleData(packet); err != nil {
|
||||||
|
t.Fatalf("handleData after local close: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -798,7 +798,7 @@ func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn,
|
|||||||
return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+
|
return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+
|
||||||
"Major Status: %d\n"+
|
"Major Status: %d\n"+
|
||||||
"Minor Status: %d\n"+
|
"Minor Status: %d\n"+
|
||||||
"Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
|
"Error Message: %q\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
|
||||||
userAuthGSSAPIErrorResp.Message)
|
userAuthGSSAPIErrorResp.Message)
|
||||||
case msgUserAuthGSSAPIToken:
|
case msgUserAuthGSSAPIToken:
|
||||||
userAuthGSSAPITokenReq := &userAuthGSSAPIToken{}
|
userAuthGSSAPITokenReq := &userAuthGSSAPIToken{}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ package ssh
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -169,6 +171,40 @@ func TestClientAuthThirdKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invalidRSASigner offers an RSA public key the server cannot parse: its public
|
||||||
|
// exponent is even, which parseRSA rejects regardless of any key size limit.
|
||||||
|
// The key is intentionally invalid for a structural reason rather than for its
|
||||||
|
// size, so the test stays meaningful even if the accepted modulus size changes.
|
||||||
|
// Its Sign method is never reached, because the key is rejected during the
|
||||||
|
// initial public key query.
|
||||||
|
type invalidRSASigner struct{}
|
||||||
|
|
||||||
|
func (invalidRSASigner) PublicKey() PublicKey {
|
||||||
|
n := new(big.Int).Lsh(big.NewInt(1), 2048)
|
||||||
|
pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 2}) // incorrect exponent.
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (invalidRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||||
|
return nil, errors.New("ssh: invalid test key must not be used to sign")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientAuthInvalidPublicKey(t *testing.T) {
|
||||||
|
config := &ClientConfig{
|
||||||
|
User: "testuser",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(invalidRSASigner{}, testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
if err := tryAuth(t, config); err != nil {
|
||||||
|
t.Fatalf("client auth failed but should have fallen back to a valid key: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// partialSuccessPublicKeyAndKbdInteractiveServer returns a server config
|
// partialSuccessPublicKeyAndKbdInteractiveServer returns a server config
|
||||||
// where the rsa key gets partial success and the server offers both publickey
|
// where the rsa key gets partial success and the server offers both publickey
|
||||||
// and keyboard-interactive as next methods. The VerifiedPublicKeyCallback
|
// and keyboard-interactive as next methods. The VerifiedPublicKeyCallback
|
||||||
|
|||||||
+4
-4
@@ -419,7 +419,7 @@ type AlgorithmNegotiationError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlgorithmNegotiationError) Error() string {
|
func (a *AlgorithmNegotiationError) Error() string {
|
||||||
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v",
|
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %q, peer offered: %q",
|
||||||
a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
|
a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ func (c *Config) SetDefaults() {
|
|||||||
if c.Rand == nil {
|
if c.Rand == nil {
|
||||||
c.Rand = rand.Reader
|
c.Rand = rand.Reader
|
||||||
}
|
}
|
||||||
if c.Ciphers == nil {
|
if len(c.Ciphers) == 0 {
|
||||||
c.Ciphers = defaultCiphers
|
c.Ciphers = defaultCiphers
|
||||||
}
|
}
|
||||||
var ciphers []string
|
var ciphers []string
|
||||||
@@ -556,7 +556,7 @@ func (c *Config) SetDefaults() {
|
|||||||
}
|
}
|
||||||
c.Ciphers = ciphers
|
c.Ciphers = ciphers
|
||||||
|
|
||||||
if c.KeyExchanges == nil {
|
if len(c.KeyExchanges) == 0 {
|
||||||
c.KeyExchanges = defaultKexAlgos
|
c.KeyExchanges = defaultKexAlgos
|
||||||
}
|
}
|
||||||
var kexs []string
|
var kexs []string
|
||||||
@@ -571,7 +571,7 @@ func (c *Config) SetDefaults() {
|
|||||||
}
|
}
|
||||||
c.KeyExchanges = kexs
|
c.KeyExchanges = kexs
|
||||||
|
|
||||||
if c.MACs == nil {
|
if len(c.MACs) == 0 {
|
||||||
c.MACs = defaultMACs
|
c.MACs = defaultMACs
|
||||||
}
|
}
|
||||||
var macs []string
|
var macs []string
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ type OpenChannelError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *OpenChannelError) Error() string {
|
func (e *OpenChannelError) Error() string {
|
||||||
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
|
return fmt.Sprintf("ssh: rejected: %s (%q)", e.Reason, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnMetadata holds metadata for the connection.
|
// ConnMetadata holds metadata for the connection.
|
||||||
|
|||||||
+1
-1
@@ -162,7 +162,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
|
|||||||
t.remoteAddr = addr
|
t.remoteAddr = addr
|
||||||
t.hostKeyCallback = config.HostKeyCallback
|
t.hostKeyCallback = config.HostKeyCallback
|
||||||
t.bannerCallback = config.BannerCallback
|
t.bannerCallback = config.BannerCallback
|
||||||
if config.HostKeyAlgorithms != nil {
|
if len(config.HostKeyAlgorithms) > 0 {
|
||||||
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
||||||
} else {
|
} else {
|
||||||
t.hostKeyAlgorithms = defaultHostKeyAlgos
|
t.hostKeyAlgorithms = defaultHostKeyAlgos
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func TestHandshakeBasic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
want := []byte{msgRequestSuccess, byte(i)}
|
want := []byte{msgRequestSuccess, byte(i)}
|
||||||
if bytes.Compare(p, want) != 0 {
|
if !bytes.Equal(p, want) {
|
||||||
t.Errorf("message %d: got %v, want %v", i, p, want)
|
t.Errorf("message %d: got %v, want %v", i, p, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1336,7 +1336,7 @@ func TestAlgorithmNegotiationError(t *testing.T) {
|
|||||||
if !errors.As(err, &negotiationError) {
|
if !errors.As(err, &negotiationError) {
|
||||||
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
||||||
}
|
}
|
||||||
expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
|
expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %q, peer offered: %q",
|
||||||
clientConf.Ciphers, serverConf.Ciphers)
|
clientConf.Ciphers, serverConf.Ciphers)
|
||||||
if err.Error() != expectedErrorString {
|
if err.Error() != expectedErrorString {
|
||||||
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
||||||
@@ -1352,7 +1352,7 @@ func TestAlgorithmNegotiationError(t *testing.T) {
|
|||||||
if !errors.As(err, &negotiationError) {
|
if !errors.As(err, &negotiationError) {
|
||||||
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
||||||
}
|
}
|
||||||
expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
|
expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %q, peer offered: %q",
|
||||||
serverConf.Ciphers, clientConf.Ciphers)
|
serverConf.Ciphers, clientConf.Ciphers)
|
||||||
if err.Error() != expectedErrorString {
|
if err.Error() != expectedErrorString {
|
||||||
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
||||||
|
|||||||
+9
-8
@@ -485,10 +485,11 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8192 bits is also the maximum RSA key size accepted by crypto/tls for
|
// 16384 bits is the largest RSA key OpenSSH will generate (ssh-keygen
|
||||||
// signature verification:
|
// caps -b at 16384), so it is the practical upper bound for keys seen on
|
||||||
// https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
|
// the wire. Rejecting anything larger bounds the CPU spent verifying an
|
||||||
if w.N.BitLen() > 8192 {
|
// attacker-supplied key and signature, mitigating a denial of service.
|
||||||
|
if w.N.BitLen() > 16384 {
|
||||||
return nil, nil, errors.New("ssh: rsa modulus too large")
|
return nil, nil, errors.New("ssh: rsa modulus too large")
|
||||||
}
|
}
|
||||||
if w.E.BitLen() > 24 {
|
if w.E.BitLen() > 24 {
|
||||||
@@ -1669,13 +1670,13 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mirror the validation done in parseRSA for public keys: cap the
|
// Mirror the validation done in parseRSA for public keys: cap the
|
||||||
// modulus at the same limit enforced by crypto/tls, reject oversized
|
// modulus at the OpenSSH-generated maximum, reject oversized or
|
||||||
// or invalid exponents, and additionally bound the prime factors to
|
// invalid exponents, and additionally bound the prime factors to
|
||||||
// avoid the expensive CRT coefficient recomputation in pk.Precompute.
|
// avoid the expensive CRT coefficient recomputation in pk.Precompute.
|
||||||
if key.N.BitLen() > 8192 {
|
if key.N.BitLen() > 16384 {
|
||||||
return nil, errors.New("ssh: rsa modulus too large")
|
return nil, errors.New("ssh: rsa modulus too large")
|
||||||
}
|
}
|
||||||
if key.P.BitLen() > 4096 || key.Q.BitLen() > 4096 {
|
if key.P.BitLen() > 8192 || key.Q.BitLen() > 8192 {
|
||||||
return nil, errors.New("ssh: rsa prime too large")
|
return nil, errors.New("ssh: rsa prime too large")
|
||||||
}
|
}
|
||||||
if key.E.BitLen() > 24 {
|
if key.E.BitLen() > 24 {
|
||||||
|
|||||||
+22
-15
@@ -218,21 +218,32 @@ func TestParseRSAPrivateKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseRSAModulusTooLarge(t *testing.T) {
|
func TestParseRSAModulusLimit(t *testing.T) {
|
||||||
|
// A 16384-bit modulus is the largest OpenSSH will generate and must be
|
||||||
|
// accepted.
|
||||||
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=="
|
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 := ParseAuthorizedKey([]byte(rsa16384)); err != nil {
|
||||||
|
t.Fatalf("ParseAuthorizedKey rejected a 16384-bit modulus: %v", err)
|
||||||
if err == nil {
|
|
||||||
t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A modulus larger than 16384 bits must be rejected to bound the cost of
|
||||||
|
// verifying an attacker-supplied key.
|
||||||
|
n := new(big.Int).Lsh(big.NewInt(1), 16384) // 16385 bits
|
||||||
|
pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 65537})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPublicKey: %v", err)
|
||||||
|
}
|
||||||
|
_, err = ParsePublicKey(pub.Marshal())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ParsePublicKey accepted a modulus larger than 16384 bits; expected it to be rejected")
|
||||||
|
}
|
||||||
expectedError := "rsa modulus too large"
|
expectedError := "rsa modulus too large"
|
||||||
if !strings.Contains(err.Error(), expectedError) {
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePrivateKeyRSAModulusTooLarge(t *testing.T) {
|
func TestParsePrivateKeyRSAModulus16384(t *testing.T) {
|
||||||
rsa16384 := []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
rsa16384 := []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAIFwAAAAdzc2gtcn
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAIFwAAAAdzc2gtcn
|
||||||
NhAAAAAwEAAQAACAEAy58lBbrFRB4r18MqVA625r4nzxjTkMlN5LxoG7ARC9L7px5lPs4o
|
NhAAAAAwEAAQAACAEAy58lBbrFRB4r18MqVA625r4nzxjTkMlN5LxoG7ARC9L7px5lPs4o
|
||||||
@@ -415,14 +426,10 @@ OthateBQGfnsGpTLxkpydh4jlYzTCp9bVRK8YXwsocglOcaRQH5ghfmgsAAAALbmljb2xh
|
|||||||
QHAxNnM=
|
QHAxNnM=
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
`)
|
`)
|
||||||
_, err := ParseRawPrivateKey(rsa16384)
|
// A 16384-bit modulus is the largest OpenSSH will generate and must be
|
||||||
if err == nil {
|
// accepted.
|
||||||
t.Fatal("ParseRawPrivateKey accepted a 16384-bit modulus; expected it to be rejected")
|
if _, err := ParseRawPrivateKey(rsa16384); err != nil {
|
||||||
}
|
t.Fatalf("ParseRawPrivateKey rejected a 16384-bit modulus: %v", err)
|
||||||
|
|
||||||
expectedError := "rsa modulus too large"
|
|
||||||
if !strings.Contains(err.Error(), expectedError) {
|
|
||||||
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,7 +1014,7 @@ func TestKnownHostsParsing(t *testing.T) {
|
|||||||
input := test.input
|
input := test.input
|
||||||
if strings.Contains(input, rsaKeyToken) {
|
if strings.Contains(input, rsaKeyToken) {
|
||||||
expectedKey = rsaPub
|
expectedKey = rsaPub
|
||||||
input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
|
input = strings.ReplaceAll(test.input, rsaKeyToken, rsaPubSerialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
|
marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
|
||||||
|
|||||||
@@ -142,6 +142,15 @@ func keyEq(a, b ssh.PublicKey) bool {
|
|||||||
return bytes.Equal(a.Marshal(), b.Marshal())
|
return bytes.Equal(a.Marshal(), b.Marshal())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// plainKeyBlob returns the serialized public portion of key: for a
|
||||||
|
// certificate this is the certified key, otherwise the key itself.
|
||||||
|
func plainKeyBlob(key ssh.PublicKey) string {
|
||||||
|
if cert, ok := key.(*ssh.Certificate); ok {
|
||||||
|
return string(cert.Key.Marshal())
|
||||||
|
}
|
||||||
|
return string(key.Marshal())
|
||||||
|
}
|
||||||
|
|
||||||
// IsHostAuthority can be used as a callback in ssh.CertChecker
|
// IsHostAuthority can be used as a callback in ssh.CertChecker
|
||||||
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
||||||
h, p, err := net.SplitHostPort(address)
|
h, p, err := net.SplitHostPort(address)
|
||||||
@@ -160,10 +169,10 @@ func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool
|
|||||||
|
|
||||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||||
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||||
if _, ok := db.revoked[string(key.Marshal())]; ok {
|
if _, ok := db.revoked[plainKeyBlob(key)]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
|
if _, ok := db.revoked[plainKeyBlob(key.SignatureKey)]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -228,7 +237,7 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if marker == markerRevoked {
|
if marker == markerRevoked {
|
||||||
db.revoked[string(key.Marshal())] = &KnownKey{
|
db.revoked[plainKeyBlob(key)] = &KnownKey{
|
||||||
Key: key,
|
Key: key,
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Line: linenum,
|
Line: linenum,
|
||||||
@@ -341,7 +350,7 @@ func (r *RevokedError) Error() string {
|
|||||||
// check checks a key against the host database. This should not be
|
// check checks a key against the host database. This should not be
|
||||||
// used for verifying certificates.
|
// used for verifying certificates.
|
||||||
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
|
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
|
||||||
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
|
if revoked := db.revoked[plainKeyBlob(remoteKey)]; revoked != nil {
|
||||||
return &RevokedError{Revoked: *revoked}
|
return &RevokedError{Revoked: *revoked}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,6 +316,68 @@ func TestRevokedCA(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRevokedCertifiedKey(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, hostPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hostKey, err := ssh.NewPublicKey(hostPriv.Public())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signCert := func(serial uint64, principal string) *ssh.Certificate {
|
||||||
|
cert := &ssh.Certificate{
|
||||||
|
CertType: ssh.HostCert,
|
||||||
|
Key: hostKey,
|
||||||
|
Serial: serial,
|
||||||
|
ValidBefore: ssh.CertTimeInfinity,
|
||||||
|
ValidPrincipals: []string{principal},
|
||||||
|
}
|
||||||
|
if err := cert.SignCert(rand.Reader, caSigner); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
cert := signCert(1, "server.org")
|
||||||
|
|
||||||
|
// Revoking the host key revokes any certificate for it, even when
|
||||||
|
// the CA is trusted, matching OpenSSH's check_key_not_revoked.
|
||||||
|
db := testDB(t, "@revoked server.org "+serialize(hostKey)+"\n"+
|
||||||
|
"@cert-authority server.org "+serialize(caSigner.PublicKey())+"\n")
|
||||||
|
if !db.IsRevoked(cert) {
|
||||||
|
t.Error("IsRevoked returned false for certificate with revoked certified key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversely, a @revoked line containing a certificate revokes the
|
||||||
|
// plain key it certifies.
|
||||||
|
db = testDB(t, "@revoked server.org "+serialize(cert)+"\n")
|
||||||
|
if err := db.check("server.org:22", testAddr, hostKey); err == nil {
|
||||||
|
t.Error("no error for plain key revoked via certificate")
|
||||||
|
} else if _, ok := err.(*RevokedError); !ok {
|
||||||
|
t.Errorf("got %T (%v), want *RevokedError", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since only public portions are compared, revoking a certificate
|
||||||
|
// also revokes any sibling certificate certifying the same key,
|
||||||
|
// whatever its serial number and principals.
|
||||||
|
sibling := signCert(2, "other.org")
|
||||||
|
db = testDB(t, "@revoked * "+serialize(cert)+"\n"+
|
||||||
|
"@cert-authority * "+serialize(caSigner.PublicKey())+"\n")
|
||||||
|
if !db.IsRevoked(sibling) {
|
||||||
|
t.Error("IsRevoked returned false for sibling certificate of a revoked certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testHostname = "hostname"
|
const testHostname = "hostname"
|
||||||
|
|
||||||
// generated with keygen -H -f
|
// generated with keygen -H -f
|
||||||
|
|||||||
+1
-10
@@ -44,16 +44,7 @@ type disconnectMsg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *disconnectMsg) Error() string {
|
func (d *disconnectMsg) Error() string {
|
||||||
return fmt.Sprintf("ssh: disconnect, reason %d: %q", d.Reason, sanitizeString(d.Message))
|
return fmt.Sprintf("ssh: disconnect, reason %d: %q", d.Reason, d.Message)
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeString(s string) string {
|
|
||||||
return strings.Map(func(r rune) rune {
|
|
||||||
if r == '\t' || (r >= ' ' && r < 0x7f) {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}, s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4253, section 7.1.
|
// See RFC 4253, section 7.1.
|
||||||
|
|||||||
@@ -269,10 +269,10 @@ func TestDisconnectMsgSanitizesMessage(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"clean message", `"clean message"`},
|
{"clean message", `"clean message"`},
|
||||||
{"line1\nline2\nline3", `"line1line2line3"`},
|
{"line1\nline2\nline3", `"line1\nline2\nline3"`},
|
||||||
{"has\x00null\x00bytes", `"hasnullbytes"`},
|
{"has\x00null\x00bytes", `"has\x00null\x00bytes"`},
|
||||||
{"\x1b[31mred\x1b[0m", `"[31mred[0m"`},
|
{"\x1b[31mred\x1b[0m", `"\x1b[31mred\x1b[0m"`},
|
||||||
{"newline\r\nCRLF", `"newlineCRLF"`},
|
{"newline\r\nCRLF", `"newline\r\nCRLF"`},
|
||||||
{"tab\there", `"tab\there"`},
|
{"tab\there", `"tab\there"`},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.in, func(t *testing.T) {
|
t.Run(tc.in, func(t *testing.T) {
|
||||||
|
|||||||
+7
-6
@@ -32,18 +32,21 @@ type chanList struct {
|
|||||||
offset uint32
|
offset uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assigns a channel ID to the given channel.
|
// add stores the given channel and assigns its localId while holding the
|
||||||
func (c *chanList) add(ch *channel) uint32 {
|
// lock, so that getChan can never return a channel whose localId is not yet
|
||||||
|
// initialized.
|
||||||
|
func (c *chanList) add(ch *channel) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
for i := range c.chans {
|
for i := range c.chans {
|
||||||
if c.chans[i] == nil {
|
if c.chans[i] == nil {
|
||||||
c.chans[i] = ch
|
c.chans[i] = ch
|
||||||
return uint32(i) + c.offset
|
ch.localId = uint32(i) + c.offset
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.chans = append(c.chans, ch)
|
c.chans = append(c.chans, ch)
|
||||||
return uint32(len(c.chans)-1) + c.offset
|
ch.localId = uint32(len(c.chans)-1) + c.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
// getChan returns the channel for the given ID.
|
// getChan returns the channel for the given ID.
|
||||||
@@ -343,8 +346,6 @@ func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Reque
|
|||||||
func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
||||||
ch := m.newChannel(chanType, channelOutbound, extra)
|
ch := m.newChannel(chanType, channelOutbound, extra)
|
||||||
|
|
||||||
ch.maxIncomingPayload = channelMaxPacket
|
|
||||||
|
|
||||||
open := channelOpenMsg{
|
open := channelOpenMsg{
|
||||||
ChanType: chanType,
|
ChanType: chanType,
|
||||||
PeersWindow: ch.myWindow,
|
PeersWindow: ch.myWindow,
|
||||||
|
|||||||
+258
-2
@@ -5,9 +5,11 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -299,7 +301,7 @@ func TestMuxReject(t *testing.T) {
|
|||||||
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
|
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
|
||||||
}
|
}
|
||||||
|
|
||||||
want := "ssh: rejected: unknown reason 42 (message)"
|
want := "ssh: rejected: unknown reason 42 (\"message\")"
|
||||||
if err.Error() != want {
|
if err.Error() != want {
|
||||||
t.Errorf("got %q, want %q", err.Error(), want)
|
t.Errorf("got %q, want %q", err.Error(), want)
|
||||||
}
|
}
|
||||||
@@ -1181,6 +1183,13 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Follow the flood with a request so the client can synchronize with
|
||||||
|
// its mux loop. Once the client receives this request, the mux loop has
|
||||||
|
// necessarily processed (and discarded) all the preceding responses.
|
||||||
|
if _, err := serverCh.SendRequest("sync", false, nil); err != nil {
|
||||||
|
done <- fmt.Errorf("send sync request: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Echo any legitimate request back.
|
// Echo any legitimate request back.
|
||||||
for req := range serverCh.incomingRequests {
|
for req := range serverCh.incomingRequests {
|
||||||
if req.WantReply {
|
if req.WantReply {
|
||||||
@@ -1193,9 +1202,17 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
|
|||||||
done <- nil
|
done <- nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
req, ok := <-clientCh.incomingRequests
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("channel closed before sync request")
|
||||||
|
}
|
||||||
|
if req.Type != "sync" {
|
||||||
|
t.Fatalf("unexpected sync request type %q", req.Type)
|
||||||
|
}
|
||||||
|
|
||||||
// If the flood had wedged the mux loop, this SendRequest would never
|
// If the flood had wedged the mux loop, this SendRequest would never
|
||||||
// receive a reply.
|
// receive a reply.
|
||||||
ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
|
ok, err = clientCh.SendRequest("ping", true, []byte("hello"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SendRequest: %v", err)
|
t.Fatalf("SendRequest: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1213,6 +1230,245 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChannelUndecidedOutboundDropsTraffic(t *testing.T) {
|
||||||
|
// Traffic on an outbound channel the peer has not confirmed yet is dropped;
|
||||||
|
// the open confirmation is still processed.
|
||||||
|
a, b := memPipe()
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
// Drain the peer end so that a write would not block.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if _, err := b.readPacket(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := newMux(a)
|
||||||
|
ch := m.newChannel("session", channelOutbound, nil)
|
||||||
|
if ch.decided {
|
||||||
|
t.Fatal("freshly opened outbound channel should not be decided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flooding channel requests must not block.
|
||||||
|
reqPacket := Marshal(channelRequestMsg{PeersID: ch.localId, Request: "x"})
|
||||||
|
for range chanSize * 4 {
|
||||||
|
if err := ch.handlePacket(reqPacket); err != nil {
|
||||||
|
t.Fatalf("handlePacket(channelRequest): %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n := len(ch.incomingRequests); n != 0 {
|
||||||
|
t.Fatalf("incomingRequests should be empty, has %d entries", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A close before the confirmation must not remove or close the channel, and
|
||||||
|
// must not emit a close for the still-unknown remote id.
|
||||||
|
if err := ch.handlePacket(Marshal(channelCloseMsg{PeersID: ch.localId})); err != nil {
|
||||||
|
t.Fatalf("handlePacket(channelClose): %v", err)
|
||||||
|
}
|
||||||
|
if m.chanList.getChan(ch.localId) != ch {
|
||||||
|
t.Fatal("premature close removed the channel from the chanList")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The open confirmation is still processed and decides the channel.
|
||||||
|
confirm := Marshal(channelOpenConfirmMsg{
|
||||||
|
PeersID: ch.localId,
|
||||||
|
MyID: 42,
|
||||||
|
MyWindow: 1 << 20,
|
||||||
|
MaxPacketSize: 1 << 15,
|
||||||
|
})
|
||||||
|
if err := ch.handlePacket(confirm); err != nil {
|
||||||
|
t.Fatalf("handlePacket(openConfirm): %v", err)
|
||||||
|
}
|
||||||
|
if !ch.decided {
|
||||||
|
t.Fatal("channel not decided after open confirmation")
|
||||||
|
}
|
||||||
|
if _, ok := (<-ch.msg).(*channelOpenConfirmMsg); !ok {
|
||||||
|
t.Fatal("open confirmation was not delivered on ch.msg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelUndecidedInboundDropsRequests(t *testing.T) {
|
||||||
|
// Channel requests on an inbound channel the local application has not
|
||||||
|
// accepted or rejected yet are dropped.
|
||||||
|
serverMux, clientMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
go DiscardRequests(serverMux.incomingRequests)
|
||||||
|
|
||||||
|
// The server assigns its first inbound channel id 0; open such a channel
|
||||||
|
// and flood it before it is decided. The channel is left undecided, as it
|
||||||
|
// would be in the window before the server accepts or rejects it: its
|
||||||
|
// NewChannel is never received from incomingChannels.
|
||||||
|
ch := clientMux.newChannel("chan", channelOutbound, nil)
|
||||||
|
if err := clientMux.sendMessage(channelOpenMsg{
|
||||||
|
ChanType: "chan",
|
||||||
|
PeersWindow: ch.myWindow,
|
||||||
|
MaxPacketSize: ch.maxIncomingPayload,
|
||||||
|
PeersID: ch.localId,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("send open: %v", err)
|
||||||
|
}
|
||||||
|
for range chanSize * 4 {
|
||||||
|
if err := clientMux.sendMessage(channelRequestMsg{PeersID: 0, Request: "x"}); err != nil {
|
||||||
|
break // the server may have closed the connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server loop keeps reading: closing the client makes it observe EOF
|
||||||
|
// and return from Wait.
|
||||||
|
clientMux.Close()
|
||||||
|
serverMux.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelRejectedInboundDropsTraffic(t *testing.T) {
|
||||||
|
// A rejected channel is decided but is never established, and traffic on it
|
||||||
|
// is dropped.
|
||||||
|
a, b := memPipe()
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if _, err := b.readPacket(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := newMux(a)
|
||||||
|
ch := m.newChannel("session", channelInbound, nil)
|
||||||
|
ch.remoteId = 42
|
||||||
|
|
||||||
|
if err := ch.Reject(ConnectionFailed, "no thanks"); err != nil {
|
||||||
|
t.Fatalf("Reject: %v", err)
|
||||||
|
}
|
||||||
|
if !ch.decided {
|
||||||
|
t.Fatal("a rejected channel should be decided")
|
||||||
|
}
|
||||||
|
if ch.established.Load() {
|
||||||
|
t.Fatal("a rejected channel should not be established")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flooding channel requests must not block.
|
||||||
|
reqPacket := Marshal(channelRequestMsg{PeersID: ch.localId, Request: "x"})
|
||||||
|
for range chanSize * 4 {
|
||||||
|
if err := ch.handlePacket(reqPacket); err != nil {
|
||||||
|
t.Fatalf("handlePacket(channelRequest): %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n := len(ch.incomingRequests); n != 0 {
|
||||||
|
t.Fatalf("incomingRequests should be empty, has %d entries", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelBogusMessageTearsDownMux(t *testing.T) {
|
||||||
|
// A message that is well-formed on the wire but is not a type expected on
|
||||||
|
// an established channel is a protocol error that tears the mux down.
|
||||||
|
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)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := clientMux.openChannel("chan", nil); err != nil {
|
||||||
|
t.Fatalf("openChannel: %v", err)
|
||||||
|
}
|
||||||
|
serverCh := <-serverRes
|
||||||
|
if serverCh == nil {
|
||||||
|
t.Fatal("server did not accept channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Craft a packet that the client mux routes to clientCh (bytes 1:5 hold the
|
||||||
|
// peer's channel id) and that decode accepts, but whose type is unexpected
|
||||||
|
// on a channel. The service-string length equals the channel id, so the
|
||||||
|
// same four bytes serve both as the routing id and as the string length.
|
||||||
|
id := serverCh.remoteId
|
||||||
|
packet := []byte{msgServiceRequest, 0, 0, 0, 0}
|
||||||
|
binary.BigEndian.PutUint32(packet[1:], id)
|
||||||
|
packet = append(packet, make([]byte, id)...)
|
||||||
|
if err := serverMux.conn.writePacket(packet); err != nil {
|
||||||
|
t.Fatalf("writePacket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mux loop exits with the protocol error.
|
||||||
|
if err := clientMux.Wait(); err == nil || !strings.Contains(err.Error(), "unexpected message type") {
|
||||||
|
t.Fatalf("mux error = %v, want unexpected message type", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelCloseIdempotent(t *testing.T) {
|
||||||
|
a, b := memPipe()
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
m := newMux(a)
|
||||||
|
ch := m.newChannel("session", channelOutbound, nil)
|
||||||
|
|
||||||
|
ch.close()
|
||||||
|
ch.close() // must be a no-op, not a panic
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewChannelFullyInitializedWhenPublished(t *testing.T) {
|
||||||
|
a, b := memPipe()
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
m := newMux(a)
|
||||||
|
|
||||||
|
const channels = 100
|
||||||
|
stop := make(chan struct{})
|
||||||
|
readerDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
// Mimic the mux loop: fetch channels by id and read the fields it
|
||||||
|
// uses to route and validate packets.
|
||||||
|
defer close(readerDone)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
for id := uint32(0); id < channels; id++ {
|
||||||
|
ch := m.chanList.getChan(id)
|
||||||
|
if ch == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := ch.localId; got != id {
|
||||||
|
t.Errorf("channel registered under id %d has localId %d", id, got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got := ch.maxIncomingPayload; got != channelMaxPacket {
|
||||||
|
t.Errorf("channel %d: maxIncomingPayload = %d, want %d", id, got, channelMaxPacket)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for range channels {
|
||||||
|
ch := m.newChannel("session", channelOutbound, nil)
|
||||||
|
if m.chanList.getChan(ch.localId) != ch {
|
||||||
|
t.Fatalf("channel %d not registered under its localId", ch.localId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(stop)
|
||||||
|
<-readerDone
|
||||||
|
}
|
||||||
|
|
||||||
func TestChannelConcurrentRequests(t *testing.T) {
|
func TestChannelConcurrentRequests(t *testing.T) {
|
||||||
writer, reader, mux := channelPair(t)
|
writer, reader, mux := channelPair(t)
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|||||||
+56
-23
@@ -26,10 +26,16 @@ type Permissions struct {
|
|||||||
// defines "force-command" (only allow the given command to
|
// defines "force-command" (only allow the given command to
|
||||||
// execute) and "source-address" (only allow connections from
|
// execute) and "source-address" (only allow connections from
|
||||||
// the given address). The SSH package currently only enforces
|
// the given address). The SSH package currently only enforces
|
||||||
// the "source-address" critical option. It is up to server
|
// the "source-address" critical option: it is validated against
|
||||||
// implementations to enforce other critical options, such as
|
// the client's remote address whenever it is present in the
|
||||||
// "force-command", by checking them after the SSH handshake
|
// Permissions returned by any authentication callback. Its value
|
||||||
// is successful. In general, SSH servers should reject
|
// is a comma-separated list of IP addresses and CIDR blocks;
|
||||||
|
// consistently with OpenSSH, a connection whose remote address is
|
||||||
|
// not an IP address, such as a Unix domain socket, never matches
|
||||||
|
// the list and is rejected when the option is present. It is up
|
||||||
|
// to server implementations to enforce other critical options,
|
||||||
|
// such as "force-command", by checking them after the SSH
|
||||||
|
// handshake is successful. In general, SSH servers should reject
|
||||||
// connections that specify critical options that are unknown
|
// connections that specify critical options that are unknown
|
||||||
// or not supported.
|
// or not supported.
|
||||||
CriticalOptions map[string]string
|
CriticalOptions map[string]string
|
||||||
@@ -223,7 +229,9 @@ type ServerConfig struct {
|
|||||||
// Permissions object can be the same object, optionally modified, or a
|
// Permissions object can be the same object, optionally modified, or a
|
||||||
// completely new object. If VerifiedPublicKeyCallback is non-nil,
|
// completely new object. If VerifiedPublicKeyCallback is non-nil,
|
||||||
// PublicKeyCallback is not allowed to return a PartialSuccessError, which
|
// PublicKeyCallback is not allowed to return a PartialSuccessError, which
|
||||||
// can instead be returned by VerifiedPublicKeyCallback.
|
// can instead be returned by VerifiedPublicKeyCallback. The
|
||||||
|
// signatureAlgorithm argument is the format of the signature that was
|
||||||
|
// successfully verified.
|
||||||
//
|
//
|
||||||
// VerifiedPublicKeyCallback does not affect which authentication methods
|
// VerifiedPublicKeyCallback does not affect which authentication methods
|
||||||
// are included in the list of methods that can be attempted by the client.
|
// are included in the list of methods that can be attempted by the client.
|
||||||
@@ -442,6 +450,10 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
|||||||
return perms, err
|
return perms, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkSourceAddress matches addr against sourceAddrs, a comma-separated list
|
||||||
|
// of IP addresses and CIDR blocks. Consistently with OpenSSH, a remote address
|
||||||
|
// that is not IP-based, such as a Unix domain socket, never matches the list
|
||||||
|
// and is rejected.
|
||||||
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return errors.New("ssh: no address known for client, but source-address match required")
|
return errors.New("ssh: no address known for client, but source-address match required")
|
||||||
@@ -449,7 +461,7 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
|||||||
|
|
||||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
|
return fmt.Errorf("ssh: remote address %v is not a TCP address when checking source-address match", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
|
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
|
||||||
@@ -472,6 +484,21 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
|||||||
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkSourceAddressCriticalOption enforces the source-address critical
|
||||||
|
// option, if present in perms, as documented in Permissions.CriticalOptions.
|
||||||
|
// A present but empty value matches no address, so it denies authentication,
|
||||||
|
// consistently with OpenSSH, rather than being treated as absent.
|
||||||
|
func checkSourceAddressCriticalOption(addr net.Addr, perms *Permissions) error {
|
||||||
|
if perms == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
saco, ok := perms.CriticalOptions[sourceAddressCriticalOption]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return checkSourceAddress(addr, saco)
|
||||||
|
}
|
||||||
|
|
||||||
func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, token []byte, s *connection,
|
func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, token []byte, s *connection,
|
||||||
sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) {
|
sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) {
|
||||||
gssAPIServer := gssapiConfig.Server
|
gssAPIServer := gssapiConfig.Server
|
||||||
@@ -685,7 +712,7 @@ userAuthLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userAuthReq.Service != serviceSSH {
|
if userAuthReq.Service != serviceSSH {
|
||||||
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
return nil, fmt.Errorf("ssh: client attempted to negotiate for unknown service: %q", userAuthReq.Service)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.user != userAuthReq.User && partialSuccessReturned {
|
if s.user != userAuthReq.User && partialSuccessReturned {
|
||||||
@@ -771,7 +798,8 @@ userAuthLoop:
|
|||||||
|
|
||||||
pubKey, err := ParsePublicKey(pubKeyData)
|
pubKey, err := ParsePublicKey(pubKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
authErr = err
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
candidate, ok := cache.get(s.user, pubKeyData)
|
candidate, ok := cache.get(s.user, pubKeyData)
|
||||||
@@ -784,13 +812,14 @@ userAuthLoop:
|
|||||||
return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined")
|
return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidate.result == nil || isPartialSuccessError) &&
|
// This check is authoritative for the Permissions returned by
|
||||||
candidate.perms != nil &&
|
// PublicKeyCallback: the check at the end of the auth loop sees
|
||||||
candidate.perms.CriticalOptions != nil &&
|
// the final Permissions, which VerifiedPublicKeyCallback may
|
||||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
// have replaced, and is skipped on partial success. It also
|
||||||
if err := checkSourceAddress(
|
// makes public key queries fail before the client signs when
|
||||||
s.RemoteAddr(),
|
// PublicKeyCallback supplies the restriction.
|
||||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption]); err != nil {
|
if candidate.result == nil || isPartialSuccessError {
|
||||||
|
if err := checkSourceAddressCriticalOption(s.RemoteAddr(), candidate.perms); err != nil {
|
||||||
candidate.result = err
|
candidate.result = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,14 +893,7 @@ userAuthLoop:
|
|||||||
// Only call VerifiedPublicKeyCallback after the key has been accepted
|
// Only call VerifiedPublicKeyCallback after the key has been accepted
|
||||||
// and successfully verified. If authErr is non-nil, the key is not
|
// and successfully verified. If authErr is non-nil, the key is not
|
||||||
// considered verified and the callback must not run.
|
// considered verified and the callback must not run.
|
||||||
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, sig.Format)
|
||||||
}
|
|
||||||
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":
|
case "gssapi-with-mic":
|
||||||
@@ -925,6 +947,17 @@ userAuthLoop:
|
|||||||
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
|
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The source-address critical option is enforced on the Permissions
|
||||||
|
// returned by any authentication callback. Permissions returned
|
||||||
|
// together with a PartialSuccessError skip this check: that is safe
|
||||||
|
// because they are required to be nil, as enforced in the partial
|
||||||
|
// success handling below.
|
||||||
|
if authErr == nil {
|
||||||
|
if err := checkSourceAddressCriticalOption(s.RemoteAddr(), perms); err != nil {
|
||||||
|
authErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
authErrs = append(authErrs, authErr)
|
authErrs = append(authErrs, authErr)
|
||||||
|
|
||||||
if config.AuthLogCallback != nil {
|
if config.AuthLogCallback != nil {
|
||||||
|
|||||||
@@ -26,11 +26,21 @@ func doClientServerAuth(t *testing.T, serverConfig *ServerConfig, clientConfig *
|
|||||||
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
|
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
|
||||||
serverAuthErrors = append(serverAuthErrors, err)
|
serverAuthErrors = append(serverAuthErrors, err)
|
||||||
}
|
}
|
||||||
go newServer(c1, serverConfig)
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
newServer(c1, serverConfig)
|
||||||
|
}()
|
||||||
c, _, _, err := NewClientConn(c2, "", clientConfig)
|
c, _, _, err := NewClientConn(c2, "", clientConfig)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
|
} else {
|
||||||
|
// Unblock the server if it is still reading from the connection.
|
||||||
|
c2.Close()
|
||||||
}
|
}
|
||||||
|
// Wait for the server side to finish before reading serverAuthErrors:
|
||||||
|
// AuthLogCallback appends to it from the server goroutine.
|
||||||
|
<-serverDone
|
||||||
return serverAuthErrors, err
|
return serverAuthErrors, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -544,6 +544,62 @@ func TestVerifiedPublicKeyCallback(t *testing.T) {
|
|||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicKeyCallbackSignatureFormat(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
var gotSignatureAlgorithm string
|
||||||
|
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) {
|
||||||
|
gotSignatureAlgorithm = signatureAlgorithm
|
||||||
|
return permissions, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
serverDone := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
conn, _, _, err := NewServerConn(c1, serverConf)
|
||||||
|
if err == nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
serverDone <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConf := &ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
// The selected algorithm and signature format differ for
|
||||||
|
// compatibility with old RSA clients.
|
||||||
|
configurablePublicKeyCallback{
|
||||||
|
signer: testSigners["rsa"].(AlgorithmSigner),
|
||||||
|
signatureAlgo: KeyAlgoRSASHA256,
|
||||||
|
signatureFormat: KeyAlgoRSA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConn, _, _, clientErr := NewClientConn(c2, "", clientConf)
|
||||||
|
if clientErr == nil {
|
||||||
|
clientConn.Close()
|
||||||
|
}
|
||||||
|
serverErr := <-serverDone
|
||||||
|
if clientErr != nil || serverErr != nil {
|
||||||
|
t.Fatalf("connection failed: client error: %v; server error: %v", clientErr, serverErr)
|
||||||
|
}
|
||||||
|
if gotSignatureAlgorithm != KeyAlgoRSA {
|
||||||
|
t.Errorf("signature algorithm = %q; want verified signature format %q", gotSignatureAlgorithm, KeyAlgoRSA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
|
func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
|
||||||
c1, c2, err := netPipe()
|
c1, c2, err := netPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -760,6 +816,107 @@ func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthCallbacksSourceAddress(t *testing.T) {
|
||||||
|
permsWithSourceAddress := func(sourceAddress string) *Permissions {
|
||||||
|
return &Permissions{
|
||||||
|
CriticalOptions: map[string]string{
|
||||||
|
sourceAddressCriticalOption: sourceAddress,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methods := []struct {
|
||||||
|
name string
|
||||||
|
serverConfig func(sourceAddress string) *ServerConfig
|
||||||
|
clientAuth []AuthMethod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
serverConfig: func(sourceAddress string) *ServerConfig {
|
||||||
|
return &ServerConfig{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
return permsWithSourceAddress(sourceAddress), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientAuth: []AuthMethod{Password(clientPassword)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "keyboard-interactive",
|
||||||
|
serverConfig: func(sourceAddress string) *ServerConfig {
|
||||||
|
return &ServerConfig{
|
||||||
|
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
|
||||||
|
return permsWithSourceAddress(sourceAddress), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientAuth: []AuthMethod{
|
||||||
|
KeyboardInteractive(func(name, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
serverConfig: func(sourceAddress string) *ServerConfig {
|
||||||
|
return &ServerConfig{
|
||||||
|
NoClientAuth: true,
|
||||||
|
NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
|
||||||
|
return permsWithSourceAddress(sourceAddress), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, method := range methods {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
sourceAddress string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"mismatching", "192.168.99.99", true},
|
||||||
|
{"matching", "127.0.0.0/8,::1/128", false},
|
||||||
|
{"present but empty", "", true},
|
||||||
|
} {
|
||||||
|
t.Run(method.name+"/"+tc.name, func(t *testing.T) {
|
||||||
|
clientConf := &ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: method.clientAuth,
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
serverAuthErrors, err := doClientServerAuth(t, method.serverConfig(tc.sourceAddress), clientConf)
|
||||||
|
if tc.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("client login succeeded with %s callback returning mismatching source-address", method.name)
|
||||||
|
}
|
||||||
|
var sourceAddressErrors int
|
||||||
|
for _, err := range serverAuthErrors {
|
||||||
|
if err != nil && strings.Contains(err.Error(), "source-address restriction") {
|
||||||
|
sourceAddressErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sourceAddressErrors == 0 {
|
||||||
|
t.Fatalf("no server auth error mentions the source-address restriction, got %v", serverAuthErrors)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("client login failed with matching source-address: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckSourceAddressNonIP(t *testing.T) {
|
||||||
|
for _, addr := range []net.Addr{
|
||||||
|
&net.UnixAddr{Name: "/run/test.sock", Net: "unix"},
|
||||||
|
&net.UnixAddr{},
|
||||||
|
nil,
|
||||||
|
} {
|
||||||
|
if err := checkSourceAddress(addr, "127.0.0.0/8,::1/128"); err == nil {
|
||||||
|
t.Errorf("source-address check succeeded for non-IP remote address %v", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
|
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
|
||||||
c1, c2, err := netPipe()
|
c1, c2, err := netPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+1
-1
@@ -745,7 +745,7 @@ func TestSessionID(t *testing.T) {
|
|||||||
|
|
||||||
s := <-serverID
|
s := <-serverID
|
||||||
c := <-clientID
|
c := <-clientID
|
||||||
if bytes.Compare(s, c) != 0 {
|
if !bytes.Equal(s, c) {
|
||||||
t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
|
t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
|
||||||
} else if len(s) == 0 {
|
} else if len(s) == 0 {
|
||||||
t.Errorf("client and server SessionID were empty.")
|
t.Errorf("client and server SessionID were empty.")
|
||||||
|
|||||||
+10
-6
@@ -118,24 +118,28 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
|
|||||||
OIDS: make([]asn1.ObjectIdentifier, n),
|
OIDS: make([]asn1.ObjectIdentifier, n),
|
||||||
}
|
}
|
||||||
for i := 0; i < int(n); i++ {
|
for i := 0; i < int(n); i++ {
|
||||||
var (
|
var desiredMech []byte
|
||||||
desiredMech []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
desiredMech, rest, ok = parseString(rest)
|
desiredMech, rest, ok = parseString(rest)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("parse string failed")
|
return nil, errors.New("parse string failed")
|
||||||
}
|
}
|
||||||
if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil {
|
trailing, err := asn1.Unmarshal(desiredMech, &s.OIDS[i])
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(trailing) != 0 {
|
||||||
|
return nil, errors.New("trailing bytes after OID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rest) != 0 {
|
||||||
|
return nil, errors.New("trailing bytes after mechanisms")
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4462 section 3.6.
|
// See RFC 4462 section 3.6.
|
||||||
func buildMIC(sessionID string, username string, service string, authMethod string) []byte {
|
func buildMIC(sessionID string, username string, service string, authMethod string) []byte {
|
||||||
out := make([]byte, 0, 0)
|
out := make([]byte, 0)
|
||||||
out = appendString(out, sessionID)
|
out = appendString(out, sessionID)
|
||||||
out = append(out, msgUserAuthRequest)
|
out = append(out, msgUserAuthRequest)
|
||||||
out = appendString(out, username)
|
out = appendString(out, username)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -17,6 +18,34 @@ func TestParseGSSAPIPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseGSSAPIPayloadMultipleMechs(t *testing.T) {
|
||||||
|
// A payload offering two mechanisms: Kerberos V5 (1.2.840.113554.1.2.2)
|
||||||
|
// followed by SPNEGO (1.3.6.1.5.5.2). The parser must track the
|
||||||
|
// remaining payload across loop iterations, not the trailing bytes
|
||||||
|
// returned by asn1.Unmarshal.
|
||||||
|
payload := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x02, // two mechanisms
|
||||||
|
0x00, 0x00, 0x00, 0x0b, // length of the first OID
|
||||||
|
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x08, // length of the second OID
|
||||||
|
0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02,
|
||||||
|
}
|
||||||
|
res, err := parseGSSAPIPayload(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(res.OIDS) != 2 {
|
||||||
|
t.Fatalf("got %d OIDs, want 2", len(res.OIDS))
|
||||||
|
}
|
||||||
|
if !res.OIDS[0].Equal(krb5Mesh) {
|
||||||
|
t.Errorf("OIDS[0]: got %v, want %v", res.OIDS[0], krb5Mesh)
|
||||||
|
}
|
||||||
|
spnego := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
|
||||||
|
if !res.OIDS[1].Equal(spnego) {
|
||||||
|
t.Errorf("OIDS[1]: got %v, want %v", res.OIDS[1], spnego)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseDubiousGSSAPIPayload(t *testing.T) {
|
func TestParseDubiousGSSAPIPayload(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go DiscardRequests(in)
|
go DiscardRequests(in)
|
||||||
|
go io.Copy(io.Discard, ch.Stderr())
|
||||||
return ch, err
|
return ch, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ func (l *unixListener) Accept() (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go DiscardRequests(incoming)
|
go DiscardRequests(incoming)
|
||||||
|
go io.Copy(io.Discard, ch.Stderr())
|
||||||
|
|
||||||
return &chanConn{
|
return &chanConn{
|
||||||
Channel: ch,
|
Channel: ch,
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go DiscardRequests(incoming)
|
go DiscardRequests(incoming)
|
||||||
|
go io.Copy(io.Discard, ch.Stderr())
|
||||||
|
|
||||||
return &chanConn{
|
return &chanConn{
|
||||||
Channel: ch,
|
Channel: ch,
|
||||||
@@ -495,6 +496,7 @@ func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go DiscardRequests(in)
|
go DiscardRequests(in)
|
||||||
|
go io.Copy(io.Discard, ch.Stderr())
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -51,3 +52,213 @@ func TestClientDialContextWithDeadline(t *testing.T) {
|
|||||||
t.Errorf("DialContext: got nil error, expected %v", context.DeadlineExceeded)
|
t.Errorf("DialContext: got nil error, expected %v", context.DeadlineExceeded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forwardingPair establishes a client/server connection pair over an
|
||||||
|
// in-memory pipe. The server grants all global requests and accepts all
|
||||||
|
// channels opened by the client, delivering them on the returned channel.
|
||||||
|
func forwardingPair(t *testing.T) (*Client, *ServerConn, <-chan Channel) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
t.Cleanup(func() { <-serverDone })
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c1.Close()
|
||||||
|
c2.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
serverConf := ServerConfig{
|
||||||
|
NoClientAuth: true,
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
incoming := make(chan *ServerConn, 1)
|
||||||
|
serverChans := make(chan Channel, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
conn, chans, reqs, err := NewServerConn(c1, &serverConf)
|
||||||
|
incoming <- conn
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewServerConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for r := range reqs {
|
||||||
|
r.Reply(true, nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for newCh := range chans {
|
||||||
|
ch, chanReqs, err := newCh.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Accept: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go DiscardRequests(chanReqs)
|
||||||
|
serverChans <- ch
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
conf := ClientConfig{
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
conn, chans, reqs, err := NewClientConn(c2, "", &conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClientConn: %v", err)
|
||||||
|
}
|
||||||
|
client := NewClient(conn, chans, reqs)
|
||||||
|
t.Cleanup(func() { client.Close() })
|
||||||
|
|
||||||
|
server := <-incoming
|
||||||
|
if server == nil {
|
||||||
|
t.Fatal("Unable to get server")
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { server.Close() })
|
||||||
|
|
||||||
|
return client, server, serverChans
|
||||||
|
}
|
||||||
|
|
||||||
|
// floodStderr writes more than the default channel receive window (2 MiB)
|
||||||
|
// to w, which must be the stderr stream of an SSH channel. Stderr data is
|
||||||
|
// buffered and its window credit is only returned when the stream is
|
||||||
|
// read, so if the peer does not drain the stream the write blocks forever
|
||||||
|
// once the remote window is exhausted.
|
||||||
|
func floodStderr(t *testing.T, w io.Writer) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
const payload = 4 * 1024 * 1024
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
remaining := payload
|
||||||
|
for remaining > 0 {
|
||||||
|
n := min(len(buf), remaining)
|
||||||
|
nw, err := w.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
done <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining -= nw
|
||||||
|
}
|
||||||
|
done <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("write to stderr: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatal("write to stderr blocked: the stderr stream is not drained")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyMainStream checks that the main stream is still usable after
|
||||||
|
// stderr traffic by sending a payload from the server channel and
|
||||||
|
// reading it back from the client connection.
|
||||||
|
func verifyMainStream(t *testing.T, serverCh Channel, clientConn net.Conn) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
want := []byte("main stream after stderr flood")
|
||||||
|
if _, err := serverCh.Write(want); err != nil {
|
||||||
|
t.Fatalf("write to main stream: %v", err)
|
||||||
|
}
|
||||||
|
got := make([]byte, len(want))
|
||||||
|
if _, err := io.ReadFull(clientConn, got); err != nil {
|
||||||
|
t.Fatalf("read from main stream: %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != string(want) {
|
||||||
|
t.Fatalf("read %q from main stream, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDialDiscardsStderr(t *testing.T) {
|
||||||
|
for _, network := range []string{"tcp", "unix"} {
|
||||||
|
t.Run(network, func(t *testing.T) {
|
||||||
|
client, _, serverChans := forwardingPair(t)
|
||||||
|
|
||||||
|
addr := "127.0.0.1:8022"
|
||||||
|
if network == "unix" {
|
||||||
|
addr = "/tmp/forwarded.sock"
|
||||||
|
}
|
||||||
|
conn, err := client.Dial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial(%q, %q): %v", network, addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
serverCh := <-serverChans
|
||||||
|
defer serverCh.Close()
|
||||||
|
|
||||||
|
floodStderr(t, serverCh.Stderr())
|
||||||
|
verifyMainStream(t, serverCh, conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientListenerDiscardsStderr(t *testing.T) {
|
||||||
|
for _, network := range []string{"tcp", "unix"} {
|
||||||
|
t.Run(network, func(t *testing.T) {
|
||||||
|
client, server, _ := forwardingPair(t)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ln net.Listener
|
||||||
|
payload []byte
|
||||||
|
chType string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if network == "tcp" {
|
||||||
|
ln, err = client.Listen("tcp", "127.0.0.1:8022")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Listen: %v", err)
|
||||||
|
}
|
||||||
|
chType = "forwarded-tcpip"
|
||||||
|
payload = Marshal(&forwardedTCPPayload{
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
Port: 8022,
|
||||||
|
OriginAddr: "127.0.0.5",
|
||||||
|
OriginPort: 1234,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ln, err = client.ListenUnix("/tmp/forwarded.sock")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListenUnix: %v", err)
|
||||||
|
}
|
||||||
|
chType = "forwarded-streamlocal@openssh.com"
|
||||||
|
payload = Marshal(&forwardedStreamLocalPayload{
|
||||||
|
SocketPath: "/tmp/forwarded.sock",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
// OpenChannel does not return until the channel is
|
||||||
|
// accepted, so run Accept concurrently.
|
||||||
|
accepted := make(chan net.Conn, 1)
|
||||||
|
go func() {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Accept: %v", err)
|
||||||
|
close(accepted)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accepted <- conn
|
||||||
|
}()
|
||||||
|
|
||||||
|
serverCh, reqs, err := server.OpenChannel(chType, payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenChannel(%q): %v", chType, err)
|
||||||
|
}
|
||||||
|
go DiscardRequests(reqs)
|
||||||
|
defer serverCh.Close()
|
||||||
|
|
||||||
|
conn := <-accepted
|
||||||
|
if conn == nil {
|
||||||
|
t.Fatal("no connection accepted")
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
floodStderr(t, serverCh.Stderr())
|
||||||
|
verifyMainStream(t, serverCh, conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+9
-3
@@ -331,13 +331,19 @@ func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err er
|
|||||||
// chars
|
// chars
|
||||||
const maxVersionStringBytes = 255
|
const maxVersionStringBytes = 255
|
||||||
|
|
||||||
|
// maxPreVersionLines is the maximum number of lines sent by the peer
|
||||||
|
// before the version string. Each of these lines is limited to a maximum
|
||||||
|
// of maxVersionStringBytes chars. Lines sent before the version string
|
||||||
|
// are silently ignored.
|
||||||
|
const maxPreVersionLines = 1024
|
||||||
|
|
||||||
// Read version string as specified by RFC 4253, section 4.2.
|
// Read version string as specified by RFC 4253, section 4.2.
|
||||||
func readVersion(r io.Reader) ([]byte, error) {
|
func readVersion(r io.Reader) ([]byte, error) {
|
||||||
versionString := make([]byte, 0, 64)
|
versionString := make([]byte, 0, 64)
|
||||||
var ok bool
|
var ok bool
|
||||||
var buf [1]byte
|
var buf [1]byte
|
||||||
|
|
||||||
for length := 0; length < maxVersionStringBytes; length++ {
|
for lines := 0; len(versionString) < maxVersionStringBytes && lines < maxPreVersionLines; {
|
||||||
_, err := io.ReadFull(r, buf[:])
|
_, err := io.ReadFull(r, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -347,9 +353,9 @@ func readVersion(r io.Reader) ([]byte, error) {
|
|||||||
if buf[0] == '\n' {
|
if buf[0] == '\n' {
|
||||||
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
|
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
|
||||||
// RFC 4253 says we need to ignore all version string lines
|
// RFC 4253 says we need to ignore all version string lines
|
||||||
// except the one containing the SSH version (provided that
|
// except the one containing the SSH version.
|
||||||
// all the lines do not exceed 255 bytes in total).
|
|
||||||
versionString = versionString[:0]
|
versionString = versionString[:0]
|
||||||
|
lines++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok = true
|
ok = true
|
||||||
|
|||||||
+16
-4
@@ -35,11 +35,15 @@ func TestReadVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadVersionError(t *testing.T) {
|
func TestReadVersionError(t *testing.T) {
|
||||||
longVersion := strings.Repeat("SSH-2.0-bla", 50)[:253]
|
tooLongVersion := strings.Repeat("SSH-2.0-bla", 60) + "\r\n"
|
||||||
multiLineVersion := strings.Repeat("ignored\r\n", 50) + "SSH-2.0-bla\r\n"
|
multiLine := strings.Repeat("ignored\r\n", 50)
|
||||||
|
tooManyLines := strings.Repeat("ignored\r\n", 2000)
|
||||||
|
tooLongLine := strings.Repeat("blah ", 100) + "\r\n"
|
||||||
cases := []string{
|
cases := []string{
|
||||||
longVersion + "too-long\r\n",
|
tooLongVersion,
|
||||||
multiLineVersion,
|
multiLine + tooLongVersion,
|
||||||
|
tooManyLines + "SSH-2.0-bla\r\n",
|
||||||
|
tooLongLine + "SSH-2.0-bla\r\n",
|
||||||
}
|
}
|
||||||
for _, in := range cases {
|
for _, in := range cases {
|
||||||
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
|
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
|
||||||
@@ -48,6 +52,14 @@ func TestReadVersionError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIgnorePreVersion(t *testing.T) {
|
||||||
|
longishLine := strings.Repeat("blah ", 30) + "\r\n"
|
||||||
|
multiLineVersion := strings.Repeat(longishLine, 100) + "SSH-2.0-bla\r\n"
|
||||||
|
if _, err := readVersion(bytes.NewBufferString(multiLineVersion)); err != nil {
|
||||||
|
t.Errorf("readVersion() got error %v, expected success", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExchangeVersionsBasic(t *testing.T) {
|
func TestExchangeVersionsBasic(t *testing.T) {
|
||||||
v := "SSH-2.0-bla"
|
v := "SSH-2.0-bla"
|
||||||
buf := bytes.NewBufferString(v + "\r\n")
|
buf := bytes.NewBufferString(v + "\r\n")
|
||||||
|
|||||||
Binary file not shown.
+137
-126
@@ -4,7 +4,7 @@ package bundle
|
|||||||
|
|
||||||
var unparsedCertificates = []unparsedCertificate{
|
var unparsedCertificates = []unparsedCertificate{
|
||||||
{
|
{
|
||||||
cn: "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS,OU=Ceres,O=FNMT-RCM,C=ES,2.5.4.97=#130f56415445532d51323832363030344a",
|
cn: "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS,OU=Ceres,O=FNMT-RCM,C=ES,2.5.4.97=VATES-Q2826004J",
|
||||||
sha256Hash: "554153b13d2cf9ddb753bfbe1a4e0ae08d0aa4187058fe60a2b862b2e4b87bcb",
|
sha256Hash: "554153b13d2cf9ddb753bfbe1a4e0ae08d0aa4187058fe60a2b862b2e4b87bcb",
|
||||||
certStartOff: 0,
|
certStartOff: 0,
|
||||||
certLength: 626,
|
certLength: 626,
|
||||||
@@ -45,671 +45,682 @@ var unparsedCertificates = []unparsedCertificate{
|
|||||||
certStartOff: 6732,
|
certStartOff: 6732,
|
||||||
certLength: 502,
|
certLength: 502,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
cn: "CN=Atos TrustedRoot 2011,O=Atos,C=DE",
|
|
||||||
sha256Hash: "f356bea244b7a91eb35d53ca9ad7864ace018e2d35d5f8f96ddf68a6f41aa474",
|
|
||||||
certStartOff: 7234,
|
|
||||||
certLength: 891,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
cn: "CN=Atos TrustedRoot Root CA ECC TLS 2021,O=Atos,C=DE",
|
cn: "CN=Atos TrustedRoot Root CA ECC TLS 2021,O=Atos,C=DE",
|
||||||
sha256Hash: "b2fae53e14ccd7ab9212064701ae279c1d8988facb775fa8a008914e663988a8",
|
sha256Hash: "b2fae53e14ccd7ab9212064701ae279c1d8988facb775fa8a008914e663988a8",
|
||||||
certStartOff: 8125,
|
certStartOff: 7234,
|
||||||
certLength: 537,
|
certLength: 537,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Atos TrustedRoot Root CA RSA TLS 2021,O=Atos,C=DE",
|
cn: "CN=Atos TrustedRoot Root CA RSA TLS 2021,O=Atos,C=DE",
|
||||||
sha256Hash: "81a9088ea59fb364c548a6f85559099b6f0405efbf18e5324ec9f457ba00112f",
|
sha256Hash: "81a9088ea59fb364c548a6f85559099b6f0405efbf18e5324ec9f457ba00112f",
|
||||||
certStartOff: 8662,
|
certStartOff: 7771,
|
||||||
certLength: 1384,
|
certLength: 1384,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Autoridad de Certificacion Firmaprofesional CIF A62634068,C=ES",
|
cn: "CN=Autoridad de Certificacion Firmaprofesional CIF A62634068,C=ES",
|
||||||
sha256Hash: "57de0583efd2b26e0361da99da9df4648def7ee8441c3b728afa9bcde0f9b26a",
|
sha256Hash: "57de0583efd2b26e0361da99da9df4648def7ee8441c3b728afa9bcde0f9b26a",
|
||||||
certStartOff: 10046,
|
certStartOff: 9155,
|
||||||
certLength: 1560,
|
certLength: 1560,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=BJCA Global Root CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
cn: "CN=BJCA Global Root CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||||
sha256Hash: "f3896f88fe7c0a882766a7fa6ad2749fb57a7f3e98fb769c1fa7b09c2c44d5ae",
|
sha256Hash: "f3896f88fe7c0a882766a7fa6ad2749fb57a7f3e98fb769c1fa7b09c2c44d5ae",
|
||||||
certStartOff: 11606,
|
certStartOff: 10715,
|
||||||
certLength: 1400,
|
certLength: 1400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=BJCA Global Root CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
cn: "CN=BJCA Global Root CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||||
sha256Hash: "574df6931e278039667b720afdc1600fc27eb66dd3092979fb73856487212882",
|
sha256Hash: "574df6931e278039667b720afdc1600fc27eb66dd3092979fb73856487212882",
|
||||||
certStartOff: 13006,
|
certStartOff: 12115,
|
||||||
certLength: 553,
|
certLength: 553,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Buypass Class 2 Root CA,O=Buypass AS-983163327,C=NO",
|
cn: "CN=Buypass Class 2 Root CA,O=Buypass AS-983163327,C=NO",
|
||||||
sha256Hash: "9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48",
|
sha256Hash: "9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48",
|
||||||
certStartOff: 13559,
|
certStartOff: 12668,
|
||||||
certLength: 1373,
|
certLength: 1373,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO",
|
cn: "CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO",
|
||||||
sha256Hash: "edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d",
|
sha256Hash: "edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d",
|
||||||
certStartOff: 14932,
|
certStartOff: 14041,
|
||||||
certLength: 1373,
|
certLength: 1373,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=CA Disig Root R2,O=Disig a.s.,L=Bratislava,C=SK",
|
cn: "CN=CA Disig Root R2,O=Disig a.s.,L=Bratislava,C=SK",
|
||||||
sha256Hash: "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
|
sha256Hash: "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
|
||||||
certStartOff: 16305,
|
certStartOff: 15414,
|
||||||
certLength: 1389,
|
certLength: 1389,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN",
|
cn: "CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN",
|
||||||
sha256Hash: "5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd",
|
sha256Hash: "5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd",
|
||||||
certStartOff: 17694,
|
certStartOff: 16803,
|
||||||
certLength: 1425,
|
certLength: 1425,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
cn: "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||||
sha256Hash: "1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7",
|
sha256Hash: "1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7",
|
||||||
certStartOff: 19119,
|
certStartOff: 18228,
|
||||||
certLength: 653,
|
certLength: 653,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
cn: "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||||
sha256Hash: "52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234",
|
sha256Hash: "52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234",
|
||||||
certStartOff: 19772,
|
certStartOff: 18881,
|
||||||
certLength: 1500,
|
certLength: 1500,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certainly Root E1,O=Certainly,C=US",
|
cn: "CN=Certainly Root E1,O=Certainly,C=US",
|
||||||
sha256Hash: "b4585f22e4ac756a4e8612a1361c5d9d031a93fd84febb778fa3068b0fc42dc2",
|
sha256Hash: "b4585f22e4ac756a4e8612a1361c5d9d031a93fd84febb778fa3068b0fc42dc2",
|
||||||
certStartOff: 21272,
|
certStartOff: 20381,
|
||||||
certLength: 507,
|
certLength: 507,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certainly Root R1,O=Certainly,C=US",
|
cn: "CN=Certainly Root R1,O=Certainly,C=US",
|
||||||
sha256Hash: "77b82cd8644c4305f7acc5cb156b45675004033d51c60c6202a8e0c33467d3a0",
|
sha256Hash: "77b82cd8644c4305f7acc5cb156b45675004033d51c60c6202a8e0c33467d3a0",
|
||||||
certStartOff: 21779,
|
certStartOff: 20888,
|
||||||
certLength: 1355,
|
certLength: 1355,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certigna Root CA,OU=0002 48146308100036,O=Dhimyotis,C=FR",
|
cn: "CN=Certigna Root CA,OU=0002 48146308100036,O=Dhimyotis,C=FR",
|
||||||
sha256Hash: "d48d3d23eedb50a459e55197601c27774b9d7b18c94d5a059511a10250b93168",
|
sha256Hash: "d48d3d23eedb50a459e55197601c27774b9d7b18c94d5a059511a10250b93168",
|
||||||
certStartOff: 23134,
|
certStartOff: 22243,
|
||||||
certLength: 1631,
|
certLength: 1631,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
cn: "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||||
sha256Hash: "6b328085625318aa50d173c98d8bda09d57e27413d114cf787a0f5d06c030cf6",
|
sha256Hash: "6b328085625318aa50d173c98d8bda09d57e27413d114cf787a0f5d06c030cf6",
|
||||||
certStartOff: 24765,
|
certStartOff: 23874,
|
||||||
certLength: 617,
|
certLength: 617,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
cn: "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||||
sha256Hash: "b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804",
|
sha256Hash: "b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804",
|
||||||
certStartOff: 25382,
|
certStartOff: 24491,
|
||||||
certLength: 1494,
|
certLength: 1494,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
cn: "CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||||
sha256Hash: "5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e",
|
sha256Hash: "5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e",
|
||||||
certStartOff: 26876,
|
certStartOff: 25985,
|
||||||
certLength: 959,
|
certLength: 959,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
cn: "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||||
sha256Hash: "fe7696573855773e37a95e7ad4d9cc96c30157c15d31765ba9b15704e1ae78fd",
|
sha256Hash: "fe7696573855773e37a95e7ad4d9cc96c30157c15d31765ba9b15704e1ae78fd",
|
||||||
certStartOff: 27835,
|
certStartOff: 26944,
|
||||||
certLength: 1476,
|
certLength: 1476,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST BR Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST BR Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "e59aaa816009c22bff5b25bad37df306f049797c1f81d85ab089e657bd8f0044",
|
sha256Hash: "e59aaa816009c22bff5b25bad37df306f049797c1f81d85ab089e657bd8f0044",
|
||||||
certStartOff: 29311,
|
certStartOff: 28420,
|
||||||
certLength: 735,
|
certLength: 735,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST BR Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST BR Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "0552e6f83fdf65e8fa9670e666df28a4e21340b510cbe52566f97c4fb94b2bd1",
|
sha256Hash: "0552e6f83fdf65e8fa9670e666df28a4e21340b510cbe52566f97c4fb94b2bd1",
|
||||||
certStartOff: 30046,
|
certStartOff: 29155,
|
||||||
certLength: 1453,
|
certLength: 1453,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST EV Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST EV Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "08170d1aa36453901a2f959245e347db0c8d37abaabc56b81aa100dc958970db",
|
sha256Hash: "08170d1aa36453901a2f959245e347db0c8d37abaabc56b81aa100dc958970db",
|
||||||
certStartOff: 31499,
|
certStartOff: 30608,
|
||||||
certLength: 735,
|
certLength: 735,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST EV Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST EV Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "8e8221b2e7d4007836a1672f0dcc299c33bc07d316f132fa1a206d587150f1ce",
|
sha256Hash: "8e8221b2e7d4007836a1672f0dcc299c33bc07d316f132fa1a206d587150f1ce",
|
||||||
certStartOff: 32234,
|
certStartOff: 31343,
|
||||||
certLength: 1453,
|
certLength: 1453,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST Root Class 3 CA 2 2009,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST Root Class 3 CA 2 2009,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1",
|
sha256Hash: "49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1",
|
||||||
certStartOff: 33687,
|
certStartOff: 32796,
|
||||||
certLength: 1079,
|
certLength: 1079,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE",
|
cn: "CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE",
|
||||||
sha256Hash: "eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881",
|
sha256Hash: "eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881",
|
||||||
certStartOff: 34766,
|
certStartOff: 33875,
|
||||||
certLength: 1095,
|
certLength: 1095,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
cn: "CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||||
sha256Hash: "7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185",
|
sha256Hash: "7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185",
|
||||||
certStartOff: 35861,
|
certStartOff: 34970,
|
||||||
certLength: 922,
|
certLength: 922,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
cn: "CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||||
sha256Hash: "7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2",
|
sha256Hash: "7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2",
|
||||||
certStartOff: 36783,
|
certStartOff: 35892,
|
||||||
certLength: 586,
|
certLength: 586,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
cn: "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||||
sha256Hash: "cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f",
|
sha256Hash: "cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f",
|
||||||
certStartOff: 37369,
|
certStartOff: 36478,
|
||||||
certLength: 914,
|
certLength: 914,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
cn: "CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||||
sha256Hash: "31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0",
|
sha256Hash: "31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0",
|
||||||
certStartOff: 38283,
|
certStartOff: 37392,
|
||||||
certLength: 579,
|
certLength: 579,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US",
|
cn: "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||||
sha256Hash: "018e13f0772532cf809bd1b17281867283fc48c6e13be9c69812854a490c1b05",
|
sha256Hash: "018e13f0772532cf809bd1b17281867283fc48c6e13be9c69812854a490c1b05",
|
||||||
certStartOff: 38862,
|
certStartOff: 37971,
|
||||||
certLength: 541,
|
certLength: 541,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US",
|
cn: "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||||
sha256Hash: "371a00dc0533b3721a7eeb40e8419e70799d2b0a0f2c1d80693165f7cec4ad75",
|
sha256Hash: "371a00dc0533b3721a7eeb40e8419e70799d2b0a0f2c1d80693165f7cec4ad75",
|
||||||
certStartOff: 39403,
|
certStartOff: 38512,
|
||||||
certLength: 1386,
|
certLength: 1386,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
cn: "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||||
sha256Hash: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
|
sha256Hash: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
|
||||||
certStartOff: 40789,
|
certStartOff: 39898,
|
||||||
certLength: 1428,
|
certLength: 1428,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN",
|
cn: "CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN",
|
||||||
sha256Hash: "bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893",
|
sha256Hash: "bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893",
|
||||||
certStartOff: 42217,
|
certStartOff: 41326,
|
||||||
certLength: 1420,
|
certLength: 1420,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GTS Root R1,O=Google Trust Services LLC,C=US",
|
cn: "CN=GTS Root R1,O=Google Trust Services LLC,C=US",
|
||||||
sha256Hash: "d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf",
|
sha256Hash: "d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf",
|
||||||
certStartOff: 43637,
|
certStartOff: 42746,
|
||||||
certLength: 1371,
|
certLength: 1371,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GTS Root R3,O=Google Trust Services LLC,C=US",
|
cn: "CN=GTS Root R3,O=Google Trust Services LLC,C=US",
|
||||||
sha256Hash: "34d8a73ee208d9bcdb0d956520934b4e40e69482596e8b6f73c8426b010a6f48",
|
sha256Hash: "34d8a73ee208d9bcdb0d956520934b4e40e69482596e8b6f73c8426b010a6f48",
|
||||||
certStartOff: 45008,
|
certStartOff: 44117,
|
||||||
certLength: 525,
|
certLength: 525,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GTS Root R4,O=Google Trust Services LLC,C=US",
|
cn: "CN=GTS Root R4,O=Google Trust Services LLC,C=US",
|
||||||
sha256Hash: "349dfa4058c5e263123b398ae795573c4e1313c83fe68f93556cd5e8031b3c7d",
|
sha256Hash: "349dfa4058c5e263123b398ae795573c4e1313c83fe68f93556cd5e8031b3c7d",
|
||||||
certStartOff: 45533,
|
certStartOff: 44642,
|
||||||
certLength: 525,
|
certLength: 525,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE",
|
cn: "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE",
|
||||||
sha256Hash: "cbb9c44d84b8043e1050ea31a69f514955d7bfd2e2c6b49301019ad61d9f5058",
|
sha256Hash: "cbb9c44d84b8043e1050ea31a69f514955d7bfd2e2c6b49301019ad61d9f5058",
|
||||||
certStartOff: 46058,
|
certStartOff: 45167,
|
||||||
certLength: 527,
|
certLength: 527,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE",
|
cn: "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE",
|
||||||
sha256Hash: "4fa3126d8d3a11d1c4855a4f807cbad6cf919d3a5a88b03bea2c6372d93c40c9",
|
sha256Hash: "4fa3126d8d3a11d1c4855a4f807cbad6cf919d3a5a88b03bea2c6372d93c40c9",
|
||||||
certStartOff: 46585,
|
certStartOff: 45694,
|
||||||
certLength: 1374,
|
certLength: 1374,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R4,O=GlobalSign",
|
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R4,O=GlobalSign",
|
||||||
sha256Hash: "b085d70b964f191a73e4af0d54ae7a0e07aafdaf9b71dd0862138ab7325a24a2",
|
sha256Hash: "b085d70b964f191a73e4af0d54ae7a0e07aafdaf9b71dd0862138ab7325a24a2",
|
||||||
certStartOff: 47959,
|
certStartOff: 47068,
|
||||||
certLength: 480,
|
certLength: 480,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign",
|
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign",
|
||||||
sha256Hash: "179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924",
|
sha256Hash: "179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924",
|
||||||
certStartOff: 48439,
|
certStartOff: 47548,
|
||||||
certLength: 546,
|
certLength: 546,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign",
|
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign",
|
||||||
sha256Hash: "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b",
|
sha256Hash: "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b",
|
||||||
certStartOff: 48985,
|
certStartOff: 48094,
|
||||||
certLength: 867,
|
certLength: 867,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign",
|
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign",
|
||||||
sha256Hash: "2cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69",
|
sha256Hash: "2cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69",
|
||||||
certStartOff: 49852,
|
certStartOff: 48961,
|
||||||
certLength: 1415,
|
certLength: 1415,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
cn: "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||||
sha256Hash: "45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda",
|
sha256Hash: "45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda",
|
||||||
certStartOff: 51267,
|
certStartOff: 50376,
|
||||||
certLength: 969,
|
certLength: 969,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
cn: "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||||
sha256Hash: "3f99cc474acfce4dfed58794665e478d1547739f2e780f1bb4ca9b133097d401",
|
sha256Hash: "3f99cc474acfce4dfed58794665e478d1547739f2e780f1bb4ca9b133097d401",
|
||||||
certStartOff: 52236,
|
certStartOff: 51345,
|
||||||
certLength: 600,
|
certLength: 600,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
cn: "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||||
sha256Hash: "d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d",
|
sha256Hash: "d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d",
|
||||||
certStartOff: 52836,
|
certStartOff: 51945,
|
||||||
certLength: 1448,
|
certLength: 1448,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Hellenic Academic and Research Institutions ECC RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
cn: "CN=Hellenic Academic and Research Institutions ECC RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||||
sha256Hash: "44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833",
|
sha256Hash: "44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833",
|
||||||
certStartOff: 54284,
|
certStartOff: 53393,
|
||||||
certLength: 711,
|
certLength: 711,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Hellenic Academic and Research Institutions RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
cn: "CN=Hellenic Academic and Research Institutions RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||||
sha256Hash: "a040929a02ce53b4acf4f2ffc6981ce4496f755e6d45fe0b2a692bcd52523f36",
|
sha256Hash: "a040929a02ce53b4acf4f2ffc6981ce4496f755e6d45fe0b2a692bcd52523f36",
|
||||||
certStartOff: 54995,
|
certStartOff: 54104,
|
||||||
certLength: 1551,
|
certLength: 1551,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
cn: "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
||||||
sha256Hash: "f015ce3cc239bfef064be9f1d2c417e1a0264a0a94be1f0c8d121864eb6949cc",
|
sha256Hash: "f015ce3cc239bfef064be9f1d2c417e1a0264a0a94be1f0c8d121864eb6949cc",
|
||||||
certStartOff: 56546,
|
certStartOff: 55655,
|
||||||
certLength: 1390,
|
certLength: 1390,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK",
|
cn: "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK",
|
||||||
sha256Hash: "5a2fc03f0c83b090bbfa40604b0988446c7636183df9846e17101a447fb8efd6",
|
sha256Hash: "5a2fc03f0c83b090bbfa40604b0988446c7636183df9846e17101a447fb8efd6",
|
||||||
certStartOff: 57936,
|
certStartOff: 57045,
|
||||||
certLength: 1491,
|
certLength: 1491,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
|
cn: "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
|
||||||
sha256Hash: "96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6",
|
sha256Hash: "96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6",
|
||||||
certStartOff: 59427,
|
certStartOff: 58536,
|
||||||
certLength: 1391,
|
certLength: 1391,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=ISRG Root X2,O=Internet Security Research Group,C=US",
|
cn: "CN=ISRG Root X2,O=Internet Security Research Group,C=US",
|
||||||
sha256Hash: "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470",
|
sha256Hash: "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470",
|
||||||
certStartOff: 60818,
|
certStartOff: 59927,
|
||||||
certLength: 543,
|
certLength: 543,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=IdenTrust Commercial Root CA 1,O=IdenTrust,C=US",
|
cn: "CN=IdenTrust Commercial Root CA 1,O=IdenTrust,C=US",
|
||||||
sha256Hash: "5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae",
|
sha256Hash: "5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae",
|
||||||
certStartOff: 61361,
|
certStartOff: 60470,
|
||||||
certLength: 1380,
|
certLength: 1380,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=IdenTrust Public Sector Root CA 1,O=IdenTrust,C=US",
|
cn: "CN=IdenTrust Public Sector Root CA 1,O=IdenTrust,C=US",
|
||||||
sha256Hash: "30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f",
|
sha256Hash: "30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f",
|
||||||
certStartOff: 62741,
|
certStartOff: 61850,
|
||||||
certLength: 1386,
|
certLength: 1386,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
|
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
|
||||||
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
|
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
|
||||||
certStartOff: 64127,
|
certStartOff: 63236,
|
||||||
certLength: 1525,
|
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",
|
cn: "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU,1.2.840.113549.1.9.1=info@e-szigno.hu",
|
||||||
sha256Hash: "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
|
sha256Hash: "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
|
||||||
certStartOff: 65652,
|
certStartOff: 64761,
|
||||||
certLength: 1038,
|
certLength: 1038,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Microsoft ECC Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
cn: "CN=Microsoft ECC Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||||
sha256Hash: "358df39d764af9e1b766e9c972df352ee15cfac227af6ad1d70e8e4a6edcba02",
|
sha256Hash: "358df39d764af9e1b766e9c972df352ee15cfac227af6ad1d70e8e4a6edcba02",
|
||||||
certStartOff: 66690,
|
certStartOff: 65799,
|
||||||
certLength: 605,
|
certLength: 605,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Microsoft RSA Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
cn: "CN=Microsoft RSA Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||||
sha256Hash: "c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0",
|
sha256Hash: "c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0",
|
||||||
certStartOff: 67295,
|
certStartOff: 66404,
|
||||||
certLength: 1452,
|
certLength: 1452,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=NAVER Global Root Certification Authority,O=NAVER BUSINESS PLATFORM Corp.,C=KR",
|
cn: "CN=NAVER Global Root Certification Authority,O=NAVER BUSINESS PLATFORM Corp.,C=KR",
|
||||||
sha256Hash: "88f438dcf8ffd1fa8f429115ffe5f82ae1e06e0c70c375faad717b34a49e7265",
|
sha256Hash: "88f438dcf8ffd1fa8f429115ffe5f82ae1e06e0c70c375faad717b34a49e7265",
|
||||||
certStartOff: 68747,
|
certStartOff: 67856,
|
||||||
certLength: 1446,
|
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",
|
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",
|
sha256Hash: "6c61dac3a2def031506be036d2a6fe401994fbd13df9c8d466599274c446ec98",
|
||||||
certStartOff: 70193,
|
certStartOff: 69302,
|
||||||
certLength: 1049,
|
certLength: 1049,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=OISTE Server Root ECC G1,O=OISTE Foundation,C=CH",
|
cn: "CN=OISTE Server Root ECC G1,O=OISTE Foundation,C=CH",
|
||||||
sha256Hash: "eec997c0c30f216f7e3b8b307d2bae42412d753fc8219dafd1520b2572850f49",
|
sha256Hash: "eec997c0c30f216f7e3b8b307d2bae42412d753fc8219dafd1520b2572850f49",
|
||||||
certStartOff: 71242,
|
certStartOff: 70351,
|
||||||
certLength: 569,
|
certLength: 569,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH",
|
cn: "CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH",
|
||||||
sha256Hash: "9ae36232a5189ffddb353dfd26520c015395d22777dac59db57b98c089a651e6",
|
sha256Hash: "9ae36232a5189ffddb353dfd26520c015395d22777dac59db57b98c089a651e6",
|
||||||
certStartOff: 71811,
|
certStartOff: 70920,
|
||||||
certLength: 1415,
|
certLength: 1415,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
cn: "CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||||
sha256Hash: "6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6",
|
sha256Hash: "6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6",
|
||||||
certStartOff: 73226,
|
certStartOff: 72335,
|
||||||
certLength: 953,
|
certLength: 953,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=OISTE WISeKey Global Root GC CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
cn: "CN=OISTE WISeKey Global Root GC CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||||
sha256Hash: "8560f91c3624daba9570b5fea0dbe36ff11a8323be9486854fb3f34a5571198d",
|
sha256Hash: "8560f91c3624daba9570b5fea0dbe36ff11a8323be9486854fb3f34a5571198d",
|
||||||
certStartOff: 74179,
|
certStartOff: 73288,
|
||||||
certLength: 621,
|
certLength: 621,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=QuoVadis Root CA 1 G3,O=QuoVadis Limited,C=BM",
|
cn: "CN=QuoVadis Root CA 1 G3,O=QuoVadis Limited,C=BM",
|
||||||
sha256Hash: "8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74",
|
sha256Hash: "8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74",
|
||||||
certStartOff: 74800,
|
certStartOff: 73909,
|
||||||
certLength: 1380,
|
certLength: 1380,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM",
|
cn: "CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM",
|
||||||
sha256Hash: "8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840",
|
sha256Hash: "8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840",
|
||||||
certStartOff: 76180,
|
certStartOff: 75289,
|
||||||
certLength: 1380,
|
certLength: 1380,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=QuoVadis Root CA 3 G3,O=QuoVadis Limited,C=BM",
|
cn: "CN=QuoVadis Root CA 3 G3,O=QuoVadis Limited,C=BM",
|
||||||
sha256Hash: "88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46",
|
sha256Hash: "88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46",
|
||||||
certStartOff: 77560,
|
certStartOff: 76669,
|
||||||
certLength: 1380,
|
certLength: 1380,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
cn: "CN=SECOM TLS ECC Root CA 2024,O=SECOM Trust Systems Co.\\, Ltd.,C=JP",
|
||||||
|
sha256Hash: "6ab2ab75f51cb4f4f0156203fbf6f646232f514be059f62833308b82b4d72db1",
|
||||||
|
certStartOff: 78049,
|
||||||
|
certLength: 592,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cn: "CN=SECOM TLS RSA Root CA 2024,O=SECOM Trust Systems Co.\\, Ltd.,C=JP",
|
||||||
|
sha256Hash: "1435f225c5d252d7a21948cc3ce62aecfa88001e3dd72d1cc3555100eb372f93",
|
||||||
|
certStartOff: 78641,
|
||||||
|
certLength: 1438,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
cn: "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||||
sha256Hash: "22a2c1f7bded704cc1e701b5f408c310880fe956b5de2a4a44f99c873a25a7c8",
|
sha256Hash: "22a2c1f7bded704cc1e701b5f408c310880fe956b5de2a4a44f99c873a25a7c8",
|
||||||
certStartOff: 78940,
|
certStartOff: 80079,
|
||||||
certLength: 664,
|
certLength: 664,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
cn: "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||||
sha256Hash: "2e7bf16cc22485a7bbe2aa8696750761b0ae39be3b2fe9d0cc6d4ef73491425c",
|
sha256Hash: "2e7bf16cc22485a7bbe2aa8696750761b0ae39be3b2fe9d0cc6d4ef73491425c",
|
||||||
certStartOff: 79604,
|
certStartOff: 80743,
|
||||||
certLength: 1519,
|
certLength: 1519,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
cn: "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||||
sha256Hash: "3417bb06cc6007da1b961c920b8ab4ce3fad820e4aa30b9acbc4a74ebdcebc65",
|
sha256Hash: "3417bb06cc6007da1b961c920b8ab4ce3fad820e4aa30b9acbc4a74ebdcebc65",
|
||||||
certStartOff: 81123,
|
certStartOff: 82262,
|
||||||
certLength: 657,
|
certLength: 657,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com Root Certification Authority RSA,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
cn: "CN=SSL.com Root Certification Authority RSA,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||||
sha256Hash: "85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69",
|
sha256Hash: "85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69",
|
||||||
certStartOff: 81780,
|
certStartOff: 82919,
|
||||||
certLength: 1505,
|
certLength: 1505,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US",
|
cn: "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US",
|
||||||
sha256Hash: "c32ffd9f46f936d16c3673990959434b9ad60aafbb9e7cf33654f144cc1ba143",
|
sha256Hash: "c32ffd9f46f936d16c3673990959434b9ad60aafbb9e7cf33654f144cc1ba143",
|
||||||
certStartOff: 83285,
|
certStartOff: 84424,
|
||||||
certLength: 574,
|
certLength: 574,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US",
|
cn: "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US",
|
||||||
sha256Hash: "8faf7d2e2cb4709bb8e0b33666bf75a5dd45b5de480f8ea8d4bfe6bebc17f2ed",
|
sha256Hash: "8faf7d2e2cb4709bb8e0b33666bf75a5dd45b5de480f8ea8d4bfe6bebc17f2ed",
|
||||||
certStartOff: 83859,
|
certStartOff: 84998,
|
||||||
certLength: 1421,
|
certLength: 1421,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SZAFIR ROOT CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL",
|
cn: "CN=SZAFIR ROOT CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL",
|
||||||
sha256Hash: "a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe",
|
sha256Hash: "a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe",
|
||||||
certStartOff: 85280,
|
certStartOff: 86419,
|
||||||
certLength: 886,
|
certLength: 886,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB",
|
cn: "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB",
|
||||||
sha256Hash: "c90f26f0fb1b4018b22227519b5ca2b53e2ca5b3be5cf18efe1bef47380c5383",
|
sha256Hash: "c90f26f0fb1b4018b22227519b5ca2b53e2ca5b3be5cf18efe1bef47380c5383",
|
||||||
certStartOff: 86166,
|
certStartOff: 87305,
|
||||||
certLength: 574,
|
certLength: 574,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB",
|
cn: "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB",
|
||||||
sha256Hash: "7bb647a62aeeac88bf257aa522d01ffea395e0ab45c73f93f65654ec38f25a06",
|
sha256Hash: "7bb647a62aeeac88bf257aa522d01ffea395e0ab45c73f93f65654ec38f25a06",
|
||||||
certStartOff: 86740,
|
certStartOff: 87879,
|
||||||
certLength: 1422,
|
certLength: 1422,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SecureSign Root CA14,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
cn: "CN=SecureSign Root CA14,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||||
sha256Hash: "4b009c1034494f9ab56bba3ba1d62731fc4d20d8955adcec10a925607261e338",
|
sha256Hash: "4b009c1034494f9ab56bba3ba1d62731fc4d20d8955adcec10a925607261e338",
|
||||||
certStartOff: 88162,
|
certStartOff: 89301,
|
||||||
certLength: 1398,
|
certLength: 1398,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SecureSign Root CA15,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
cn: "CN=SecureSign Root CA15,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||||
sha256Hash: "e778f0f095fe843729cd1a0082179e5314a9c291442805e1fb1d8fb6b8886c3a",
|
sha256Hash: "e778f0f095fe843729cd1a0082179e5314a9c291442805e1fb1d8fb6b8886c3a",
|
||||||
certStartOff: 89560,
|
certStartOff: 90699,
|
||||||
certLength: 551,
|
certLength: 551,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Security Communication ECC RootCA1,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
cn: "CN=Security Communication ECC RootCA1,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||||
sha256Hash: "e74fbda55bd564c473a36b441aa799c8a68e077440e8288b9fa1e50e4bbaca11",
|
sha256Hash: "e74fbda55bd564c473a36b441aa799c8a68e077440e8288b9fa1e50e4bbaca11",
|
||||||
certStartOff: 90111,
|
certStartOff: 91250,
|
||||||
certLength: 572,
|
certLength: 572,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
cn: "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||||
sha256Hash: "2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5",
|
sha256Hash: "2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5",
|
||||||
certStartOff: 90683,
|
certStartOff: 91822,
|
||||||
certLength: 993,
|
certLength: 993,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
cn: "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||||
sha256Hash: "568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
|
sha256Hash: "568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
|
||||||
certStartOff: 91676,
|
certStartOff: 92815,
|
||||||
certLength: 1011,
|
certLength: 1011,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH",
|
cn: "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH",
|
||||||
sha256Hash: "193144f431e0fddb740717d4de926a571133884b4360d30e272913cbe660ce41",
|
sha256Hash: "193144f431e0fddb740717d4de926a571133884b4360d30e272913cbe660ce41",
|
||||||
certStartOff: 92687,
|
certStartOff: 93826,
|
||||||
certLength: 1431,
|
certLength: 1431,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=T-TeleSec GlobalRoot Class 2,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
cn: "CN=T-TeleSec GlobalRoot Class 2,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||||
sha256Hash: "91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552",
|
sha256Hash: "91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552",
|
||||||
certStartOff: 94118,
|
certStartOff: 95257,
|
||||||
certLength: 967,
|
certLength: 967,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
cn: "CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||||
sha256Hash: "fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd",
|
sha256Hash: "fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd",
|
||||||
certStartOff: 95085,
|
certStartOff: 96224,
|
||||||
certLength: 967,
|
certLength: 967,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
cn: "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||||
sha256Hash: "3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558",
|
sha256Hash: "3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558",
|
||||||
certStartOff: 96052,
|
certStartOff: 97191,
|
||||||
certLength: 1425,
|
certLength: 1425,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
cn: "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||||
sha256Hash: "59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b",
|
sha256Hash: "59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b",
|
||||||
certStartOff: 97477,
|
certStartOff: 98616,
|
||||||
certLength: 1349,
|
certLength: 1349,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW",
|
cn: "CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||||
sha256Hash: "bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44",
|
sha256Hash: "bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44",
|
||||||
certStartOff: 98826,
|
certStartOff: 99965,
|
||||||
certLength: 895,
|
certLength: 895,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Telekom Security TLS ECC Root 2020,O=Deutsche Telekom Security GmbH,C=DE",
|
cn: "CN=Telekom Security TLS ECC Root 2020,O=Deutsche Telekom Security GmbH,C=DE",
|
||||||
sha256Hash: "578af4ded0853f4e5998db4aeaf9cbea8d945f60b620a38d1a3c13b2bc7ba8e1",
|
sha256Hash: "578af4ded0853f4e5998db4aeaf9cbea8d945f60b620a38d1a3c13b2bc7ba8e1",
|
||||||
certStartOff: 99721,
|
certStartOff: 100860,
|
||||||
certLength: 582,
|
certLength: 582,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Telekom Security TLS RSA Root 2023,O=Deutsche Telekom Security GmbH,C=DE",
|
cn: "CN=Telekom Security TLS RSA Root 2023,O=Deutsche Telekom Security GmbH,C=DE",
|
||||||
sha256Hash: "efc65cadbb59adb6efe84da22311b35624b71b3b1ea0da8b6655174ec8978646",
|
sha256Hash: "efc65cadbb59adb6efe84da22311b35624b71b3b1ea0da8b6655174ec8978646",
|
||||||
certStartOff: 100303,
|
certStartOff: 101442,
|
||||||
certLength: 1463,
|
certLength: 1463,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
cn: "CN=Telia EC TLS Root CA v3,O=Telia Company AB,C=SE",
|
||||||
|
sha256Hash: "098e08a91dbbf77478b96cceb89b1413a5da37b7c862606a955deb07179f4326",
|
||||||
|
certStartOff: 102905,
|
||||||
|
certLength: 566,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cn: "CN=Telia RSA TLS Root CA v3,O=Telia Company AB,C=SE",
|
||||||
|
sha256Hash: "d13db1294c45ebc6fc86c6bbf69fa29bdfe692dff7c713c243c7a956c6a2284c",
|
||||||
|
certStartOff: 103471,
|
||||||
|
certLength: 1414,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI",
|
cn: "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI",
|
||||||
sha256Hash: "242b69742fcb1e5b2abf98898b94572187544e5b4d9911786573621f6a74b82c",
|
sha256Hash: "242b69742fcb1e5b2abf98898b94572187544e5b4d9911786573621f6a74b82c",
|
||||||
certStartOff: 101766,
|
certStartOff: 104885,
|
||||||
certLength: 1400,
|
certLength: 1400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TrustAsia Global Root CA G3,O=TrustAsia Technologies\\, Inc.,C=CN",
|
cn: "CN=TrustAsia Global Root CA G3,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||||
sha256Hash: "e0d3226aeb1163c2e48ff9be3b50b4c6431be7bb1eacc5c36b5d5ec509039a08",
|
sha256Hash: "e0d3226aeb1163c2e48ff9be3b50b4c6431be7bb1eacc5c36b5d5ec509039a08",
|
||||||
certStartOff: 103166,
|
certStartOff: 106285,
|
||||||
certLength: 1449,
|
certLength: 1449,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TrustAsia Global Root CA G4,O=TrustAsia Technologies\\, Inc.,C=CN",
|
cn: "CN=TrustAsia Global Root CA G4,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||||
sha256Hash: "be4b56cb5056c0136a526df444508daa36a0b54f42e4ac38f72af470e479654c",
|
sha256Hash: "be4b56cb5056c0136a526df444508daa36a0b54f42e4ac38f72af470e479654c",
|
||||||
certStartOff: 104615,
|
certStartOff: 107734,
|
||||||
certLength: 601,
|
certLength: 601,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TrustAsia TLS ECC Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
cn: "CN=TrustAsia TLS ECC Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||||
sha256Hash: "c0076b9ef0531fb1a656d67c4ebe97cd5dbaa41ef44598acc2489878c92d8711",
|
sha256Hash: "c0076b9ef0531fb1a656d67c4ebe97cd5dbaa41ef44598acc2489878c92d8711",
|
||||||
certStartOff: 105216,
|
certStartOff: 108335,
|
||||||
certLength: 565,
|
certLength: 565,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TrustAsia TLS RSA Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
cn: "CN=TrustAsia TLS RSA Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||||
sha256Hash: "06c08d7dafd876971eb1124fe67f847ec0c7a158d3ea53cbe940e2ea9791f4c3",
|
sha256Hash: "06c08d7dafd876971eb1124fe67f847ec0c7a158d3ea53cbe940e2ea9791f4c3",
|
||||||
certStartOff: 105781,
|
certStartOff: 108900,
|
||||||
certLength: 1412,
|
certLength: 1412,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN",
|
cn: "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN",
|
||||||
sha256Hash: "2e44102ab58cb85419451c8e19d9acf3662cafbc614b6a53960a30f7d0e2eb41",
|
sha256Hash: "2e44102ab58cb85419451c8e19d9acf3662cafbc614b6a53960a30f7d0e2eb41",
|
||||||
certStartOff: 107193,
|
certStartOff: 110312,
|
||||||
certLength: 1463,
|
certLength: 1463,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=UCA Extended Validation Root,O=UniTrust,C=CN",
|
cn: "CN=UCA Extended Validation Root,O=UniTrust,C=CN",
|
||||||
sha256Hash: "d43af9b35473755c9684fc06d7d8cb70ee5c28e773fb294eb41ee71722924d24",
|
sha256Hash: "d43af9b35473755c9684fc06d7d8cb70ee5c28e773fb294eb41ee71722924d24",
|
||||||
certStartOff: 108656,
|
certStartOff: 111775,
|
||||||
certLength: 1374,
|
certLength: 1374,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=UCA Global G2 Root,O=UniTrust,C=CN",
|
cn: "CN=UCA Global G2 Root,O=UniTrust,C=CN",
|
||||||
sha256Hash: "9bea11c976fe014764c1be56a6f914b5a560317abd9988393382e5161aa0493c",
|
sha256Hash: "9bea11c976fe014764c1be56a6f914b5a560317abd9988393382e5161aa0493c",
|
||||||
certStartOff: 110030,
|
certStartOff: 113149,
|
||||||
certLength: 1354,
|
certLength: 1354,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
cn: "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||||
sha256Hash: "4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a",
|
sha256Hash: "4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a",
|
||||||
certStartOff: 111384,
|
certStartOff: 114503,
|
||||||
certLength: 659,
|
certLength: 659,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
cn: "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||||
sha256Hash: "e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2",
|
sha256Hash: "e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2",
|
||||||
certStartOff: 112043,
|
certStartOff: 115162,
|
||||||
certLength: 1506,
|
certLength: 1506,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
|
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=VATHU-23584497",
|
||||||
sha256Hash: "beb00b30839b9bc32c32e4447905950641f26421b15ed089198b518ae2ea1b99",
|
sha256Hash: "beb00b30839b9bc32c32e4447905950641f26421b15ed089198b518ae2ea1b99",
|
||||||
certStartOff: 113549,
|
certStartOff: 116668,
|
||||||
certLength: 580,
|
certLength: 580,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=e-Szigno TLS Root CA 2023,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
|
cn: "CN=e-Szigno TLS Root CA 2023,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=VATHU-23584497",
|
||||||
sha256Hash: "b49141502d00663d740f2e7ec340c52800962666121a36d09cf7dd2b90384fb4",
|
sha256Hash: "b49141502d00663d740f2e7ec340c52800962666121a36d09cf7dd2b90384fb4",
|
||||||
certStartOff: 114129,
|
certStartOff: 117248,
|
||||||
certLength: 723,
|
certLength: 723,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=emSign ECC Root CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US",
|
cn: "CN=emSign ECC Root CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||||
sha256Hash: "bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3",
|
sha256Hash: "bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3",
|
||||||
certStartOff: 114852,
|
certStartOff: 117971,
|
||||||
certLength: 559,
|
certLength: 559,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=emSign ECC Root CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
cn: "CN=emSign ECC Root CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||||
sha256Hash: "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
|
sha256Hash: "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
|
||||||
certStartOff: 115411,
|
certStartOff: 118530,
|
||||||
certLength: 594,
|
certLength: 594,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=emSign Root CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US",
|
cn: "CN=emSign Root CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||||
sha256Hash: "125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f",
|
sha256Hash: "125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f",
|
||||||
certStartOff: 116005,
|
certStartOff: 119124,
|
||||||
certLength: 887,
|
certLength: 887,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=emSign Root CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
cn: "CN=emSign Root CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||||
sha256Hash: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367",
|
sha256Hash: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367",
|
||||||
certStartOff: 116892,
|
certStartOff: 120011,
|
||||||
certLength: 920,
|
certLength: 920,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=vTrus ECC Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
cn: "CN=vTrus ECC Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||||
sha256Hash: "30fbba2c32238e2a98547af97931e550428b9b3f1c8eeb6633dcfa86c5b27dd3",
|
sha256Hash: "30fbba2c32238e2a98547af97931e550428b9b3f1c8eeb6633dcfa86c5b27dd3",
|
||||||
certStartOff: 117812,
|
certStartOff: 120931,
|
||||||
certLength: 531,
|
certLength: 531,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "CN=vTrus Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
cn: "CN=vTrus Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||||
sha256Hash: "8a71de6559336f426c26e53880d00d88a18da4c6a91f0dcb6194e206c5c96387",
|
sha256Hash: "8a71de6559336f426c26e53880d00d88a18da4c6a91f0dcb6194e206c5c96387",
|
||||||
certStartOff: 118343,
|
certStartOff: 121462,
|
||||||
certLength: 1370,
|
certLength: 1370,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES",
|
cn: "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES",
|
||||||
sha256Hash: "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
|
sha256Hash: "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
|
||||||
certStartOff: 119713,
|
certStartOff: 122832,
|
||||||
certLength: 1415,
|
certLength: 1415,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "OU=Security Communication RootCA2,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
cn: "OU=Security Communication RootCA2,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||||
sha256Hash: "513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6",
|
sha256Hash: "513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6",
|
||||||
certStartOff: 121128,
|
certStartOff: 124247,
|
||||||
certLength: 891,
|
certLength: 891,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cn: "OU=certSIGN ROOT CA G2,O=CERTSIGN SA,C=RO",
|
cn: "OU=certSIGN ROOT CA G2,O=CERTSIGN SA,C=RO",
|
||||||
sha256Hash: "657cfe2fa73faa38462571f332a2363a46fce7020951710702cdfbb6eeda3305",
|
sha256Hash: "657cfe2fa73faa38462571f332a2363a46fce7020951710702cdfbb6eeda3305",
|
||||||
certStartOff: 122019,
|
certStartOff: 125138,
|
||||||
certLength: 1355,
|
certLength: 1355,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
cn: "OU=ePKI Root Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
|
||||||
sha256Hash: "c0a6f4dc63a24bfdcf54ef2a6a082a0a72de35803e2ff5ff527ae5d87206dfd5",
|
|
||||||
certStartOff: 123374,
|
|
||||||
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",
|
cn: "SERIALNUMBER=G63287510,CN=ANF Secure Server Root CA,OU=ANF CA Raiz,O=ANF Autoridad de Certificacion,C=ES",
|
||||||
sha256Hash: "fb8fec759169b9106b1e511644c618c51304373f6c0643088d8beffd1b997599",
|
sha256Hash: "fb8fec759169b9106b1e511644c618c51304373f6c0643088d8beffd1b997599",
|
||||||
certStartOff: 124834,
|
certStartOff: 126493,
|
||||||
certLength: 1523,
|
certLength: 1523,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2026 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.27
|
||||||
|
|
||||||
|
package bundle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/hex"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// subjectsEqual reports whether two RFC 2253 DN strings match.
|
||||||
|
//
|
||||||
|
// When testing with Go 1.27 or newer, it requires subjects to match exactly.
|
||||||
|
// When testing with Go 1.26, 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).
|
||||||
|
//
|
||||||
|
// When Go 1.26 is no longer supported,
|
||||||
|
// the Go 1.26 version can be removed and
|
||||||
|
// the Go 1.27+ version can be inlined.
|
||||||
|
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:]]+)`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2026 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.27
|
||||||
|
|
||||||
|
package bundle
|
||||||
|
|
||||||
|
// subjectsEqual reports whether two RFC 2253 DN strings match.
|
||||||
|
//
|
||||||
|
// When testing with Go 1.27 or newer, it requires subjects to match exactly.
|
||||||
|
// When testing with Go 1.26, 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).
|
||||||
|
//
|
||||||
|
// When Go 1.26 is no longer supported,
|
||||||
|
// the Go 1.26 version can be removed and
|
||||||
|
// the Go 1.27+ version can be inlined.
|
||||||
|
func subjectsEqual(a, b string) bool {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
@@ -7,9 +7,7 @@ package bundle
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"regexp"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,34 +30,3 @@ 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
|
module golang.org/x/crypto/x509roots/fallback
|
||||||
|
|
||||||
go 1.25.0
|
go 1.26.0
|
||||||
|
|||||||
Reference in New Issue
Block a user