Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1c0d99298 | ||
|
|
3c7c86938f | ||
|
|
533fb3f7e4 | ||
|
|
abbc44d451 | ||
|
|
e052873987 | ||
|
|
b61cf853a8 | ||
|
|
9c2cd33e8d | ||
|
|
890731877d | ||
|
|
ffd87b4878 | ||
|
|
4e7a7384ec | ||
|
|
b25012b37b | ||
|
|
6c195c8a97 | ||
|
|
f717e29698 | ||
|
|
e7c36ccb47 | ||
|
|
0fb843a472 | ||
|
|
e3d1254f1e | ||
|
|
a1ce0fee12 | ||
|
|
a749d17980 | ||
|
|
7ee59707ff | ||
|
|
44decbfe70 | ||
|
|
b8a14a8d65 | ||
|
|
9d9d507896 | ||
|
|
fd0b90d21f | ||
|
|
b9e53593a6 | ||
|
|
cc0e4fc1d4 | ||
|
|
a8e9237a21 | ||
|
|
03ca0dcccb | ||
|
|
8400f4a938 | ||
|
|
81c6cb34a8 | ||
|
|
982eaa62df | ||
|
|
159944f128 | ||
|
|
a408498e55 | ||
|
|
cab0f71854 | ||
|
|
2f26647a79 | ||
|
|
e08b06753d | ||
|
|
7d0074ccc6 | ||
|
|
506e022208 | ||
|
|
7dacc380ba | ||
|
|
19acf81bd7 | ||
|
|
3a1c6b4b61 | ||
|
|
f4602e4040 | ||
|
|
4e0068c009 | ||
|
|
e79546e28b | ||
|
|
f91f7a7c31 | ||
|
|
2df4153a03 | ||
|
|
bcf6a849ef | ||
|
|
b4f2b62076 | ||
|
|
79ec3a51fc | ||
|
|
122a78f140 | ||
|
|
c0531f9c34 | ||
|
|
0997000b45 | ||
|
|
017a1aaa2d | ||
|
|
cf29fa96f8 | ||
|
|
0b7aa0cfb0 | ||
|
|
1faea2975c | ||
|
|
627cb894b6 | ||
|
|
dca4914afe | ||
|
|
1336e21bd6 | ||
|
|
2beaa59a3c | ||
|
|
66c3d8ce71 | ||
|
|
ddb4e80c6a | ||
|
|
f4d47b0db5 | ||
|
|
96dc232fbd | ||
|
|
8c9ba31836 | ||
|
|
559e062ce8 |
+1
-1
@@ -690,7 +690,7 @@ func (c *Client) addNonce(h http.Header) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||||
r, err := http.NewRequest("HEAD", url, nil)
|
r, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ type Manager struct {
|
|||||||
// RenewBefore optionally specifies how early certificates should
|
// RenewBefore optionally specifies how early certificates should
|
||||||
// be renewed before they expire.
|
// be renewed before they expire.
|
||||||
//
|
//
|
||||||
// If zero, they're renewed 30 days before expiration.
|
// If zero, they're renewed at the lesser of 30 days or
|
||||||
|
// 1/3 of the certificate lifetime.
|
||||||
RenewBefore time.Duration
|
RenewBefore time.Duration
|
||||||
|
|
||||||
// Client is used to perform low-level operations, such as account registration
|
// Client is used to perform low-level operations, such as account registration
|
||||||
@@ -247,10 +248,6 @@ func (m *Manager) TLSConfig() *tls.Config {
|
|||||||
// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
|
// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
|
||||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
|
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
|
||||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if m.Prompt == nil {
|
|
||||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := hello.ServerName
|
name := hello.ServerName
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, errors.New("acme/autocert: missing server name")
|
return nil, errors.New("acme/autocert: missing server name")
|
||||||
@@ -464,7 +461,7 @@ func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error
|
|||||||
leaf: cert.Leaf,
|
leaf: cert.Leaf,
|
||||||
}
|
}
|
||||||
m.state[ck] = s
|
m.state[ck] = s
|
||||||
m.startRenew(ck, s.key, s.leaf.NotAfter)
|
m.startRenew(ck, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,7 +607,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
|
|||||||
}
|
}
|
||||||
state.cert = der
|
state.cert = der
|
||||||
state.leaf = leaf
|
state.leaf = leaf
|
||||||
m.startRenew(ck, state.key, state.leaf.NotAfter)
|
m.startRenew(ck, state.key, state.leaf.NotBefore, state.leaf.NotAfter)
|
||||||
return state.tlscert()
|
return state.tlscert()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,7 +905,7 @@ func httpTokenCacheKey(tokenPath string) string {
|
|||||||
//
|
//
|
||||||
// The key argument is a certificate private key.
|
// The key argument is a certificate private key.
|
||||||
// The exp argument is the cert expiration time (NotAfter).
|
// The exp argument is the cert expiration time (NotAfter).
|
||||||
func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
|
func (m *Manager) startRenew(ck certKey, key crypto.Signer, notBefore, notAfter time.Time) {
|
||||||
m.renewalMu.Lock()
|
m.renewalMu.Lock()
|
||||||
defer m.renewalMu.Unlock()
|
defer m.renewalMu.Unlock()
|
||||||
if m.renewal[ck] != nil {
|
if m.renewal[ck] != nil {
|
||||||
@@ -920,7 +917,7 @@ func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
|
|||||||
}
|
}
|
||||||
dr := &domainRenewal{m: m, ck: ck, key: key}
|
dr := &domainRenewal{m: m, ck: ck, key: key}
|
||||||
m.renewal[ck] = dr
|
m.renewal[ck] = dr
|
||||||
dr.start(exp)
|
dr.start(notBefore, notAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopRenew stops all currently running cert renewal timers.
|
// stopRenew stops all currently running cert renewal timers.
|
||||||
@@ -1028,13 +1025,6 @@ func (m *Manager) hostPolicy() HostPolicy {
|
|||||||
return defaultHostPolicy
|
return defaultHostPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) renewBefore() time.Duration {
|
|
||||||
if m.RenewBefore > renewJitter {
|
|
||||||
return m.RenewBefore
|
|
||||||
}
|
|
||||||
return 720 * time.Hour // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) now() time.Time {
|
func (m *Manager) now() time.Time {
|
||||||
if m.nowFunc != nil {
|
if m.nowFunc != nil {
|
||||||
return m.nowFunc()
|
return m.nowFunc()
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ func TestGetCertificate(t *testing.T) {
|
|||||||
prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
|
prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
|
||||||
man.Prompt = nil
|
man.Prompt = nil
|
||||||
},
|
},
|
||||||
expectError: "Manager.Prompt not set",
|
expectError: "missing Manager.Prompt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "trailingDot",
|
name: "trailingDot",
|
||||||
@@ -561,9 +561,9 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
|||||||
{"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
|
{"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
|
||||||
{"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
|
{"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
|
||||||
{"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
|
{"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
|
||||||
{"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
|
{"GET", "http://[2602:d1:abcd::c60a]:1234", 302, "https://[2602:d1:abcd::c60a]:443/"},
|
||||||
{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"},
|
{"GET", "http://[2602:d1:abcd::c60a]", 302, "https://[2602:d1:abcd::c60a]/"},
|
||||||
{"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"},
|
{"GET", "http://[2602:d1:abcd::c60a]/foo?a=b", 302, "https://[2602:d1:abcd::c60a]/foo?a=b"},
|
||||||
{"HEAD", "http://example.org", 302, "https://example.org/"},
|
{"HEAD", "http://example.org", 302, "https://example.org/"},
|
||||||
{"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
|
{"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
|
||||||
{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func (ca *CAServer) URL() string {
|
|||||||
return ca.url
|
return ca.url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roots returns a pool cointaining the CA root.
|
// Roots returns a pool containing the CA root.
|
||||||
func (ca *CAServer) Roots() *x509.CertPool {
|
func (ca *CAServer) Roots() *x509.CertPool {
|
||||||
if ca.url == "" {
|
if ca.url == "" {
|
||||||
panic("Roots called before Start")
|
panic("Roots called before Start")
|
||||||
@@ -239,7 +239,8 @@ type discovery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type discoveryMeta struct {
|
type discoveryMeta struct {
|
||||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
Terms string `json:"termsOfService,omitempty"`
|
||||||
|
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type challenge struct {
|
type challenge struct {
|
||||||
@@ -281,6 +282,7 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
NewAccount: ca.serverURL("/new-account"),
|
NewAccount: ca.serverURL("/new-account"),
|
||||||
NewOrder: ca.serverURL("/new-order"),
|
NewOrder: ca.serverURL("/new-order"),
|
||||||
Meta: discoveryMeta{
|
Meta: discoveryMeta{
|
||||||
|
Terms: ca.serverURL("/terms"),
|
||||||
ExternalAccountRequired: ca.eabRequired,
|
ExternalAccountRequired: ca.eabRequired,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-17
@@ -11,9 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
|
||||||
const renewJitter = time.Hour
|
|
||||||
|
|
||||||
// domainRenewal tracks the state used by the periodic timers
|
// domainRenewal tracks the state used by the periodic timers
|
||||||
// renewing a single domain's cert.
|
// renewing a single domain's cert.
|
||||||
type domainRenewal struct {
|
type domainRenewal struct {
|
||||||
@@ -30,13 +27,13 @@ type domainRenewal struct {
|
|||||||
// defined by the certificate expiration time exp.
|
// defined by the certificate expiration time exp.
|
||||||
//
|
//
|
||||||
// If the timer is already started, calling start is a noop.
|
// If the timer is already started, calling start is a noop.
|
||||||
func (dr *domainRenewal) start(exp time.Time) {
|
func (dr *domainRenewal) start(notBefore, notAfter time.Time) {
|
||||||
dr.timerMu.Lock()
|
dr.timerMu.Lock()
|
||||||
defer dr.timerMu.Unlock()
|
defer dr.timerMu.Unlock()
|
||||||
if dr.timer != nil {
|
if dr.timer != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
dr.timer = time.AfterFunc(dr.next(notBefore, notAfter), dr.renew)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop stops the cert renewal timer and waits for any in-flight calls to renew
|
// stop stops the cert renewal timer and waits for any in-flight calls to renew
|
||||||
@@ -79,7 +76,7 @@ func (dr *domainRenewal) renew() {
|
|||||||
// TODO: rotate dr.key at some point?
|
// TODO: rotate dr.key at some point?
|
||||||
next, err := dr.do(ctx)
|
next, err := dr.do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
next = renewJitter / 2
|
next = time.Hour / 2
|
||||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||||
}
|
}
|
||||||
testDidRenewLoop(next, err)
|
testDidRenewLoop(next, err)
|
||||||
@@ -107,8 +104,8 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
|||||||
// a race is likely unavoidable in a distributed environment
|
// a race is likely unavoidable in a distributed environment
|
||||||
// but we try nonetheless
|
// but we try nonetheless
|
||||||
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||||
next := dr.next(tlscert.Leaf.NotAfter)
|
next := dr.next(tlscert.Leaf.NotBefore, tlscert.Leaf.NotAfter)
|
||||||
if next > dr.m.renewBefore()+renewJitter {
|
if next > 0 {
|
||||||
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||||
if ok {
|
if ok {
|
||||||
state := &certState{
|
state := &certState{
|
||||||
@@ -139,18 +136,23 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
dr.updateState(state)
|
dr.updateState(state)
|
||||||
return dr.next(leaf.NotAfter), nil
|
return dr.next(leaf.NotBefore, leaf.NotAfter), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
// next returns the wait time before the next renewal should start.
|
||||||
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
|
// If manager.RenewBefore is set, it uses that capped at 30 days,
|
||||||
// add a bit of randomness to renew deadline
|
// otherwise it uses a default of 1/3 of the cert lifetime.
|
||||||
n := pseudoRand.int63n(int64(renewJitter))
|
// It builds in a jitter of 10% of the renew threshold, capped at 1 hour.
|
||||||
d -= time.Duration(n)
|
func (dr *domainRenewal) next(notBefore, notAfter time.Time) time.Duration {
|
||||||
if d < 0 {
|
threshold := min(notAfter.Sub(notBefore)/3, 30*24*time.Hour)
|
||||||
return 0
|
if dr.m.RenewBefore > 0 {
|
||||||
|
threshold = min(dr.m.RenewBefore, 30*24*time.Hour)
|
||||||
}
|
}
|
||||||
return d
|
maxJitter := min(threshold/10, time.Hour)
|
||||||
|
jitter := pseudoRand.int63n(int64(maxJitter))
|
||||||
|
renewAt := notAfter.Add(-(threshold - time.Duration(jitter)))
|
||||||
|
renewWait := renewAt.Sub(dr.m.now())
|
||||||
|
return max(0, renewWait)
|
||||||
}
|
}
|
||||||
|
|
||||||
var testDidRenewLoop = func(next time.Duration, err error) {}
|
var testDidRenewLoop = func(next time.Duration, err error) {}
|
||||||
|
|||||||
@@ -17,27 +17,60 @@ import (
|
|||||||
|
|
||||||
func TestRenewalNext(t *testing.T) {
|
func TestRenewalNext(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
man := &Manager{
|
nowFn := func() time.Time { return now }
|
||||||
RenewBefore: 7 * 24 * time.Hour,
|
|
||||||
nowFunc: func() time.Time { return now },
|
|
||||||
}
|
|
||||||
defer man.stopRenew()
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
expiry time.Time
|
name string
|
||||||
min, max time.Duration
|
renewBefore time.Duration // arg to Manager
|
||||||
|
// leaf cert validity
|
||||||
|
notBefore time.Time
|
||||||
|
validFor time.Duration
|
||||||
|
// wait time
|
||||||
|
waitMin, waitMax time.Duration
|
||||||
}{
|
}{
|
||||||
{now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour},
|
{"default renewal, 1h cert, valid",
|
||||||
{now.Add(time.Hour), 0, 1},
|
0, now, time.Hour, 40 * time.Minute, 50 * time.Minute},
|
||||||
{now, 0, 1},
|
{"default renewal, 1h cert, should renew",
|
||||||
{now.Add(-time.Hour), 0, 1},
|
0, now.Add(-50 * time.Minute), time.Hour, 0, 0},
|
||||||
|
{"default renewal, 1h cert, expired",
|
||||||
|
0, now.Add(-400 * 24 * time.Hour), time.Hour, 0, 0},
|
||||||
|
{"default renewal, 6d cert, valid",
|
||||||
|
0, now, 6 * 24 * time.Hour, 4 * 24 * time.Hour, (4*24 + 1) * time.Hour},
|
||||||
|
{"default renewal, 6d cert, should renew",
|
||||||
|
0, now.Add(-5 * 24 * time.Hour), 6 * 24 * time.Hour, 0, 0},
|
||||||
|
{"default renewal, 6d cert, expired",
|
||||||
|
0, now.Add(-400 * 24 * time.Hour), 6 * 24 * time.Hour, 0, 0},
|
||||||
|
{"default renewal, 90d cert, valid",
|
||||||
|
0, now, 90 * 24 * time.Hour, 60 * 24 * time.Hour, (60*24 + 1) * time.Hour},
|
||||||
|
{"default renewal, 90d cert, should renew",
|
||||||
|
0, now.Add(-70 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
|
||||||
|
{"default renewal, 90d cert, expired",
|
||||||
|
0, now.Add(-400 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
|
||||||
|
{"default renewal, 398d cert, valid",
|
||||||
|
0, now, 398 * 24 * time.Hour, (368 * 24) * time.Hour, (368*24 + 1) * time.Hour},
|
||||||
|
{"default renewal, 398d cert, should renew",
|
||||||
|
0, now.Add(-378 * 24 * time.Hour), 398 * 24 * time.Hour, 0, 0},
|
||||||
|
{"default renewal, 398d cert, expired",
|
||||||
|
0, now.Add(-400 * 24 * time.Hour), 398 * 24 * time.Hour, 0, 0},
|
||||||
|
{"7d renewal, 90d cert, valid",
|
||||||
|
7 * 24 * time.Hour, now, 90 * 24 * time.Hour, 83 * 24 * time.Hour, (83*24 + 1) * time.Hour},
|
||||||
|
{"7d renewal, 90d cert, should not renew",
|
||||||
|
7 * 24 * time.Hour, now.Add(-70 * 24 * time.Hour), 90 * 24 * time.Hour, 13 * 24 * time.Hour, (13*24 + 1) * time.Hour},
|
||||||
|
{"7d renewal, 90d cert, should renew",
|
||||||
|
7 * 24 * time.Hour, now.Add(-85 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
|
||||||
|
{"7d renewal, 90d cert, expired",
|
||||||
|
7 * 24 * time.Hour, now.Add(-400 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
dr := &domainRenewal{m: man}
|
for _, test := range tt {
|
||||||
for i, test := range tt {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
next := dr.next(test.expiry)
|
dr := &domainRenewal{m: &Manager{RenewBefore: test.renewBefore, nowFunc: nowFn}}
|
||||||
if next < test.min || test.max < next {
|
defer dr.m.stopRenew()
|
||||||
t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max)
|
|
||||||
}
|
next := dr.next(test.notBefore, test.notBefore.Add(test.validFor))
|
||||||
|
if next < test.waitMin || next > test.waitMax {
|
||||||
|
t.Errorf("expected wait time: %v <= %v <= %v", test.waitMin, next, test.waitMax)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +180,6 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
|||||||
|
|
||||||
// set internal state to an almost expired cert
|
// set internal state to an almost expired cert
|
||||||
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
|
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -239,7 +269,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// trigger renew
|
// trigger renew
|
||||||
man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter)
|
man.startRenew(exampleCertKey, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
|
||||||
<-renewed
|
<-renewed
|
||||||
func() {
|
func() {
|
||||||
man.renewalMu.Lock()
|
man.renewalMu.Lock()
|
||||||
|
|||||||
+2
-2
@@ -128,7 +128,7 @@ func wantStatus(codes ...int) resOkay {
|
|||||||
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||||
retry := c.retryTimer()
|
retry := c.retryTimer()
|
||||||
for {
|
for {
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -757,7 +757,7 @@ func prepareBinaries(t *testing.T, pebbleDir string) string {
|
|||||||
|
|
||||||
// We don't want to build in the module cache dir, which might not be
|
// We don't want to build in the module cache dir, which might not be
|
||||||
// writable or to pollute the user's clone with binaries if pebbleLocalDir
|
// writable or to pollute the user's clone with binaries if pebbleLocalDir
|
||||||
//is used.
|
// is used.
|
||||||
binDir := t.TempDir()
|
binDir := t.TempDir()
|
||||||
|
|
||||||
build := func(cmd string) {
|
build := func(cmd string) {
|
||||||
|
|||||||
+4
-1
@@ -53,6 +53,9 @@ func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tos
|
|||||||
Contact: acct.Contact,
|
Contact: acct.Contact,
|
||||||
}
|
}
|
||||||
if c.dir.Terms != "" {
|
if c.dir.Terms != "" {
|
||||||
|
if prompt == nil {
|
||||||
|
return nil, errors.New("acme: missing Manager.Prompt to accept server's terms of service")
|
||||||
|
}
|
||||||
req.TermsAgreed = prompt(c.dir.Terms)
|
req.TermsAgreed = prompt(c.dir.Terms)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +235,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
|
|||||||
return responseOrder(res)
|
return responseOrder(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrder retrives an order identified by the given URL.
|
// GetOrder retrieves an order identified by the given URL.
|
||||||
// For orders created with AuthorizeOrder, the url value is Order.URI.
|
// For orders created with AuthorizeOrder, the url value is Order.URI.
|
||||||
//
|
//
|
||||||
// If a caller needs to poll an order until its status is final,
|
// If a caller needs to poll an order until its status is final,
|
||||||
|
|||||||
+5
-1
@@ -167,7 +167,11 @@ type OrderError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (oe *OrderError) Error() string {
|
func (oe *OrderError) Error() string {
|
||||||
return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
str := fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||||
|
if oe.Problem != nil {
|
||||||
|
str += fmt.Sprintf("; problem: %s", oe.Problem)
|
||||||
|
}
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// RateLimit reports whether err represents a rate limit error and
|
// RateLimit reports whether err represents a rate limit error and
|
||||||
|
|||||||
@@ -217,3 +217,19 @@ func TestErrorStringerWithSubproblems(t *testing.T) {
|
|||||||
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOrderErrorProblem(t *testing.T) {
|
||||||
|
oe := &OrderError{
|
||||||
|
OrderURL: "url",
|
||||||
|
Status: "invalid",
|
||||||
|
Problem: &Error{
|
||||||
|
StatusCode: 400,
|
||||||
|
ProblemType: "type",
|
||||||
|
Detail: "detail",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
want := "acme: order url status: invalid; problem: 400 type: detail"
|
||||||
|
if got := oe.Error(); got != want {
|
||||||
|
t.Errorf("oe.Error() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-7
@@ -6,7 +6,7 @@
|
|||||||
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
||||||
// be used to derive cryptographic keys from passwords.
|
// be used to derive cryptographic keys from passwords.
|
||||||
//
|
//
|
||||||
// For a detailed specification of Argon2 see [1].
|
// For a detailed specification of Argon2 see [argon2-specs.pdf].
|
||||||
//
|
//
|
||||||
// If you aren't sure which function you need, use Argon2id (IDKey) and
|
// If you aren't sure which function you need, use Argon2id (IDKey) and
|
||||||
// the parameter recommendations for your scenario.
|
// the parameter recommendations for your scenario.
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
// It uses data-independent memory access, which is preferred for password
|
// It uses data-independent memory access, which is preferred for password
|
||||||
// hashing and password-based key derivation. Argon2i requires more passes over
|
// hashing and password-based key derivation. Argon2i requires more passes over
|
||||||
// memory than Argon2id to protect from trade-off attacks. The recommended
|
// memory than Argon2id to protect from trade-off attacks. The recommended
|
||||||
// parameters (taken from [2]) for non-interactive operations are time=3 and to
|
// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to
|
||||||
// use the maximum available memory.
|
// use the maximum available memory.
|
||||||
//
|
//
|
||||||
// # Argon2id
|
// # Argon2id
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
// half of the first iteration over the memory and data-dependent memory access
|
// half of the first iteration over the memory and data-dependent memory access
|
||||||
// for the rest. Argon2id is side-channel resistant and provides better brute-
|
// for the rest. Argon2id is side-channel resistant and provides better brute-
|
||||||
// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
|
// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
|
||||||
// parameters for non-interactive operations (taken from [2]) are time=1 and to
|
// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to
|
||||||
// use the maximum available memory.
|
// use the maximum available memory.
|
||||||
//
|
//
|
||||||
// [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
// [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||||
// [2] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3
|
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||||
package argon2
|
package argon2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -59,7 +59,7 @@ const (
|
|||||||
//
|
//
|
||||||
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
||||||
//
|
//
|
||||||
// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number.
|
// [RFC 9106 Section 7.3] recommends time=3, and memory=32*1024 as a sensible number.
|
||||||
// If using that amount of memory (32 MB) is not possible in some contexts then
|
// If using that amount of memory (32 MB) is not possible in some contexts then
|
||||||
// the time parameter can be increased to compensate.
|
// the time parameter can be increased to compensate.
|
||||||
//
|
//
|
||||||
@@ -69,6 +69,8 @@ const (
|
|||||||
// adjusted to the number of available CPUs. The cost parameters should be
|
// adjusted to the number of available CPUs. The cost parameters should be
|
||||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||||
// good random salt.
|
// good random salt.
|
||||||
|
//
|
||||||
|
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||||
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||||
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
|
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,7 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
|
|||||||
//
|
//
|
||||||
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
||||||
//
|
//
|
||||||
// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number.
|
// [RFC 9106 Section 7.3] recommends time=1, and memory=64*1024 as a sensible number.
|
||||||
// If using that amount of memory (64 MB) is not possible in some contexts then
|
// If using that amount of memory (64 MB) is not possible in some contexts then
|
||||||
// the time parameter can be increased to compensate.
|
// the time parameter can be increased to compensate.
|
||||||
//
|
//
|
||||||
@@ -93,6 +95,8 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
|
|||||||
// adjusted to the numbers of available CPUs. The cost parameters should be
|
// adjusted to the numbers of available CPUs. The cost parameters should be
|
||||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||||
// good random salt.
|
// good random salt.
|
||||||
|
//
|
||||||
|
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||||
func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||||
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
|
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ hash.XOF = (*xof)(nil)
|
||||||
|
|
||||||
func fromHex(s string) []byte {
|
func fromHex(s string) []byte {
|
||||||
b, err := hex.DecodeString(s)
|
b, err := hex.DecodeString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ loop:
|
|||||||
MOVD $NUM_ROUNDS, R21
|
MOVD $NUM_ROUNDS, R21
|
||||||
VLD1 (R11), [V30.S4, V31.S4]
|
VLD1 (R11), [V30.S4, V31.S4]
|
||||||
|
|
||||||
// load contants
|
// load constants
|
||||||
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
|
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
|
||||||
WORD $0x4D60E940
|
WORD $0x4D60E940
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,9 @@ type chacha20poly1305 struct {
|
|||||||
|
|
||||||
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||||
func New(key []byte) (cipher.AEAD, error) {
|
func New(key []byte) (cipher.AEAD, error) {
|
||||||
|
if fips140Enforced() {
|
||||||
|
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||||
|
}
|
||||||
if len(key) != KeySize {
|
if len(key) != KeySize {
|
||||||
return nil, errors.New("chacha20poly1305: bad key length")
|
return nil, errors.New("chacha20poly1305: bad key length")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool
|
|||||||
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
|
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
useAVX2 = cpu.X86.HasSSSE3 && cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
||||||
)
|
)
|
||||||
|
|
||||||
// setupState writes a ChaCha20 input matrix to state. See
|
// setupState writes a ChaCha20 input matrix to state. See
|
||||||
@@ -47,7 +47,7 @@ func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||||
if !cpu.X86.HasSSSE3 {
|
if !useAVX2 {
|
||||||
return c.sealGeneric(dst, nonce, plaintext, additionalData)
|
return c.sealGeneric(dst, nonce, plaintext, additionalData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +56,17 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
|||||||
|
|
||||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||||
if alias.InexactOverlap(out, plaintext) {
|
if alias.InexactOverlap(out, plaintext) {
|
||||||
panic("chacha20poly1305: invalid buffer overlap")
|
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||||
}
|
}
|
||||||
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||||
if !cpu.X86.HasSSSE3 {
|
if !useAVX2 {
|
||||||
return c.openGeneric(dst, nonce, ciphertext, additionalData)
|
return c.openGeneric(dst, nonce, ciphertext, additionalData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
|
|||||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||||
if alias.InexactOverlap(out, ciphertext) {
|
if alias.InexactOverlap(out, ciphertext) {
|
||||||
panic("chacha20poly1305: invalid buffer overlap")
|
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||||
}
|
}
|
||||||
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
|
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
|
||||||
for i := range out {
|
for i := range out {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b
|
|||||||
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
|
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
|
||||||
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
|
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
|
||||||
if alias.InexactOverlap(out, plaintext) {
|
if alias.InexactOverlap(out, plaintext) {
|
||||||
panic("chacha20poly1305: invalid buffer overlap")
|
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||||
}
|
}
|
||||||
|
|
||||||
var polyKey [32]byte
|
var polyKey [32]byte
|
||||||
@@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
|
|||||||
|
|
||||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||||
if alias.InexactOverlap(out, ciphertext) {
|
if alias.InexactOverlap(out, ciphertext) {
|
||||||
panic("chacha20poly1305: invalid buffer overlap")
|
panic("chacha20poly1305: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
|
||||||
}
|
}
|
||||||
if !p.Verify(tag) {
|
if !p.Verify(tag) {
|
||||||
for i := range out {
|
for i := range out {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func TestRandom(t *testing.T) {
|
|||||||
t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
|
t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
|
func benchmarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.SetBytes(int64(len(buf)))
|
b.SetBytes(int64(len(buf)))
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
|
func benchmarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.SetBytes(int64(len(buf)))
|
b.SetBytes(int64(len(buf)))
|
||||||
|
|
||||||
@@ -204,17 +204,17 @@ func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
|
|||||||
func BenchmarkChacha20Poly1305(b *testing.B) {
|
func BenchmarkChacha20Poly1305(b *testing.B) {
|
||||||
for _, length := range []int{64, 1350, 8 * 1024} {
|
for _, length := range []int{64, 1350, 8 * 1024} {
|
||||||
b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
|
b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
|
||||||
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
|
benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
|
||||||
})
|
})
|
||||||
b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
|
b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
|
||||||
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
|
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
||||||
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
|
benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
|
||||||
})
|
})
|
||||||
b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
|
||||||
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
|
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build go1.25
|
//go:build !go1.26
|
||||||
|
|
||||||
package blake2b
|
package chacha20poly1305
|
||||||
|
|
||||||
import "hash"
|
func fips140Enforced() bool { return false }
|
||||||
|
|
||||||
var _ hash.XOF = (*xof)(nil)
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.26
|
||||||
|
|
||||||
|
package chacha20poly1305
|
||||||
|
|
||||||
|
import "crypto/fips140"
|
||||||
|
|
||||||
|
func fips140Enforced() bool { return fips140.Enforced() }
|
||||||
@@ -22,6 +22,9 @@ type xchacha20poly1305 struct {
|
|||||||
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
|
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
|
||||||
// nonces are randomly generated.
|
// nonces are randomly generated.
|
||||||
func NewX(key []byte) (cipher.AEAD, error) {
|
func NewX(key []byte) (cipher.AEAD, error) {
|
||||||
|
if fips140Enforced() {
|
||||||
|
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||||
|
}
|
||||||
if len(key) != KeySize {
|
if len(key) != KeySize {
|
||||||
return nil, errors.New("chacha20poly1305: bad key length")
|
return nil, errors.New("chacha20poly1305: bad key length")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package curve25519 provides an implementation of the X25519 function, which
|
// Package curve25519 provides an implementation of the X25519 function, which
|
||||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
// performs scalar multiplication on the elliptic curve known as Curve25519
|
||||||
// See RFC 7748.
|
// according to [RFC 7748].
|
||||||
//
|
//
|
||||||
// This package is a wrapper for the X25519 implementation
|
// The curve25519 package is a wrapper for the X25519 implementation in the
|
||||||
// in the crypto/ecdh package.
|
// crypto/ecdh package. It is [frozen] and is not accepting new features.
|
||||||
|
//
|
||||||
|
// [RFC 7748]: https://datatracker.ietf.org/doc/html/rfc7748
|
||||||
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
package curve25519
|
package curve25519
|
||||||
|
|
||||||
import "crypto/ecdh"
|
import "crypto/ecdh"
|
||||||
|
|||||||
+7
-4
@@ -2,16 +2,19 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
// Package ed25519 implements the Ed25519 signature algorithm.
|
||||||
// https://ed25519.cr.yp.to/.
|
|
||||||
//
|
//
|
||||||
// These functions are also compatible with the “Ed25519” function defined in
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
|
// [RFC 8032]. However, unlike RFC 8032's formulation, this package's private key
|
||||||
// representation includes a public key suffix to make multiple signing
|
// representation includes a public key suffix to make multiple signing
|
||||||
// operations with the same key more efficient. This package refers to the RFC
|
// operations with the same key more efficient. This package refers to the RFC
|
||||||
// 8032 private key as the “seed”.
|
// 8032 private key as the “seed”.
|
||||||
//
|
//
|
||||||
// This package is a wrapper around the standard library crypto/ed25519 package.
|
// The ed25519 package is a wrapper for the Ed25519 implementation in the
|
||||||
|
// crypto/ed25519 package. It is [frozen] and is not accepting new features.
|
||||||
|
//
|
||||||
|
// [RFC 8032]: https://datatracker.ietf.org/doc/html/rfc8032
|
||||||
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
package ed25519
|
package ed25519
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
module golang.org/x/crypto
|
module golang.org/x/crypto
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/net v0.43.0 // tagx:ignore
|
golang.org/x/net v0.54.0 // tagx:ignore
|
||||||
golang.org/x/sys v0.36.0
|
golang.org/x/sys v0.45.0
|
||||||
golang.org/x/term v0.35.0
|
golang.org/x/term v0.43.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.29.0 // indirect
|
require golang.org/x/text v0.37.0 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
|
|||||||
+13
-8
@@ -11,6 +11,7 @@
|
|||||||
package hkdf
|
package hkdf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hkdf"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
@@ -24,15 +25,19 @@ import (
|
|||||||
// Expand invocations and different context values. Most common scenarios,
|
// Expand invocations and different context values. Most common scenarios,
|
||||||
// including the generation of multiple keys, should use New instead.
|
// including the generation of multiple keys, should use New instead.
|
||||||
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
||||||
if salt == nil {
|
// Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
|
||||||
salt = make([]byte, hash().Size())
|
// key (which in HKDF is the salt). The only possible error is FIPS 140
|
||||||
|
// enforcement of the hash, which had to panic under this API anyway. We
|
||||||
|
// don't use the stdlib Expand, because it switched to returning a []byte
|
||||||
|
// instead of an io.Reader, and Expand uses the HMAC key as a key.
|
||||||
|
out, err := hkdf.Extract(hash, secret, salt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
extractor := hmac.New(hash, salt)
|
return out
|
||||||
extractor.Write(secret)
|
|
||||||
return extractor.Sum(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type hkdf struct {
|
type hkdfReader struct {
|
||||||
expander hash.Hash
|
expander hash.Hash
|
||||||
size int
|
size int
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ type hkdf struct {
|
|||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *hkdf) Read(p []byte) (int, error) {
|
func (f *hkdfReader) Read(p []byte) (int, error) {
|
||||||
// Check whether enough data can be generated
|
// Check whether enough data can be generated
|
||||||
need := len(p)
|
need := len(p)
|
||||||
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
||||||
@@ -84,7 +89,7 @@ func (f *hkdf) Read(p []byte) (int, error) {
|
|||||||
// 3.3. Most common scenarios will want to use New instead.
|
// 3.3. Most common scenarios will want to use New instead.
|
||||||
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
||||||
expander := hmac.New(hash, pseudorandomKey)
|
expander := hmac.New(hash, pseudorandomKey)
|
||||||
return &hkdf{expander, expander.Size(), info, 1, nil, nil}
|
return &hkdfReader{expander, expander.Size(), info, 1, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a Reader, from which keys can be read, using the given hash,
|
// New returns a Reader, from which keys can be read, using the given hash,
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func TestRsaPss(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Run all the tests twice: the first time with the salt length
|
// Run all the tests twice: the first time with the salt length
|
||||||
// as PSSSaltLengthAuto, and the second time with the salt length
|
// as PSSSaltLengthAuto, and the second time with the salt length
|
||||||
// explictily set to tg.SLen.
|
// explicitly set to tg.SLen.
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
for _, sig := range tg.Tests {
|
for _, sig := range tg.Tests {
|
||||||
h.Reset()
|
h.Reset()
|
||||||
|
|||||||
+10
-19
@@ -2,25 +2,16 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
/*
|
// Package auth authenticates a message using a secret key.
|
||||||
Package auth authenticates a message using a secret key.
|
//
|
||||||
|
// This package is interoperable with [NaCl].
|
||||||
The Sum function, viewed as a function of the message for a uniform random
|
//
|
||||||
key, is designed to meet the standard notion of unforgeability. This means
|
// The auth package is essentially a wrapper for HMAC-SHA-512 (implemented by
|
||||||
that an attacker cannot find authenticators for any messages not authenticated
|
// crypto/hmac and crypto/sha512), truncated to 32 bytes. It is [frozen] and is
|
||||||
by the sender, even if the attacker has adaptively influenced the messages
|
// not accepting new features.
|
||||||
authenticated by the sender. For a formal definition see, e.g., Section 2.4
|
//
|
||||||
of Bellare, Kilian, and Rogaway, "The security of the cipher block chaining
|
// [NaCl]: https://nacl.cr.yp.to/auth.html
|
||||||
message authentication code," Journal of Computer and System Sciences 61 (2000),
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
362–399; http://www-cse.ucsd.edu/~mihir/papers/cbc.html.
|
|
||||||
|
|
||||||
auth does not make any promises regarding "strong" unforgeability; perhaps
|
|
||||||
one valid authenticator can be converted into another valid authenticator for
|
|
||||||
the same message. NaCl also does not make any promises regarding "truncated
|
|
||||||
unforgeability."
|
|
||||||
|
|
||||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/auth.html.
|
|
||||||
*/
|
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func TestSealOpenAnonymous(t *testing.T) {
|
|||||||
t.Fatal("expected out to be unchanged")
|
t.Fatal("expected out to be unchanged")
|
||||||
}
|
}
|
||||||
if !bytes.HasPrefix(box, orig) {
|
if !bytes.HasPrefix(box, orig) {
|
||||||
t.Fatal("expected out to be coppied to returned slice")
|
t.Fatal("expected out to be copied to returned slice")
|
||||||
}
|
}
|
||||||
_, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
|
_, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
+8
-13
@@ -4,20 +4,15 @@
|
|||||||
|
|
||||||
// Package sign signs small messages using public-key cryptography.
|
// Package sign signs small messages using public-key cryptography.
|
||||||
//
|
//
|
||||||
// Sign uses Ed25519 to sign messages. The length of messages is not hidden.
|
// This package is interoperable with [libsodium], as well as [TweetNaCl].
|
||||||
// Messages should be small because:
|
|
||||||
// 1. The whole message needs to be held in memory to be processed.
|
|
||||||
// 2. Using large messages pressures implementations on small machines to process
|
|
||||||
// plaintext without verifying the signature. This is very dangerous, and this API
|
|
||||||
// discourages it, but a protocol that uses excessive message sizes might present
|
|
||||||
// some implementations with no other choice.
|
|
||||||
// 3. Performance may be improved by working with messages that fit into data caches.
|
|
||||||
// Thus large amounts of data should be chunked so that each message is small.
|
|
||||||
//
|
//
|
||||||
// This package is not interoperable with the current release of NaCl
|
// The sign package is essentially a wrapper for the Ed25519 signature
|
||||||
// (https://nacl.cr.yp.to/sign.html), which does not support Ed25519 yet. However,
|
// algorithm (implemented by crypto/ed25519). It is [frozen] and is not accepting
|
||||||
// it is compatible with the NaCl fork libsodium (https://www.libsodium.org), as well
|
// new features.
|
||||||
// as TweetNaCl (https://tweetnacl.cr.yp.to/).
|
//
|
||||||
|
// [libsodium]: https://libsodium.gitbook.io/doc/public-key_cryptography/public-key_signatures
|
||||||
|
// [TweetNaCl]: https://tweetnacl.cr.yp.to/
|
||||||
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
package sign
|
package sign
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -69,6 +69,6 @@ func TestGenerateSignOpen(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(message, testMessage) {
|
if !bytes.Equal(message, testMessage) {
|
||||||
t.Fatalf("verified message does not match signed messge, got\n%x\n, expected\n%x", message, testMessage)
|
t.Fatalf("verified message does not match signed message, got\n%x\n, expected\n%x", message, testMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func TestOpaqueParseReason(t *testing.T) {
|
|||||||
const expectedBad = 3
|
const expectedBad = 3
|
||||||
// Test post-conditions, make sure we actually parsed packets as expected.
|
// Test post-conditions, make sure we actually parsed packets as expected.
|
||||||
if badPackets != expectedBad {
|
if badPackets != expectedBad {
|
||||||
t.Errorf("unexpected # unparseable packets: %d (want %d)", badPackets, expectedBad)
|
t.Errorf("unexpected # unparsable packets: %d (want %d)", badPackets, expectedBad)
|
||||||
}
|
}
|
||||||
if uid == nil {
|
if uid == nil {
|
||||||
t.Errorf("failed to find expected UID in unsupported keyring")
|
t.Errorf("failed to find expected UID in unsupported keyring")
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ func (c *Config) hash() crypto.Hash {
|
|||||||
|
|
||||||
func (c *Config) encodedCount() uint8 {
|
func (c *Config) encodedCount() uint8 {
|
||||||
if c == nil || c.S2KCount == 0 {
|
if c == nil || c.S2KCount == 0 {
|
||||||
return 96 // The common case. Correspoding to 65536
|
return 96 // The common case. Corresponding to 65536
|
||||||
}
|
}
|
||||||
|
|
||||||
i := c.S2KCount
|
i := c.S2KCount
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
// The version of OTR implemented by this package has been deprecated
|
// The version of OTR implemented by this package has been deprecated
|
||||||
// (https://bugs.otr.im/lib/libotr/issues/140). An implementation of OTRv3 is
|
// (https://bugs.otr.im/lib/libotr/issues/140). An implementation of OTRv3 is
|
||||||
// available at https://github.com/coyim/otr3.
|
// available at https://github.com/coyim/otr3.
|
||||||
|
//
|
||||||
|
// The otr package is [frozen] and is not accepting new features.
|
||||||
|
//
|
||||||
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
package otr
|
package otr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+3
-3
@@ -75,8 +75,8 @@ const libOTRPrivateKey = `(privkeys
|
|||||||
(account
|
(account
|
||||||
(name "foo@example.com")
|
(name "foo@example.com")
|
||||||
(protocol prpl-jabber)
|
(protocol prpl-jabber)
|
||||||
(private-key
|
(private-key
|
||||||
(dsa
|
(dsa
|
||||||
(p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
|
(p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
|
||||||
(q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
|
(q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
|
||||||
(g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
|
(g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
|
||||||
@@ -402,7 +402,7 @@ func TestAgainstLibOTR(t *testing.T) {
|
|||||||
// This test requires otr.c.test to be built as /tmp/a.out.
|
// This test requires otr.c.test to be built as /tmp/a.out.
|
||||||
// If enabled, this tests runs forever performing OTR handshakes in a
|
// If enabled, this tests runs forever performing OTR handshakes in a
|
||||||
// loop.
|
// loop.
|
||||||
return
|
t.Skip("This test requires otr.c.test to be built as /tmp/a.out")
|
||||||
|
|
||||||
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
|
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
|
||||||
var alice Conversation
|
var alice Conversation
|
||||||
|
|||||||
+13
-60
@@ -2,24 +2,17 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
/*
|
// Package pbkdf2 implements the key derivation function PBKDF2 as defined in
|
||||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
// RFC 8018 (PKCS #5 v2.1).
|
||||||
2898 / PKCS #5 v2.0.
|
//
|
||||||
|
// This package is a wrapper for the PBKDF2 implementation in the
|
||||||
A key derivation function is useful when encrypting data based on a password
|
// [crypto/pbkdf2] package. It is [frozen] and is not accepting new features.
|
||||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
//
|
||||||
a secure encryption key based on the password.
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
|
|
||||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
|
||||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
|
||||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
|
||||||
choose, you can pass the `New` functions from the different SHA packages to
|
|
||||||
pbkdf2.Key.
|
|
||||||
*/
|
|
||||||
package pbkdf2
|
package pbkdf2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/pbkdf2"
|
||||||
"hash"
|
"hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,51 +20,11 @@ import (
|
|||||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
// the supplied hash function.
|
// the supplied hash function.
|
||||||
//
|
|
||||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
|
||||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
|
||||||
// doing:
|
|
||||||
//
|
|
||||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
|
||||||
//
|
|
||||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
|
||||||
// RFC.
|
|
||||||
//
|
|
||||||
// Using a higher iteration count will increase the cost of an exhaustive
|
|
||||||
// search but will also make derivation proportionally slower.
|
|
||||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
prf := hmac.New(h, password)
|
out, err := pbkdf2.Key(h, string(password), salt, iter, keyLen)
|
||||||
hashLen := prf.Size()
|
if err != nil {
|
||||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
// FIPS 140 enforcement, or an invalid key length.
|
||||||
|
panic(err)
|
||||||
var buf [4]byte
|
|
||||||
dk := make([]byte, 0, numBlocks*hashLen)
|
|
||||||
U := make([]byte, hashLen)
|
|
||||||
for block := 1; block <= numBlocks; block++ {
|
|
||||||
// N.B.: || means concatenation, ^ means XOR
|
|
||||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
|
||||||
// U_1 = PRF(password, salt || uint(i))
|
|
||||||
prf.Reset()
|
|
||||||
prf.Write(salt)
|
|
||||||
buf[0] = byte(block >> 24)
|
|
||||||
buf[1] = byte(block >> 16)
|
|
||||||
buf[2] = byte(block >> 8)
|
|
||||||
buf[3] = byte(block)
|
|
||||||
prf.Write(buf[:4])
|
|
||||||
dk = prf.Sum(dk)
|
|
||||||
T := dk[len(dk)-hashLen:]
|
|
||||||
copy(U, T)
|
|
||||||
|
|
||||||
// U_n = PRF(password, U_(n-1))
|
|
||||||
for n := 2; n <= iter; n++ {
|
|
||||||
prf.Reset()
|
|
||||||
prf.Write(U)
|
|
||||||
U = U[:0]
|
|
||||||
U = prf.Sum(U)
|
|
||||||
for x := range U {
|
|
||||||
T[x] ^= U[x]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return dk[:keyLen]
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-5
@@ -4,12 +4,16 @@
|
|||||||
|
|
||||||
// Package pkcs12 implements some of PKCS#12.
|
// Package pkcs12 implements some of PKCS#12.
|
||||||
//
|
//
|
||||||
// This implementation is distilled from https://tools.ietf.org/html/rfc7292
|
// This implementation is distilled from [RFC 7292] and referenced documents.
|
||||||
// and referenced documents. It is intended for decoding P12/PFX-stored
|
// It is intended for decoding P12/PFX-stored certificates and keys for use
|
||||||
// certificates and keys for use with the crypto/tls package.
|
// with the crypto/tls package.
|
||||||
//
|
//
|
||||||
// This package is frozen. If it's missing functionality you need, consider
|
// The pkcs12 package is [frozen] and is not accepting new features.
|
||||||
// an alternative like software.sslmate.com/src/go-pkcs12.
|
// If it's missing functionality you need, consider an alternative like
|
||||||
|
// software.sslmate.com/src/go-pkcs12.
|
||||||
|
//
|
||||||
|
// [RFC 7292]: https://datatracker.ietf.org/doc/html/rfc7292
|
||||||
|
// [frozen]: https://go.dev/wiki/Frozen
|
||||||
package pkcs12
|
package pkcs12
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package salsa provides low-level access to functions in the Salsa family.
|
// Package salsa provides low-level access to functions in the Salsa family.
|
||||||
|
//
|
||||||
|
// Deprecated: this package exposes unsafe low-level operations. New applications
|
||||||
|
// should consider using the AEAD construction in golang.org/x/crypto/chacha20poly1305
|
||||||
|
// instead. Existing users should migrate to golang.org/x/crypto/salsa20.
|
||||||
package salsa
|
package salsa
|
||||||
|
|
||||||
import "math/bits"
|
import "math/bits"
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) {
|
|||||||
if N <= 1 || N&(N-1) != 0 {
|
if N <= 1 || N&(N-1) != 0 {
|
||||||
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
|
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
|
||||||
}
|
}
|
||||||
|
if r <= 0 || p <= 0 {
|
||||||
|
return nil, errors.New("scrypt: parameters must be > 0")
|
||||||
|
}
|
||||||
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
|
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
|
||||||
return nil, errors.New("scrypt: parameters are too large")
|
return nil, errors.New("scrypt: parameters are too large")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ var bad = []testVector{
|
|||||||
{"p", "s", 1, 1, 1, nil}, // N == 1
|
{"p", "s", 1, 1, 1, nil}, // N == 1
|
||||||
{"p", "s", 7, 8, 1, nil}, // N is not power of 2
|
{"p", "s", 7, 8, 1, nil}, // N is not power of 2
|
||||||
{"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large
|
{"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large
|
||||||
|
{"p", "s", 2, 0, 1, nil}, // r too small
|
||||||
|
{"p", "s", 2, 1, 0, nil}, // p too small
|
||||||
|
{"p", "s", 2, -1, 1, nil}, // r is negative
|
||||||
|
{"p", "s", 2, 1, -1, nil}, // p is negative
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKey(t *testing.T) {
|
func TestKey(t *testing.T) {
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
module sha3/_asm
|
|
||||||
|
|
||||||
go 1.22
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/mmcloughlin/avo v0.6.0
|
|
||||||
golang.org/x/crypto v0.33.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
golang.org/x/mod v0.19.0 // indirect
|
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
|
||||||
golang.org/x/tools v0.23.0 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
|
||||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
|
||||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
|
||||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
|
||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
|
||||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
|
||||||
@@ -1,438 +0,0 @@
|
|||||||
// Copyright 2024 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.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources at https://github.com/gvanas/KeccakCodePackage
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/mmcloughlin/avo/build"
|
|
||||||
. "github.com/mmcloughlin/avo/operand"
|
|
||||||
. "github.com/mmcloughlin/avo/reg"
|
|
||||||
_ "golang.org/x/crypto/sha3"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run . -out ../keccakf_amd64.s -pkg sha3
|
|
||||||
|
|
||||||
// Round Constants for use in the ι step.
|
|
||||||
var RoundConstants = [24]uint64{
|
|
||||||
0x0000000000000001,
|
|
||||||
0x0000000000008082,
|
|
||||||
0x800000000000808A,
|
|
||||||
0x8000000080008000,
|
|
||||||
0x000000000000808B,
|
|
||||||
0x0000000080000001,
|
|
||||||
0x8000000080008081,
|
|
||||||
0x8000000000008009,
|
|
||||||
0x000000000000008A,
|
|
||||||
0x0000000000000088,
|
|
||||||
0x0000000080008009,
|
|
||||||
0x000000008000000A,
|
|
||||||
0x000000008000808B,
|
|
||||||
0x800000000000008B,
|
|
||||||
0x8000000000008089,
|
|
||||||
0x8000000000008003,
|
|
||||||
0x8000000000008002,
|
|
||||||
0x8000000000000080,
|
|
||||||
0x000000000000800A,
|
|
||||||
0x800000008000000A,
|
|
||||||
0x8000000080008081,
|
|
||||||
0x8000000000008080,
|
|
||||||
0x0000000080000001,
|
|
||||||
0x8000000080008008,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Temporary registers
|
|
||||||
rT1 GPPhysical = RAX
|
|
||||||
|
|
||||||
// Round vars
|
|
||||||
rpState = Mem{Base: RDI}
|
|
||||||
rpStack = Mem{Base: RSP}
|
|
||||||
|
|
||||||
rDa = RBX
|
|
||||||
rDe = RCX
|
|
||||||
rDi = RDX
|
|
||||||
rDo = R8
|
|
||||||
rDu = R9
|
|
||||||
|
|
||||||
rBa = R10
|
|
||||||
rBe = R11
|
|
||||||
rBi = R12
|
|
||||||
rBo = R13
|
|
||||||
rBu = R14
|
|
||||||
|
|
||||||
rCa = RSI
|
|
||||||
rCe = RBP
|
|
||||||
rCi = rBi
|
|
||||||
rCo = rBo
|
|
||||||
rCu = R15
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ba = iota * 8
|
|
||||||
_be
|
|
||||||
_bi
|
|
||||||
_bo
|
|
||||||
_bu
|
|
||||||
_ga
|
|
||||||
_ge
|
|
||||||
_gi
|
|
||||||
_go
|
|
||||||
_gu
|
|
||||||
_ka
|
|
||||||
_ke
|
|
||||||
_ki
|
|
||||||
_ko
|
|
||||||
_ku
|
|
||||||
_ma
|
|
||||||
_me
|
|
||||||
_mi
|
|
||||||
_mo
|
|
||||||
_mu
|
|
||||||
_sa
|
|
||||||
_se
|
|
||||||
_si
|
|
||||||
_so
|
|
||||||
_su
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
Package("golang.org/x/crypto/sha3")
|
|
||||||
ConstraintExpr("amd64,!purego,gc")
|
|
||||||
keccakF1600()
|
|
||||||
Generate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func MOVQ_RBI_RCE() { MOVQ(rBi, rCe) }
|
|
||||||
func XORQ_RT1_RCA() { XORQ(rT1, rCa) }
|
|
||||||
func XORQ_RT1_RCE() { XORQ(rT1, rCe) }
|
|
||||||
func XORQ_RBA_RCU() { XORQ(rBa, rCu) }
|
|
||||||
func XORQ_RBE_RCU() { XORQ(rBe, rCu) }
|
|
||||||
func XORQ_RDU_RCU() { XORQ(rDu, rCu) }
|
|
||||||
func XORQ_RDA_RCA() { XORQ(rDa, rCa) }
|
|
||||||
func XORQ_RDE_RCE() { XORQ(rDe, rCe) }
|
|
||||||
|
|
||||||
type ArgMacro func()
|
|
||||||
|
|
||||||
func mKeccakRound(
|
|
||||||
iState, oState Mem,
|
|
||||||
rc U64,
|
|
||||||
B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU,
|
|
||||||
K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA,
|
|
||||||
M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA,
|
|
||||||
S_RDE_RCE ArgMacro,
|
|
||||||
) {
|
|
||||||
Comment("Prepare round")
|
|
||||||
MOVQ(rCe, rDa)
|
|
||||||
ROLQ(Imm(1), rDa)
|
|
||||||
|
|
||||||
MOVQ(iState.Offset(_bi), rCi)
|
|
||||||
XORQ(iState.Offset(_gi), rDi)
|
|
||||||
XORQ(rCu, rDa)
|
|
||||||
XORQ(iState.Offset(_ki), rCi)
|
|
||||||
XORQ(iState.Offset(_mi), rDi)
|
|
||||||
XORQ(rDi, rCi)
|
|
||||||
|
|
||||||
MOVQ(rCi, rDe)
|
|
||||||
ROLQ(Imm(1), rDe)
|
|
||||||
|
|
||||||
MOVQ(iState.Offset(_bo), rCo)
|
|
||||||
XORQ(iState.Offset(_go), rDo)
|
|
||||||
XORQ(rCa, rDe)
|
|
||||||
XORQ(iState.Offset(_ko), rCo)
|
|
||||||
XORQ(iState.Offset(_mo), rDo)
|
|
||||||
XORQ(rDo, rCo)
|
|
||||||
|
|
||||||
MOVQ(rCo, rDi)
|
|
||||||
ROLQ(Imm(1), rDi)
|
|
||||||
|
|
||||||
MOVQ(rCu, rDo)
|
|
||||||
XORQ(rCe, rDi)
|
|
||||||
ROLQ(Imm(1), rDo)
|
|
||||||
|
|
||||||
MOVQ(rCa, rDu)
|
|
||||||
XORQ(rCi, rDo)
|
|
||||||
ROLQ(Imm(1), rDu)
|
|
||||||
|
|
||||||
Comment("Result b")
|
|
||||||
MOVQ(iState.Offset(_ba), rBa)
|
|
||||||
MOVQ(iState.Offset(_ge), rBe)
|
|
||||||
XORQ(rCo, rDu)
|
|
||||||
MOVQ(iState.Offset(_ki), rBi)
|
|
||||||
MOVQ(iState.Offset(_mo), rBo)
|
|
||||||
MOVQ(iState.Offset(_su), rBu)
|
|
||||||
XORQ(rDe, rBe)
|
|
||||||
ROLQ(Imm(44), rBe)
|
|
||||||
XORQ(rDi, rBi)
|
|
||||||
XORQ(rDa, rBa)
|
|
||||||
ROLQ(Imm(43), rBi)
|
|
||||||
|
|
||||||
MOVQ(rBe, rCa)
|
|
||||||
MOVQ(rc, rT1)
|
|
||||||
ORQ(rBi, rCa)
|
|
||||||
XORQ(rBa, rT1)
|
|
||||||
XORQ(rT1, rCa)
|
|
||||||
MOVQ(rCa, oState.Offset(_ba))
|
|
||||||
|
|
||||||
XORQ(rDu, rBu)
|
|
||||||
ROLQ(Imm(14), rBu)
|
|
||||||
MOVQ(rBa, rCu)
|
|
||||||
ANDQ(rBe, rCu)
|
|
||||||
XORQ(rBu, rCu)
|
|
||||||
MOVQ(rCu, oState.Offset(_bu))
|
|
||||||
|
|
||||||
XORQ(rDo, rBo)
|
|
||||||
ROLQ(Imm(21), rBo)
|
|
||||||
MOVQ(rBo, rT1)
|
|
||||||
ANDQ(rBu, rT1)
|
|
||||||
XORQ(rBi, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_bi))
|
|
||||||
|
|
||||||
NOTQ(rBi)
|
|
||||||
ORQ(rBa, rBu)
|
|
||||||
ORQ(rBo, rBi)
|
|
||||||
XORQ(rBo, rBu)
|
|
||||||
XORQ(rBe, rBi)
|
|
||||||
MOVQ(rBu, oState.Offset(_bo))
|
|
||||||
MOVQ(rBi, oState.Offset(_be))
|
|
||||||
B_RBI_RCE()
|
|
||||||
|
|
||||||
Comment("Result g")
|
|
||||||
MOVQ(iState.Offset(_gu), rBe)
|
|
||||||
XORQ(rDu, rBe)
|
|
||||||
MOVQ(iState.Offset(_ka), rBi)
|
|
||||||
ROLQ(Imm(20), rBe)
|
|
||||||
XORQ(rDa, rBi)
|
|
||||||
ROLQ(Imm(3), rBi)
|
|
||||||
MOVQ(iState.Offset(_bo), rBa)
|
|
||||||
MOVQ(rBe, rT1)
|
|
||||||
ORQ(rBi, rT1)
|
|
||||||
XORQ(rDo, rBa)
|
|
||||||
MOVQ(iState.Offset(_me), rBo)
|
|
||||||
MOVQ(iState.Offset(_si), rBu)
|
|
||||||
ROLQ(Imm(28), rBa)
|
|
||||||
XORQ(rBa, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ga))
|
|
||||||
G_RT1_RCA()
|
|
||||||
|
|
||||||
XORQ(rDe, rBo)
|
|
||||||
ROLQ(Imm(45), rBo)
|
|
||||||
MOVQ(rBi, rT1)
|
|
||||||
ANDQ(rBo, rT1)
|
|
||||||
XORQ(rBe, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ge))
|
|
||||||
G_RT1_RCE()
|
|
||||||
|
|
||||||
XORQ(rDi, rBu)
|
|
||||||
ROLQ(Imm(61), rBu)
|
|
||||||
MOVQ(rBu, rT1)
|
|
||||||
ORQ(rBa, rT1)
|
|
||||||
XORQ(rBo, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_go))
|
|
||||||
|
|
||||||
ANDQ(rBe, rBa)
|
|
||||||
XORQ(rBu, rBa)
|
|
||||||
MOVQ(rBa, oState.Offset(_gu))
|
|
||||||
NOTQ(rBu)
|
|
||||||
G_RBA_RCU()
|
|
||||||
|
|
||||||
ORQ(rBu, rBo)
|
|
||||||
XORQ(rBi, rBo)
|
|
||||||
MOVQ(rBo, oState.Offset(_gi))
|
|
||||||
|
|
||||||
Comment("Result k")
|
|
||||||
MOVQ(iState.Offset(_be), rBa)
|
|
||||||
MOVQ(iState.Offset(_gi), rBe)
|
|
||||||
MOVQ(iState.Offset(_ko), rBi)
|
|
||||||
MOVQ(iState.Offset(_mu), rBo)
|
|
||||||
MOVQ(iState.Offset(_sa), rBu)
|
|
||||||
XORQ(rDi, rBe)
|
|
||||||
ROLQ(Imm(6), rBe)
|
|
||||||
XORQ(rDo, rBi)
|
|
||||||
ROLQ(Imm(25), rBi)
|
|
||||||
MOVQ(rBe, rT1)
|
|
||||||
ORQ(rBi, rT1)
|
|
||||||
XORQ(rDe, rBa)
|
|
||||||
ROLQ(Imm(1), rBa)
|
|
||||||
XORQ(rBa, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ka))
|
|
||||||
K_RT1_RCA()
|
|
||||||
|
|
||||||
XORQ(rDu, rBo)
|
|
||||||
ROLQ(Imm(8), rBo)
|
|
||||||
MOVQ(rBi, rT1)
|
|
||||||
ANDQ(rBo, rT1)
|
|
||||||
XORQ(rBe, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ke))
|
|
||||||
K_RT1_RCE()
|
|
||||||
|
|
||||||
XORQ(rDa, rBu)
|
|
||||||
ROLQ(Imm(18), rBu)
|
|
||||||
NOTQ(rBo)
|
|
||||||
MOVQ(rBo, rT1)
|
|
||||||
ANDQ(rBu, rT1)
|
|
||||||
XORQ(rBi, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ki))
|
|
||||||
|
|
||||||
MOVQ(rBu, rT1)
|
|
||||||
ORQ(rBa, rT1)
|
|
||||||
XORQ(rBo, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ko))
|
|
||||||
|
|
||||||
ANDQ(rBe, rBa)
|
|
||||||
XORQ(rBu, rBa)
|
|
||||||
MOVQ(rBa, oState.Offset(_ku))
|
|
||||||
K_RBA_RCU()
|
|
||||||
|
|
||||||
Comment("Result m")
|
|
||||||
MOVQ(iState.Offset(_ga), rBe)
|
|
||||||
XORQ(rDa, rBe)
|
|
||||||
MOVQ(iState.Offset(_ke), rBi)
|
|
||||||
ROLQ(Imm(36), rBe)
|
|
||||||
XORQ(rDe, rBi)
|
|
||||||
MOVQ(iState.Offset(_bu), rBa)
|
|
||||||
ROLQ(Imm(10), rBi)
|
|
||||||
MOVQ(rBe, rT1)
|
|
||||||
MOVQ(iState.Offset(_mi), rBo)
|
|
||||||
ANDQ(rBi, rT1)
|
|
||||||
XORQ(rDu, rBa)
|
|
||||||
MOVQ(iState.Offset(_so), rBu)
|
|
||||||
ROLQ(Imm(27), rBa)
|
|
||||||
XORQ(rBa, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_ma))
|
|
||||||
M_RT1_RCA()
|
|
||||||
|
|
||||||
XORQ(rDi, rBo)
|
|
||||||
ROLQ(Imm(15), rBo)
|
|
||||||
MOVQ(rBi, rT1)
|
|
||||||
ORQ(rBo, rT1)
|
|
||||||
XORQ(rBe, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_me))
|
|
||||||
M_RT1_RCE()
|
|
||||||
|
|
||||||
XORQ(rDo, rBu)
|
|
||||||
ROLQ(Imm(56), rBu)
|
|
||||||
NOTQ(rBo)
|
|
||||||
MOVQ(rBo, rT1)
|
|
||||||
ORQ(rBu, rT1)
|
|
||||||
XORQ(rBi, rT1)
|
|
||||||
MOVQ(rT1, oState.Offset(_mi))
|
|
||||||
|
|
||||||
ORQ(rBa, rBe)
|
|
||||||
XORQ(rBu, rBe)
|
|
||||||
MOVQ(rBe, oState.Offset(_mu))
|
|
||||||
|
|
||||||
ANDQ(rBa, rBu)
|
|
||||||
XORQ(rBo, rBu)
|
|
||||||
MOVQ(rBu, oState.Offset(_mo))
|
|
||||||
M_RBE_RCU()
|
|
||||||
|
|
||||||
Comment("Result s")
|
|
||||||
MOVQ(iState.Offset(_bi), rBa)
|
|
||||||
MOVQ(iState.Offset(_go), rBe)
|
|
||||||
MOVQ(iState.Offset(_ku), rBi)
|
|
||||||
XORQ(rDi, rBa)
|
|
||||||
MOVQ(iState.Offset(_ma), rBo)
|
|
||||||
ROLQ(Imm(62), rBa)
|
|
||||||
XORQ(rDo, rBe)
|
|
||||||
MOVQ(iState.Offset(_se), rBu)
|
|
||||||
ROLQ(Imm(55), rBe)
|
|
||||||
|
|
||||||
XORQ(rDu, rBi)
|
|
||||||
MOVQ(rBa, rDu)
|
|
||||||
XORQ(rDe, rBu)
|
|
||||||
ROLQ(Imm(2), rBu)
|
|
||||||
ANDQ(rBe, rDu)
|
|
||||||
XORQ(rBu, rDu)
|
|
||||||
MOVQ(rDu, oState.Offset(_su))
|
|
||||||
|
|
||||||
ROLQ(Imm(39), rBi)
|
|
||||||
S_RDU_RCU()
|
|
||||||
NOTQ(rBe)
|
|
||||||
XORQ(rDa, rBo)
|
|
||||||
MOVQ(rBe, rDa)
|
|
||||||
ANDQ(rBi, rDa)
|
|
||||||
XORQ(rBa, rDa)
|
|
||||||
MOVQ(rDa, oState.Offset(_sa))
|
|
||||||
S_RDA_RCA()
|
|
||||||
|
|
||||||
ROLQ(Imm(41), rBo)
|
|
||||||
MOVQ(rBi, rDe)
|
|
||||||
ORQ(rBo, rDe)
|
|
||||||
XORQ(rBe, rDe)
|
|
||||||
MOVQ(rDe, oState.Offset(_se))
|
|
||||||
S_RDE_RCE()
|
|
||||||
|
|
||||||
MOVQ(rBo, rDi)
|
|
||||||
MOVQ(rBu, rDo)
|
|
||||||
ANDQ(rBu, rDi)
|
|
||||||
ORQ(rBa, rDo)
|
|
||||||
XORQ(rBi, rDi)
|
|
||||||
XORQ(rBo, rDo)
|
|
||||||
MOVQ(rDi, oState.Offset(_si))
|
|
||||||
MOVQ(rDo, oState.Offset(_so))
|
|
||||||
}
|
|
||||||
|
|
||||||
// keccakF1600 applies the Keccak permutation to a 1600b-wide
|
|
||||||
// state represented as a slice of 25 uint64s.
|
|
||||||
func keccakF1600() {
|
|
||||||
Implement("keccakF1600")
|
|
||||||
AllocLocal(200)
|
|
||||||
|
|
||||||
Load(Param("a"), rpState.Base)
|
|
||||||
|
|
||||||
Comment("Convert the user state into an internal state")
|
|
||||||
NOTQ(rpState.Offset(_be))
|
|
||||||
NOTQ(rpState.Offset(_bi))
|
|
||||||
NOTQ(rpState.Offset(_go))
|
|
||||||
NOTQ(rpState.Offset(_ki))
|
|
||||||
NOTQ(rpState.Offset(_mi))
|
|
||||||
NOTQ(rpState.Offset(_sa))
|
|
||||||
|
|
||||||
Comment("Execute the KeccakF permutation")
|
|
||||||
MOVQ(rpState.Offset(_ba), rCa)
|
|
||||||
MOVQ(rpState.Offset(_be), rCe)
|
|
||||||
MOVQ(rpState.Offset(_bu), rCu)
|
|
||||||
|
|
||||||
XORQ(rpState.Offset(_ga), rCa)
|
|
||||||
XORQ(rpState.Offset(_ge), rCe)
|
|
||||||
XORQ(rpState.Offset(_gu), rCu)
|
|
||||||
|
|
||||||
XORQ(rpState.Offset(_ka), rCa)
|
|
||||||
XORQ(rpState.Offset(_ke), rCe)
|
|
||||||
XORQ(rpState.Offset(_ku), rCu)
|
|
||||||
|
|
||||||
XORQ(rpState.Offset(_ma), rCa)
|
|
||||||
XORQ(rpState.Offset(_me), rCe)
|
|
||||||
XORQ(rpState.Offset(_mu), rCu)
|
|
||||||
|
|
||||||
XORQ(rpState.Offset(_sa), rCa)
|
|
||||||
XORQ(rpState.Offset(_se), rCe)
|
|
||||||
MOVQ(rpState.Offset(_si), rDi)
|
|
||||||
MOVQ(rpState.Offset(_so), rDo)
|
|
||||||
XORQ(rpState.Offset(_su), rCu)
|
|
||||||
|
|
||||||
for i, rc := range RoundConstants[:len(RoundConstants)-1] {
|
|
||||||
var iState, oState Mem
|
|
||||||
if i%2 == 0 {
|
|
||||||
iState, oState = rpState, rpStack
|
|
||||||
} else {
|
|
||||||
iState, oState = rpStack, rpState
|
|
||||||
}
|
|
||||||
mKeccakRound(iState, oState, U64(rc), MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
}
|
|
||||||
mKeccakRound(rpStack, rpState, U64(RoundConstants[len(RoundConstants)-1]), NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP)
|
|
||||||
|
|
||||||
Comment("Revert the internal state to the user state")
|
|
||||||
NOTQ(rpState.Offset(_be))
|
|
||||||
NOTQ(rpState.Offset(_bi))
|
|
||||||
NOTQ(rpState.Offset(_go))
|
|
||||||
NOTQ(rpState.Offset(_ki))
|
|
||||||
NOTQ(rpState.Offset(_mi))
|
|
||||||
NOTQ(rpState.Offset(_sa))
|
|
||||||
|
|
||||||
RET()
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Copyright 2023 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !noopt
|
|
||||||
|
|
||||||
package sha3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sink byte
|
|
||||||
|
|
||||||
func TestAllocations(t *testing.T) {
|
|
||||||
want := 0.0
|
|
||||||
|
|
||||||
if runtime.GOARCH == "s390x" {
|
|
||||||
// On s390x the returned hash.Hash is conditional so it escapes.
|
|
||||||
want = 3.0
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("New", func(t *testing.T) {
|
|
||||||
if allocs := testing.AllocsPerRun(10, func() {
|
|
||||||
h := sha3.New256()
|
|
||||||
b := []byte("ABC")
|
|
||||||
h.Write(b)
|
|
||||||
out := make([]byte, 0, 32)
|
|
||||||
out = h.Sum(out)
|
|
||||||
sink ^= out[0]
|
|
||||||
}); allocs > want {
|
|
||||||
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("NewShake", func(t *testing.T) {
|
|
||||||
if allocs := testing.AllocsPerRun(10, func() {
|
|
||||||
h := sha3.NewShake128()
|
|
||||||
b := []byte("ABC")
|
|
||||||
h.Write(b)
|
|
||||||
out := make([]byte, 0, 32)
|
|
||||||
out = h.Sum(out)
|
|
||||||
sink ^= out[0]
|
|
||||||
h.Read(out)
|
|
||||||
sink ^= out[0]
|
|
||||||
}); allocs > want {
|
|
||||||
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("Sum", func(t *testing.T) {
|
|
||||||
if allocs := testing.AllocsPerRun(10, func() {
|
|
||||||
b := []byte("ABC")
|
|
||||||
out := sha3.Sum256(b)
|
|
||||||
sink ^= out[0]
|
|
||||||
}); allocs > want {
|
|
||||||
t.Errorf("expected zero allocations, got %0.1f", allocs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package sha3 implements the SHA-3 fixed-output-length hash functions and
|
|
||||||
// the SHAKE variable-output-length hash functions defined by FIPS-202.
|
|
||||||
//
|
|
||||||
// All types in this package also implement [encoding.BinaryMarshaler],
|
|
||||||
// [encoding.BinaryAppender] and [encoding.BinaryUnmarshaler] to marshal and
|
|
||||||
// unmarshal the internal state of the hash.
|
|
||||||
//
|
|
||||||
// Both types of hash function use the "sponge" construction and the Keccak
|
|
||||||
// permutation. For a detailed specification see http://keccak.noekeon.org/
|
|
||||||
//
|
|
||||||
// # Guidance
|
|
||||||
//
|
|
||||||
// If you aren't sure what function you need, use SHAKE256 with at least 64
|
|
||||||
// bytes of output. The SHAKE instances are faster than the SHA3 instances;
|
|
||||||
// the latter have to allocate memory to conform to the hash.Hash interface.
|
|
||||||
//
|
|
||||||
// If you need a secret-key MAC (message authentication code), prepend the
|
|
||||||
// secret key to the input, hash with SHAKE256 and read at least 32 bytes of
|
|
||||||
// output.
|
|
||||||
//
|
|
||||||
// # Security strengths
|
|
||||||
//
|
|
||||||
// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
|
|
||||||
// strength against preimage attacks of x bits. Since they only produce "x"
|
|
||||||
// bits of output, their collision-resistance is only "x/2" bits.
|
|
||||||
//
|
|
||||||
// The SHAKE-256 and -128 functions have a generic security strength of 256 and
|
|
||||||
// 128 bits against all attacks, provided that at least 2x bits of their output
|
|
||||||
// is used. Requesting more than 64 or 32 bytes of output, respectively, does
|
|
||||||
// not increase the collision-resistance of the SHAKE functions.
|
|
||||||
//
|
|
||||||
// # The sponge construction
|
|
||||||
//
|
|
||||||
// A sponge builds a pseudo-random function from a public pseudo-random
|
|
||||||
// permutation, by applying the permutation to a state of "rate + capacity"
|
|
||||||
// bytes, but hiding "capacity" of the bytes.
|
|
||||||
//
|
|
||||||
// A sponge starts out with a zero state. To hash an input using a sponge, up
|
|
||||||
// to "rate" bytes of the input are XORed into the sponge's state. The sponge
|
|
||||||
// is then "full" and the permutation is applied to "empty" it. This process is
|
|
||||||
// repeated until all the input has been "absorbed". The input is then padded.
|
|
||||||
// The digest is "squeezed" from the sponge in the same way, except that output
|
|
||||||
// is copied out instead of input being XORed in.
|
|
||||||
//
|
|
||||||
// A sponge is parameterized by its generic security strength, which is equal
|
|
||||||
// to half its capacity; capacity + rate is equal to the permutation's width.
|
|
||||||
// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
|
|
||||||
// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
|
|
||||||
//
|
|
||||||
// # Recommendations
|
|
||||||
//
|
|
||||||
// The SHAKE functions are recommended for most new uses. They can produce
|
|
||||||
// output of arbitrary length. SHAKE256, with an output length of at least
|
|
||||||
// 64 bytes, provides 256-bit security against all attacks. The Keccak team
|
|
||||||
// recommends it for most applications upgrading from SHA2-512. (NIST chose a
|
|
||||||
// much stronger, but much slower, sponge instance for SHA3-512.)
|
|
||||||
//
|
|
||||||
// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions.
|
|
||||||
// They produce output of the same length, with the same security strengths
|
|
||||||
// against all attacks. This means, in particular, that SHA3-256 only has
|
|
||||||
// 128-bit collision resistance, because its output length is 32 bytes.
|
|
||||||
package sha3
|
|
||||||
+50
-83
@@ -2,127 +2,94 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package sha3 implements the SHA-3 hash algorithms and the SHAKE extendable
|
||||||
|
// output functions defined in FIPS 202.
|
||||||
|
//
|
||||||
|
// Most of this package is a wrapper around the crypto/sha3 package in the
|
||||||
|
// standard library. The only exception is the legacy Keccak hash functions.
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
// This file provides functions for creating instances of the SHA-3
|
|
||||||
// and SHAKE hash functions, as well as utility functions for hashing
|
|
||||||
// bytes.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto/sha3"
|
||||||
"hash"
|
"hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New224 creates a new SHA3-224 hash.
|
// New224 creates a new SHA3-224 hash.
|
||||||
// Its generic security strength is 224 bits against preimage attacks,
|
// Its generic security strength is 224 bits against preimage attacks,
|
||||||
// and 112 bits against collision attacks.
|
// and 112 bits against collision attacks.
|
||||||
|
//
|
||||||
|
// It is a wrapper for the [sha3.New224] function in the standard library.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func New224() hash.Hash {
|
func New224() hash.Hash {
|
||||||
return new224()
|
return sha3.New224()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New256 creates a new SHA3-256 hash.
|
// New256 creates a new SHA3-256 hash.
|
||||||
// Its generic security strength is 256 bits against preimage attacks,
|
// Its generic security strength is 256 bits against preimage attacks,
|
||||||
// and 128 bits against collision attacks.
|
// and 128 bits against collision attacks.
|
||||||
|
//
|
||||||
|
// It is a wrapper for the [sha3.New256] function in the standard library.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func New256() hash.Hash {
|
func New256() hash.Hash {
|
||||||
return new256()
|
return sha3.New256()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New384 creates a new SHA3-384 hash.
|
// New384 creates a new SHA3-384 hash.
|
||||||
// Its generic security strength is 384 bits against preimage attacks,
|
// Its generic security strength is 384 bits against preimage attacks,
|
||||||
// and 192 bits against collision attacks.
|
// and 192 bits against collision attacks.
|
||||||
|
//
|
||||||
|
// It is a wrapper for the [sha3.New384] function in the standard library.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func New384() hash.Hash {
|
func New384() hash.Hash {
|
||||||
return new384()
|
return sha3.New384()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New512 creates a new SHA3-512 hash.
|
// New512 creates a new SHA3-512 hash.
|
||||||
// Its generic security strength is 512 bits against preimage attacks,
|
// Its generic security strength is 512 bits against preimage attacks,
|
||||||
// and 256 bits against collision attacks.
|
// and 256 bits against collision attacks.
|
||||||
|
//
|
||||||
|
// It is a wrapper for the [sha3.New512] function in the standard library.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func New512() hash.Hash {
|
func New512() hash.Hash {
|
||||||
return new512()
|
return sha3.New512()
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
crypto.RegisterHash(crypto.SHA3_224, New224)
|
|
||||||
crypto.RegisterHash(crypto.SHA3_256, New256)
|
|
||||||
crypto.RegisterHash(crypto.SHA3_384, New384)
|
|
||||||
crypto.RegisterHash(crypto.SHA3_512, New512)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
dsbyteSHA3 = 0b00000110
|
|
||||||
dsbyteKeccak = 0b00000001
|
|
||||||
dsbyteShake = 0b00011111
|
|
||||||
dsbyteCShake = 0b00000100
|
|
||||||
|
|
||||||
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
|
|
||||||
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
|
|
||||||
rateK256 = (1600 - 256) / 8
|
|
||||||
rateK448 = (1600 - 448) / 8
|
|
||||||
rateK512 = (1600 - 512) / 8
|
|
||||||
rateK768 = (1600 - 768) / 8
|
|
||||||
rateK1024 = (1600 - 1024) / 8
|
|
||||||
)
|
|
||||||
|
|
||||||
func new224Generic() *state {
|
|
||||||
return &state{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3}
|
|
||||||
}
|
|
||||||
|
|
||||||
func new256Generic() *state {
|
|
||||||
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3}
|
|
||||||
}
|
|
||||||
|
|
||||||
func new384Generic() *state {
|
|
||||||
return &state{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3}
|
|
||||||
}
|
|
||||||
|
|
||||||
func new512Generic() *state {
|
|
||||||
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLegacyKeccak256 creates a new Keccak-256 hash.
|
|
||||||
//
|
|
||||||
// Only use this function if you require compatibility with an existing cryptosystem
|
|
||||||
// that uses non-standard padding. All other users should use New256 instead.
|
|
||||||
func NewLegacyKeccak256() hash.Hash {
|
|
||||||
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLegacyKeccak512 creates a new Keccak-512 hash.
|
|
||||||
//
|
|
||||||
// Only use this function if you require compatibility with an existing cryptosystem
|
|
||||||
// that uses non-standard padding. All other users should use New512 instead.
|
|
||||||
func NewLegacyKeccak512() hash.Hash {
|
|
||||||
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sum224 returns the SHA3-224 digest of the data.
|
// Sum224 returns the SHA3-224 digest of the data.
|
||||||
func Sum224(data []byte) (digest [28]byte) {
|
//
|
||||||
h := New224()
|
// It is a wrapper for the [sha3.Sum224] function in the standard library.
|
||||||
h.Write(data)
|
//
|
||||||
h.Sum(digest[:0])
|
//go:fix inline
|
||||||
return
|
func Sum224(data []byte) [28]byte {
|
||||||
|
return sha3.Sum224(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sum256 returns the SHA3-256 digest of the data.
|
// Sum256 returns the SHA3-256 digest of the data.
|
||||||
func Sum256(data []byte) (digest [32]byte) {
|
//
|
||||||
h := New256()
|
// It is a wrapper for the [sha3.Sum256] function in the standard library.
|
||||||
h.Write(data)
|
//
|
||||||
h.Sum(digest[:0])
|
//go:fix inline
|
||||||
return
|
func Sum256(data []byte) [32]byte {
|
||||||
|
return sha3.Sum256(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sum384 returns the SHA3-384 digest of the data.
|
// Sum384 returns the SHA3-384 digest of the data.
|
||||||
func Sum384(data []byte) (digest [48]byte) {
|
//
|
||||||
h := New384()
|
// It is a wrapper for the [sha3.Sum384] function in the standard library.
|
||||||
h.Write(data)
|
//
|
||||||
h.Sum(digest[:0])
|
//go:fix inline
|
||||||
return
|
func Sum384(data []byte) [48]byte {
|
||||||
|
return sha3.Sum384(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sum512 returns the SHA3-512 digest of the data.
|
// Sum512 returns the SHA3-512 digest of the data.
|
||||||
func Sum512(data []byte) (digest [64]byte) {
|
//
|
||||||
h := New512()
|
// It is a wrapper for the [sha3.Sum512] function in the standard library.
|
||||||
h.Write(data)
|
//
|
||||||
h.Sum(digest[:0])
|
//go:fix inline
|
||||||
return
|
func Sum512(data []byte) [64]byte {
|
||||||
|
return sha3.Sum512(data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2023 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !gc || purego || !s390x
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
func new224() *state {
|
|
||||||
return new224Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
func new256() *state {
|
|
||||||
return new256Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
func new384() *state {
|
|
||||||
return new384Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
func new512() *state {
|
|
||||||
return new512Generic()
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// Copyright 2015 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 amd64 && !purego && gc
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
// This function is implemented in keccakf_amd64.s.
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func keccakF1600(a *[25]uint64)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,15 +4,46 @@
|
|||||||
|
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
|
// This implementation is only used for NewLegacyKeccak256 and
|
||||||
|
// NewLegacyKeccak512, which are not implemented by crypto/sha3.
|
||||||
|
// All other functions in this package are wrappers around crypto/sha3.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"hash"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
"golang.org/x/sys/cpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dsbyteKeccak = 0b00000001
|
||||||
|
|
||||||
|
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
|
||||||
|
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
|
||||||
|
rateK256 = (1600 - 256) / 8
|
||||||
|
rateK512 = (1600 - 512) / 8
|
||||||
|
rateK1024 = (1600 - 1024) / 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLegacyKeccak256 creates a new Keccak-256 hash.
|
||||||
|
//
|
||||||
|
// Only use this function if you require compatibility with an existing cryptosystem
|
||||||
|
// that uses non-standard padding. All other users should use New256 instead.
|
||||||
|
func NewLegacyKeccak256() hash.Hash {
|
||||||
|
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLegacyKeccak512 creates a new Keccak-512 hash.
|
||||||
|
//
|
||||||
|
// Only use this function if you require compatibility with an existing cryptosystem
|
||||||
|
// that uses non-standard padding. All other users should use New512 instead.
|
||||||
|
func NewLegacyKeccak512() hash.Hash {
|
||||||
|
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
|
||||||
|
}
|
||||||
|
|
||||||
// spongeDirection indicates the direction bytes are flowing through the sponge.
|
// spongeDirection indicates the direction bytes are flowing through the sponge.
|
||||||
type spongeDirection int
|
type spongeDirection int
|
||||||
|
|
||||||
@@ -173,12 +204,9 @@ func (d *state) Sum(in []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
magicSHA3 = "sha\x08"
|
|
||||||
magicShake = "sha\x09"
|
|
||||||
magicCShake = "sha\x0a"
|
|
||||||
magicKeccak = "sha\x0b"
|
magicKeccak = "sha\x0b"
|
||||||
// magic || rate || main state || n || sponge direction
|
// magic || rate || main state || n || sponge direction
|
||||||
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
|
marshaledSize = len(magicKeccak) + 1 + 200 + 1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *state) MarshalBinary() ([]byte, error) {
|
func (d *state) MarshalBinary() ([]byte, error) {
|
||||||
@@ -187,12 +215,6 @@ func (d *state) MarshalBinary() ([]byte, error) {
|
|||||||
|
|
||||||
func (d *state) AppendBinary(b []byte) ([]byte, error) {
|
func (d *state) AppendBinary(b []byte) ([]byte, error) {
|
||||||
switch d.dsbyte {
|
switch d.dsbyte {
|
||||||
case dsbyteSHA3:
|
|
||||||
b = append(b, magicSHA3...)
|
|
||||||
case dsbyteShake:
|
|
||||||
b = append(b, magicShake...)
|
|
||||||
case dsbyteCShake:
|
|
||||||
b = append(b, magicCShake...)
|
|
||||||
case dsbyteKeccak:
|
case dsbyteKeccak:
|
||||||
b = append(b, magicKeccak...)
|
b = append(b, magicKeccak...)
|
||||||
default:
|
default:
|
||||||
@@ -210,12 +232,9 @@ func (d *state) UnmarshalBinary(b []byte) error {
|
|||||||
return errors.New("sha3: invalid hash state")
|
return errors.New("sha3: invalid hash state")
|
||||||
}
|
}
|
||||||
|
|
||||||
magic := string(b[:len(magicSHA3)])
|
magic := string(b[:len(magicKeccak)])
|
||||||
b = b[len(magicSHA3):]
|
b = b[len(magicKeccak):]
|
||||||
switch {
|
switch {
|
||||||
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
|
|
||||||
case magic == magicShake && d.dsbyte == dsbyteShake:
|
|
||||||
case magic == magicCShake && d.dsbyte == dsbyteCShake:
|
|
||||||
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
|
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
|
||||||
default:
|
default:
|
||||||
return errors.New("sha3: invalid hash state identifier")
|
return errors.New("sha3: invalid hash state identifier")
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
// 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 || purego || !gc
|
|
||||||
|
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
|
// This implementation is only used for NewLegacyKeccak256 and
|
||||||
|
// NewLegacyKeccak512, which are not implemented by crypto/sha3.
|
||||||
|
// All other functions in this package are wrappers around crypto/sha3.
|
||||||
|
|
||||||
import "math/bits"
|
import "math/bits"
|
||||||
|
|
||||||
// rc stores the round constants for use in the ι step.
|
// rc stores the round constants for use in the ι step.
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
// Copyright 2017 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
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
// This file contains code for using the 'compute intermediate
|
|
||||||
// message digest' (KIMD) and 'compute last message digest' (KLMD)
|
|
||||||
// instructions to compute SHA-3 and SHAKE hashes on IBM Z.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
// codes represent 7-bit KIMD/KLMD function codes as defined in
|
|
||||||
// the Principles of Operation.
|
|
||||||
type code uint64
|
|
||||||
|
|
||||||
const (
|
|
||||||
// function codes for KIMD/KLMD
|
|
||||||
sha3_224 code = 32
|
|
||||||
sha3_256 = 33
|
|
||||||
sha3_384 = 34
|
|
||||||
sha3_512 = 35
|
|
||||||
shake_128 = 36
|
|
||||||
shake_256 = 37
|
|
||||||
nopad = 0x100
|
|
||||||
)
|
|
||||||
|
|
||||||
// kimd is a wrapper for the 'compute intermediate message digest' instruction.
|
|
||||||
// src must be a multiple of the rate for the given function code.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func kimd(function code, chain *[200]byte, src []byte)
|
|
||||||
|
|
||||||
// klmd is a wrapper for the 'compute last message digest' instruction.
|
|
||||||
// src padding is handled by the instruction.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func klmd(function code, chain *[200]byte, dst, src []byte)
|
|
||||||
|
|
||||||
type asmState struct {
|
|
||||||
a [200]byte // 1600 bit state
|
|
||||||
buf []byte // care must be taken to ensure cap(buf) is a multiple of rate
|
|
||||||
rate int // equivalent to block size
|
|
||||||
storage [3072]byte // underlying storage for buf
|
|
||||||
outputLen int // output length for full security
|
|
||||||
function code // KIMD/KLMD function code
|
|
||||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAsmState(function code) *asmState {
|
|
||||||
var s asmState
|
|
||||||
s.function = function
|
|
||||||
switch function {
|
|
||||||
case sha3_224:
|
|
||||||
s.rate = 144
|
|
||||||
s.outputLen = 28
|
|
||||||
case sha3_256:
|
|
||||||
s.rate = 136
|
|
||||||
s.outputLen = 32
|
|
||||||
case sha3_384:
|
|
||||||
s.rate = 104
|
|
||||||
s.outputLen = 48
|
|
||||||
case sha3_512:
|
|
||||||
s.rate = 72
|
|
||||||
s.outputLen = 64
|
|
||||||
case shake_128:
|
|
||||||
s.rate = 168
|
|
||||||
s.outputLen = 32
|
|
||||||
case shake_256:
|
|
||||||
s.rate = 136
|
|
||||||
s.outputLen = 64
|
|
||||||
default:
|
|
||||||
panic("sha3: unrecognized function code")
|
|
||||||
}
|
|
||||||
|
|
||||||
// limit s.buf size to a multiple of s.rate
|
|
||||||
s.resetBuf()
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *asmState) clone() *asmState {
|
|
||||||
c := *s
|
|
||||||
c.buf = c.storage[:len(s.buf):cap(s.buf)]
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyIntoBuf copies b into buf. It will panic if there is not enough space to
|
|
||||||
// store all of b.
|
|
||||||
func (s *asmState) copyIntoBuf(b []byte) {
|
|
||||||
bufLen := len(s.buf)
|
|
||||||
s.buf = s.buf[:len(s.buf)+len(b)]
|
|
||||||
copy(s.buf[bufLen:], b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetBuf points buf at storage, sets the length to 0 and sets cap to be a
|
|
||||||
// multiple of the rate.
|
|
||||||
func (s *asmState) resetBuf() {
|
|
||||||
max := (cap(s.storage) / s.rate) * s.rate
|
|
||||||
s.buf = s.storage[:0:max]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write (via the embedded io.Writer interface) adds more data to the running hash.
|
|
||||||
// It never returns an error.
|
|
||||||
func (s *asmState) Write(b []byte) (int, error) {
|
|
||||||
if s.state != spongeAbsorbing {
|
|
||||||
panic("sha3: Write after Read")
|
|
||||||
}
|
|
||||||
length := len(b)
|
|
||||||
for len(b) > 0 {
|
|
||||||
if len(s.buf) == 0 && len(b) >= cap(s.buf) {
|
|
||||||
// Hash the data directly and push any remaining bytes
|
|
||||||
// into the buffer.
|
|
||||||
remainder := len(b) % s.rate
|
|
||||||
kimd(s.function, &s.a, b[:len(b)-remainder])
|
|
||||||
if remainder != 0 {
|
|
||||||
s.copyIntoBuf(b[len(b)-remainder:])
|
|
||||||
}
|
|
||||||
return length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.buf) == cap(s.buf) {
|
|
||||||
// flush the buffer
|
|
||||||
kimd(s.function, &s.a, s.buf)
|
|
||||||
s.buf = s.buf[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy as much as we can into the buffer
|
|
||||||
n := len(b)
|
|
||||||
if len(b) > cap(s.buf)-len(s.buf) {
|
|
||||||
n = cap(s.buf) - len(s.buf)
|
|
||||||
}
|
|
||||||
s.copyIntoBuf(b[:n])
|
|
||||||
b = b[n:]
|
|
||||||
}
|
|
||||||
return length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read squeezes an arbitrary number of bytes from the sponge.
|
|
||||||
func (s *asmState) Read(out []byte) (n int, err error) {
|
|
||||||
// The 'compute last message digest' instruction only stores the digest
|
|
||||||
// at the first operand (dst) for SHAKE functions.
|
|
||||||
if s.function != shake_128 && s.function != shake_256 {
|
|
||||||
panic("sha3: can only call Read for SHAKE functions")
|
|
||||||
}
|
|
||||||
|
|
||||||
n = len(out)
|
|
||||||
|
|
||||||
// need to pad if we were absorbing
|
|
||||||
if s.state == spongeAbsorbing {
|
|
||||||
s.state = spongeSqueezing
|
|
||||||
|
|
||||||
// write hash directly into out if possible
|
|
||||||
if len(out)%s.rate == 0 {
|
|
||||||
klmd(s.function, &s.a, out, s.buf) // len(out) may be 0
|
|
||||||
s.buf = s.buf[:0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// write hash into buffer
|
|
||||||
max := cap(s.buf)
|
|
||||||
if max > len(out) {
|
|
||||||
max = (len(out)/s.rate)*s.rate + s.rate
|
|
||||||
}
|
|
||||||
klmd(s.function, &s.a, s.buf[:max], s.buf)
|
|
||||||
s.buf = s.buf[:max]
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(out) > 0 {
|
|
||||||
// flush the buffer
|
|
||||||
if len(s.buf) != 0 {
|
|
||||||
c := copy(out, s.buf)
|
|
||||||
out = out[c:]
|
|
||||||
s.buf = s.buf[c:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// write hash directly into out if possible
|
|
||||||
if len(out)%s.rate == 0 {
|
|
||||||
klmd(s.function|nopad, &s.a, out, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// write hash into buffer
|
|
||||||
s.resetBuf()
|
|
||||||
if cap(s.buf) > len(out) {
|
|
||||||
s.buf = s.buf[:(len(out)/s.rate)*s.rate+s.rate]
|
|
||||||
}
|
|
||||||
klmd(s.function|nopad, &s.a, s.buf, nil)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum appends the current hash to b and returns the resulting slice.
|
|
||||||
// It does not change the underlying hash state.
|
|
||||||
func (s *asmState) Sum(b []byte) []byte {
|
|
||||||
if s.state != spongeAbsorbing {
|
|
||||||
panic("sha3: Sum after Read")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the state to preserve the original.
|
|
||||||
a := s.a
|
|
||||||
|
|
||||||
// Hash the buffer. Note that we don't clear it because we
|
|
||||||
// aren't updating the state.
|
|
||||||
switch s.function {
|
|
||||||
case sha3_224, sha3_256, sha3_384, sha3_512:
|
|
||||||
klmd(s.function, &a, nil, s.buf)
|
|
||||||
return append(b, a[:s.outputLen]...)
|
|
||||||
case shake_128, shake_256:
|
|
||||||
d := make([]byte, s.outputLen, 64)
|
|
||||||
klmd(s.function, &a, d, s.buf)
|
|
||||||
return append(b, d[:s.outputLen]...)
|
|
||||||
default:
|
|
||||||
panic("sha3: unknown function")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the Hash to its initial state.
|
|
||||||
func (s *asmState) Reset() {
|
|
||||||
for i := range s.a {
|
|
||||||
s.a[i] = 0
|
|
||||||
}
|
|
||||||
s.resetBuf()
|
|
||||||
s.state = spongeAbsorbing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the number of bytes Sum will return.
|
|
||||||
func (s *asmState) Size() int {
|
|
||||||
return s.outputLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockSize returns the hash's underlying block size.
|
|
||||||
// The Write method must be able to accept any amount
|
|
||||||
// of data, but it may operate more efficiently if all writes
|
|
||||||
// are a multiple of the block size.
|
|
||||||
func (s *asmState) BlockSize() int {
|
|
||||||
return s.rate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns a copy of the ShakeHash in its current state.
|
|
||||||
func (s *asmState) Clone() ShakeHash {
|
|
||||||
return s.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new224 returns an assembly implementation of SHA3-224 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func new224() hash.Hash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(sha3_224)
|
|
||||||
}
|
|
||||||
return new224Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new256 returns an assembly implementation of SHA3-256 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func new256() hash.Hash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(sha3_256)
|
|
||||||
}
|
|
||||||
return new256Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new384 returns an assembly implementation of SHA3-384 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func new384() hash.Hash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(sha3_384)
|
|
||||||
}
|
|
||||||
return new384Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new512 returns an assembly implementation of SHA3-512 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func new512() hash.Hash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(sha3_512)
|
|
||||||
}
|
|
||||||
return new512Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newShake128 returns an assembly implementation of SHAKE-128 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func newShake128() ShakeHash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(shake_128)
|
|
||||||
}
|
|
||||||
return newShake128Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newShake256 returns an assembly implementation of SHAKE-256 if available,
|
|
||||||
// otherwise it returns a generic implementation.
|
|
||||||
func newShake256() ShakeHash {
|
|
||||||
if cpu.S390X.HasSHA3 {
|
|
||||||
return newAsmState(shake_256)
|
|
||||||
}
|
|
||||||
return newShake256Generic()
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright 2017 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
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// func kimd(function code, chain *[200]byte, src []byte)
|
|
||||||
TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40
|
|
||||||
MOVD function+0(FP), R0
|
|
||||||
MOVD chain+8(FP), R1
|
|
||||||
LMG src+16(FP), R2, R3 // R2=base, R3=len
|
|
||||||
|
|
||||||
continue:
|
|
||||||
WORD $0xB93E0002 // KIMD --, R2
|
|
||||||
BVS continue // continue if interrupted
|
|
||||||
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
|
|
||||||
RET
|
|
||||||
|
|
||||||
// func klmd(function code, chain *[200]byte, dst, src []byte)
|
|
||||||
TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64
|
|
||||||
// TODO: SHAKE support
|
|
||||||
MOVD function+0(FP), R0
|
|
||||||
MOVD chain+8(FP), R1
|
|
||||||
LMG dst+16(FP), R2, R3 // R2=base, R3=len
|
|
||||||
LMG src+40(FP), R4, R5 // R4=base, R5=len
|
|
||||||
|
|
||||||
continue:
|
|
||||||
WORD $0xB93F0024 // KLMD R2, R4
|
|
||||||
BVS continue // continue if interrupted
|
|
||||||
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
|
|
||||||
RET
|
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"encoding"
|
"encoding"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@@ -523,124 +522,3 @@ func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
|
|||||||
t.Errorf("got %x, want %x", got, want)
|
t.Errorf("got %x, want %x", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkPermutationFunction measures the speed of the permutation function
|
|
||||||
// with no input data.
|
|
||||||
func BenchmarkPermutationFunction(b *testing.B) {
|
|
||||||
b.SetBytes(int64(200))
|
|
||||||
var lanes [25]uint64
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
keccakF1600(&lanes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmarkHash tests the speed to hash num buffers of buflen each.
|
|
||||||
func benchmarkHash(b *testing.B, h hash.Hash, size, num int) {
|
|
||||||
b.StopTimer()
|
|
||||||
h.Reset()
|
|
||||||
data := sequentialBytes(size)
|
|
||||||
b.SetBytes(int64(size * num))
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
var state []byte
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
for j := 0; j < num; j++ {
|
|
||||||
h.Write(data)
|
|
||||||
}
|
|
||||||
state = h.Sum(state[:0])
|
|
||||||
}
|
|
||||||
b.StopTimer()
|
|
||||||
h.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmarkShake is specialized to the Shake instances, which don't
|
|
||||||
// require a copy on reading output.
|
|
||||||
func benchmarkShake(b *testing.B, h ShakeHash, size, num int) {
|
|
||||||
b.StopTimer()
|
|
||||||
h.Reset()
|
|
||||||
data := sequentialBytes(size)
|
|
||||||
d := make([]byte, 32)
|
|
||||||
|
|
||||||
b.SetBytes(int64(size * num))
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
h.Reset()
|
|
||||||
for j := 0; j < num; j++ {
|
|
||||||
h.Write(data)
|
|
||||||
}
|
|
||||||
h.Read(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) }
|
|
||||||
func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) }
|
|
||||||
func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) }
|
|
||||||
func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) }
|
|
||||||
|
|
||||||
func BenchmarkShake128_MTU(b *testing.B) { benchmarkShake(b, NewShake128(), 1350, 1) }
|
|
||||||
func BenchmarkShake256_MTU(b *testing.B) { benchmarkShake(b, NewShake256(), 1350, 1) }
|
|
||||||
func BenchmarkShake256_16x(b *testing.B) { benchmarkShake(b, NewShake256(), 16, 1024) }
|
|
||||||
func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewShake256(), 1024, 1024) }
|
|
||||||
|
|
||||||
func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) }
|
|
||||||
|
|
||||||
func Example_sum() {
|
|
||||||
buf := []byte("some data to hash")
|
|
||||||
// A hash needs to be 64 bytes long to have 256-bit collision resistance.
|
|
||||||
h := make([]byte, 64)
|
|
||||||
// Compute a 64-byte hash of buf and put it in h.
|
|
||||||
ShakeSum256(h, buf)
|
|
||||||
fmt.Printf("%x\n", h)
|
|
||||||
// Output: 0f65fe41fc353e52c55667bb9e2b27bfcc8476f2c413e9437d272ee3194a4e3146d05ec04a25d16b8f577c19b82d16b1424c3e022e783d2b4da98de3658d363d
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_mac() {
|
|
||||||
k := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long")
|
|
||||||
buf := []byte("and this is some data to authenticate")
|
|
||||||
// A MAC with 32 bytes of output has 256-bit security strength -- if you use at least a 32-byte-long key.
|
|
||||||
h := make([]byte, 32)
|
|
||||||
d := NewShake256()
|
|
||||||
// Write the key into the hash.
|
|
||||||
d.Write(k)
|
|
||||||
// Now write the data.
|
|
||||||
d.Write(buf)
|
|
||||||
// Read 32 bytes of output from the hash into h.
|
|
||||||
d.Read(h)
|
|
||||||
fmt.Printf("%x\n", h)
|
|
||||||
// Output: 78de2974bd2711d5549ffd32b753ef0f5fa80a0db2556db60f0987eb8a9218ff
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNewCShake256() {
|
|
||||||
out := make([]byte, 32)
|
|
||||||
msg := []byte("The quick brown fox jumps over the lazy dog")
|
|
||||||
|
|
||||||
// Example 1: Simple cshake
|
|
||||||
c1 := NewCShake256([]byte("NAME"), []byte("Partition1"))
|
|
||||||
c1.Write(msg)
|
|
||||||
c1.Read(out)
|
|
||||||
fmt.Println(hex.EncodeToString(out))
|
|
||||||
|
|
||||||
// Example 2: Different customization string produces different digest
|
|
||||||
c1 = NewCShake256([]byte("NAME"), []byte("Partition2"))
|
|
||||||
c1.Write(msg)
|
|
||||||
c1.Read(out)
|
|
||||||
fmt.Println(hex.EncodeToString(out))
|
|
||||||
|
|
||||||
// Example 3: Longer output length produces longer digest
|
|
||||||
out = make([]byte, 64)
|
|
||||||
c1 = NewCShake256([]byte("NAME"), []byte("Partition1"))
|
|
||||||
c1.Write(msg)
|
|
||||||
c1.Read(out)
|
|
||||||
fmt.Println(hex.EncodeToString(out))
|
|
||||||
|
|
||||||
// Example 4: Next read produces different result
|
|
||||||
c1.Read(out)
|
|
||||||
fmt.Println(hex.EncodeToString(out))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
//a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b
|
|
||||||
//a8db03e71f3e4da5c4eee9d28333cdd355f51cef3c567e59be5beb4ecdbb28f0
|
|
||||||
//a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b9dd98c6ee866ca7dc5a39d53e960f400bcd5a19c8a2d6ec6459f63696543a0d8
|
|
||||||
//85e73a72228d08b46515553ca3a29d47df3047e5d84b12d6c2c63e579f4fd1105716b7838e92e981863907f434bfd4443c9e56ea09da998d2f9b47db71988109
|
|
||||||
}
|
|
||||||
|
|||||||
+49
-123
@@ -4,24 +4,10 @@
|
|||||||
|
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
// This file defines the ShakeHash interface, and provides
|
|
||||||
// functions for creating SHAKE and cSHAKE instances, as well as utility
|
|
||||||
// functions for hashing bytes to arbitrary-length output.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// SHAKE implementation is based on FIPS PUB 202 [1]
|
|
||||||
// cSHAKE implementations is based on NIST SP 800-185 [2]
|
|
||||||
//
|
|
||||||
// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
|
|
||||||
// [2] https://doi.org/10.6028/NIST.SP.800-185
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/sha3"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"math/bits"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShakeHash defines the interface to hash functions that support
|
// ShakeHash defines the interface to hash functions that support
|
||||||
@@ -32,7 +18,7 @@ type ShakeHash interface {
|
|||||||
hash.Hash
|
hash.Hash
|
||||||
|
|
||||||
// Read reads more output from the hash; reading affects the hash's
|
// Read reads more output from the hash; reading affects the hash's
|
||||||
// state. (ShakeHash.Read is thus very different from Hash.Sum)
|
// state. (ShakeHash.Read is thus very different from Hash.Sum.)
|
||||||
// It never returns an error, but subsequent calls to Write or Sum
|
// It never returns an error, but subsequent calls to Write or Sum
|
||||||
// will panic.
|
// will panic.
|
||||||
io.Reader
|
io.Reader
|
||||||
@@ -41,115 +27,18 @@ type ShakeHash interface {
|
|||||||
Clone() ShakeHash
|
Clone() ShakeHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// cSHAKE specific context
|
|
||||||
type cshakeState struct {
|
|
||||||
*state // SHA-3 state context and Read/Write operations
|
|
||||||
|
|
||||||
// initBlock is the cSHAKE specific initialization set of bytes. It is initialized
|
|
||||||
// by newCShake function and stores concatenation of N followed by S, encoded
|
|
||||||
// by the method specified in 3.3 of [1].
|
|
||||||
// It is stored here in order for Reset() to be able to put context into
|
|
||||||
// initial state.
|
|
||||||
initBlock []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytepad(data []byte, rate int) []byte {
|
|
||||||
out := make([]byte, 0, 9+len(data)+rate-1)
|
|
||||||
out = append(out, leftEncode(uint64(rate))...)
|
|
||||||
out = append(out, data...)
|
|
||||||
if padlen := rate - len(out)%rate; padlen < rate {
|
|
||||||
out = append(out, make([]byte, padlen)...)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftEncode(x uint64) []byte {
|
|
||||||
// Let n be the smallest positive integer for which 2^(8n) > x.
|
|
||||||
n := (bits.Len64(x) + 7) / 8
|
|
||||||
if n == 0 {
|
|
||||||
n = 1
|
|
||||||
}
|
|
||||||
// Return n || x with n as a byte and x an n bytes in big-endian order.
|
|
||||||
b := make([]byte, 9)
|
|
||||||
binary.BigEndian.PutUint64(b[1:], x)
|
|
||||||
b = b[9-n-1:]
|
|
||||||
b[0] = byte(n)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
|
|
||||||
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
|
|
||||||
c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes
|
|
||||||
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...)
|
|
||||||
c.initBlock = append(c.initBlock, N...)
|
|
||||||
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...)
|
|
||||||
c.initBlock = append(c.initBlock, S...)
|
|
||||||
c.Write(bytepad(c.initBlock, c.rate))
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the hash to initial state.
|
|
||||||
func (c *cshakeState) Reset() {
|
|
||||||
c.state.Reset()
|
|
||||||
c.Write(bytepad(c.initBlock, c.rate))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns copy of a cSHAKE context within its current state.
|
|
||||||
func (c *cshakeState) Clone() ShakeHash {
|
|
||||||
b := make([]byte, len(c.initBlock))
|
|
||||||
copy(b, c.initBlock)
|
|
||||||
return &cshakeState{state: c.clone(), initBlock: b}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns copy of SHAKE context within its current state.
|
|
||||||
func (c *state) Clone() ShakeHash {
|
|
||||||
return c.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cshakeState) MarshalBinary() ([]byte, error) {
|
|
||||||
return c.AppendBinary(make([]byte, 0, marshaledSize+len(c.initBlock)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cshakeState) AppendBinary(b []byte) ([]byte, error) {
|
|
||||||
b, err := c.state.AppendBinary(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b = append(b, c.initBlock...)
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cshakeState) UnmarshalBinary(b []byte) error {
|
|
||||||
if len(b) <= marshaledSize {
|
|
||||||
return errors.New("sha3: invalid hash state")
|
|
||||||
}
|
|
||||||
if err := c.state.UnmarshalBinary(b[:marshaledSize]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.initBlock = bytes.Clone(b[marshaledSize:])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
|
// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
|
||||||
// Its generic security strength is 128 bits against all attacks if at
|
// Its generic security strength is 128 bits against all attacks if at
|
||||||
// least 32 bytes of its output are used.
|
// least 32 bytes of its output are used.
|
||||||
func NewShake128() ShakeHash {
|
func NewShake128() ShakeHash {
|
||||||
return newShake128()
|
return &shakeWrapper{sha3.NewSHAKE128(), 32, false, sha3.NewSHAKE128}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
||||||
// Its generic security strength is 256 bits against all attacks if
|
// Its generic security strength is 256 bits against all attacks if
|
||||||
// at least 64 bytes of its output are used.
|
// at least 64 bytes of its output are used.
|
||||||
func NewShake256() ShakeHash {
|
func NewShake256() ShakeHash {
|
||||||
return newShake256()
|
return &shakeWrapper{sha3.NewSHAKE256(), 64, false, sha3.NewSHAKE256}
|
||||||
}
|
|
||||||
|
|
||||||
func newShake128Generic() *state {
|
|
||||||
return &state{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newShake256Generic() *state {
|
|
||||||
return &state{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
|
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
|
||||||
@@ -159,10 +48,9 @@ func newShake256Generic() *state {
|
|||||||
// computations on same input with different S yield unrelated outputs.
|
// computations on same input with different S yield unrelated outputs.
|
||||||
// When N and S are both empty, this is equivalent to NewShake128.
|
// When N and S are both empty, this is equivalent to NewShake128.
|
||||||
func NewCShake128(N, S []byte) ShakeHash {
|
func NewCShake128(N, S []byte) ShakeHash {
|
||||||
if len(N) == 0 && len(S) == 0 {
|
return &shakeWrapper{sha3.NewCSHAKE128(N, S), 32, false, func() *sha3.SHAKE {
|
||||||
return NewShake128()
|
return sha3.NewCSHAKE128(N, S)
|
||||||
}
|
}}
|
||||||
return newCShake(N, S, rateK256, 32, dsbyteCShake)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
|
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
|
||||||
@@ -172,10 +60,9 @@ func NewCShake128(N, S []byte) ShakeHash {
|
|||||||
// computations on same input with different S yield unrelated outputs.
|
// computations on same input with different S yield unrelated outputs.
|
||||||
// When N and S are both empty, this is equivalent to NewShake256.
|
// When N and S are both empty, this is equivalent to NewShake256.
|
||||||
func NewCShake256(N, S []byte) ShakeHash {
|
func NewCShake256(N, S []byte) ShakeHash {
|
||||||
if len(N) == 0 && len(S) == 0 {
|
return &shakeWrapper{sha3.NewCSHAKE256(N, S), 64, false, func() *sha3.SHAKE {
|
||||||
return NewShake256()
|
return sha3.NewCSHAKE256(N, S)
|
||||||
}
|
}}
|
||||||
return newCShake(N, S, rateK512, 64, dsbyteCShake)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShakeSum128 writes an arbitrary-length digest of data into hash.
|
// ShakeSum128 writes an arbitrary-length digest of data into hash.
|
||||||
@@ -191,3 +78,42 @@ func ShakeSum256(hash, data []byte) {
|
|||||||
h.Write(data)
|
h.Write(data)
|
||||||
h.Read(hash)
|
h.Read(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shakeWrapper adds the Size, Sum, and Clone methods to a sha3.SHAKE
|
||||||
|
// to implement the ShakeHash interface.
|
||||||
|
type shakeWrapper struct {
|
||||||
|
*sha3.SHAKE
|
||||||
|
outputLen int
|
||||||
|
squeezing bool
|
||||||
|
newSHAKE func() *sha3.SHAKE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *shakeWrapper) Read(p []byte) (n int, err error) {
|
||||||
|
w.squeezing = true
|
||||||
|
return w.SHAKE.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *shakeWrapper) Clone() ShakeHash {
|
||||||
|
s := w.newSHAKE()
|
||||||
|
b, err := w.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // unreachable
|
||||||
|
}
|
||||||
|
if err := s.UnmarshalBinary(b); err != nil {
|
||||||
|
panic(err) // unreachable
|
||||||
|
}
|
||||||
|
return &shakeWrapper{s, w.outputLen, w.squeezing, w.newSHAKE}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *shakeWrapper) Size() int { return w.outputLen }
|
||||||
|
|
||||||
|
func (w *shakeWrapper) Sum(b []byte) []byte {
|
||||||
|
if w.squeezing {
|
||||||
|
panic("sha3: Sum after Read")
|
||||||
|
}
|
||||||
|
out := make([]byte, w.outputLen)
|
||||||
|
// Clone the state so that we don't affect future Write calls.
|
||||||
|
s := w.Clone()
|
||||||
|
s.Read(out)
|
||||||
|
return append(b, out...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright 2023 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !gc || purego || !s390x
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
func newShake128() *state {
|
|
||||||
return newShake128Generic()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newShake256() *state {
|
|
||||||
return newShake256Generic()
|
|
||||||
}
|
|
||||||
+11
-2
@@ -430,8 +430,9 @@ func (c *client) List() ([]*Key, error) {
|
|||||||
return keys, nil
|
return keys, nil
|
||||||
case *failureAgentMsg:
|
case *failureAgentMsg:
|
||||||
return nil, errors.New("agent: failed to list keys")
|
return nil, errors.New("agent: failed to list keys")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("agent: failed to list keys, unexpected message type %T", msg)
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||||
@@ -462,8 +463,9 @@ func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFl
|
|||||||
return &sig, nil
|
return &sig, nil
|
||||||
case *failureAgentMsg:
|
case *failureAgentMsg:
|
||||||
return nil, errors.New("agent: failed to sign challenge")
|
return nil, errors.New("agent: failed to sign challenge")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("agent: failed to sign challenge, unexpected message type %T", msg)
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal parses an agent message in packet, returning the parsed
|
// unmarshal parses an agent message in packet, returning the parsed
|
||||||
@@ -661,6 +663,13 @@ func (c *client) Add(key AddedKey) error {
|
|||||||
constraints = append(constraints, agentConstrainConfirm)
|
constraints = append(constraints, agentConstrainConfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ext := range key.ConstraintExtensions {
|
||||||
|
constraints = append(constraints, ssh.Marshal(constrainExtensionAgentMsg{
|
||||||
|
ExtensionName: ext.ExtensionName,
|
||||||
|
ExtensionDetails: ext.ExtensionDetails,
|
||||||
|
})...)
|
||||||
|
}
|
||||||
|
|
||||||
cert := key.Certificate
|
cert := key.Certificate
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||||
|
|||||||
+113
-1
@@ -7,6 +7,7 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -39,7 +40,11 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(bin, "-s")
|
cmd := exec.Command(bin, "-s")
|
||||||
cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior.
|
cmd.Env = []string{
|
||||||
|
// ssh-agent creates ~/.ssh and ~/.ssh/agent;
|
||||||
|
// ensure a writeable home directory.
|
||||||
|
"HOME=" + t.TempDir(),
|
||||||
|
} // Do not let the user's environment influence ssh-agent behavior.
|
||||||
cmd.Stderr = new(bytes.Buffer)
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -347,6 +352,53 @@ func TestServerResponseTooLarge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInvalidResponses(t *testing.T) {
|
||||||
|
a, b, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer func() { <-done }()
|
||||||
|
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
agent := NewClient(a)
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
resp := []byte{agentSuccess}
|
||||||
|
msg := make([]byte, 4+len(resp))
|
||||||
|
binary.BigEndian.PutUint32(msg[:4], uint32(len(resp)))
|
||||||
|
copy(msg[4:], resp)
|
||||||
|
|
||||||
|
if _, err := b.Write(msg); err != nil {
|
||||||
|
t.Errorf("unexpected error sending agent reply: %v", err)
|
||||||
|
b.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := b.Write(msg); err != nil {
|
||||||
|
t.Errorf("unexpected error sending agent reply: %v", err)
|
||||||
|
b.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err = agent.List()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error expected")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "failed to list keys, unexpected message type") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
_, err = agent.Sign(testPublicKeys["rsa"], []byte("message"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error expected")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "failed to sign challenge, unexpected message type") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestAuth(t *testing.T) {
|
||||||
agent, _, cleanup := startOpenSSHAgent(t)
|
agent, _, cleanup := startOpenSSHAgent(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@@ -542,3 +594,63 @@ func TestAgentExtensions(t *testing.T) {
|
|||||||
t.Fatal("should have gotten agent extension failure")
|
t.Fatal("should have gotten agent extension failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// capturingAgent records the AddedKey observed on the server side of the
|
||||||
|
// agent protocol. It forwards to a keyring with constraint fields stripped,
|
||||||
|
// so the keyring's own rejection of unsupported constraints does not
|
||||||
|
// interfere with what this test is measuring: the client-side wire
|
||||||
|
// serialization of ConstraintExtensions.
|
||||||
|
type capturingAgent struct {
|
||||||
|
Agent
|
||||||
|
lastAdd AddedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCapturingAgent() *capturingAgent {
|
||||||
|
return &capturingAgent{Agent: NewKeyring()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *capturingAgent) Add(key AddedKey) error {
|
||||||
|
a.lastAdd = key
|
||||||
|
stripped := key
|
||||||
|
stripped.ConstraintExtensions = nil
|
||||||
|
stripped.ConfirmBeforeUse = false
|
||||||
|
return a.Agent.Add(stripped)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddConstraintExtensionsWireFormat verifies that client.Add serializes
|
||||||
|
// ConstraintExtensions into the SSH_AGENTC_ADD_IDENTITY payload and the
|
||||||
|
// server deserializes them back into the AddedKey delivered to the backend.
|
||||||
|
// Regressions in the client marshal loop (missing, swapped fields, wrong
|
||||||
|
// framing) would be invisible to a keyring-based rejection test, which
|
||||||
|
// signals only "extensions were present", not "the right ones arrived".
|
||||||
|
func TestAddConstraintExtensionsWireFormat(t *testing.T) {
|
||||||
|
capturing := newCapturingAgent()
|
||||||
|
client, cleanup := startAgent(t, capturing)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
constraints := []ConstraintExtension{
|
||||||
|
{ExtensionName: "ext-one@example.com", ExtensionDetails: []byte("details-one")},
|
||||||
|
{ExtensionName: "ext-two@example.com", ExtensionDetails: []byte("\x00\x01\x02\xff")},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Add(AddedKey{
|
||||||
|
PrivateKey: testPrivateKeys["rsa"],
|
||||||
|
Comment: "wire-format-test",
|
||||||
|
ConstraintExtensions: constraints,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("client.Add: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := capturing.lastAdd.ConstraintExtensions
|
||||||
|
if len(got) != len(constraints) {
|
||||||
|
t.Fatalf("server received %d extensions, want %d", len(got), len(constraints))
|
||||||
|
}
|
||||||
|
for i, want := range constraints {
|
||||||
|
if got[i].ExtensionName != want.ExtensionName {
|
||||||
|
t.Errorf("extension[%d] name: got %q, want %q", i, got[i].ExtensionName, want.ExtensionName)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got[i].ExtensionDetails, want.ExtensionDetails) {
|
||||||
|
t.Errorf("extension[%d] details: got %x, want %x", i, got[i].ExtensionDetails, want.ExtensionDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+19
-6
@@ -32,8 +32,10 @@ type keyring struct {
|
|||||||
|
|
||||||
var errLocked = errors.New("agent: locked")
|
var errLocked = errors.New("agent: locked")
|
||||||
|
|
||||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
// NewKeyring returns an Agent that holds keys in memory. It is safe for
|
||||||
// for concurrent use by multiple goroutines.
|
// concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// The returned Agent only supports the "lifetime" constraint.
|
||||||
func NewKeyring() Agent {
|
func NewKeyring() Agent {
|
||||||
return &keyring{}
|
return &keyring{}
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ func (r *keyring) Unlock(passphrase []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
||||||
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
|
// with a lifetimesecs constraint and seconds >= lifetimesecs seconds have
|
||||||
// elapsed, it is removed. The caller *must* be holding the keyring mutex.
|
// elapsed, it is removed. The caller *must* be holding the keyring mutex.
|
||||||
func (r *keyring) expireKeysLocked() {
|
func (r *keyring) expireKeysLocked() {
|
||||||
for _, k := range r.keys {
|
for _, k := range r.keys {
|
||||||
@@ -143,15 +145,26 @@ func (r *keyring) List() ([]*Key, error) {
|
|||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert adds a private key to the keyring. If a certificate
|
// Add adds a private key to the keyring. If a certificate is given, that
|
||||||
// is given, that certificate is added as public key. Note that
|
// certificate is added as public key.
|
||||||
// any constraints given are ignored.
|
//
|
||||||
|
// Add returns an error if key contains ConstraintExtensions or
|
||||||
|
// ConfirmBeforeUse.
|
||||||
func (r *keyring) Add(key AddedKey) error {
|
func (r *keyring) Add(key AddedKey) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
if r.locked {
|
if r.locked {
|
||||||
return errLocked
|
return errLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.ConfirmBeforeUse {
|
||||||
|
return errors.New("agent: confirm before use constraint is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(key.ConstraintExtensions) > 0 {
|
||||||
|
return errors.New("agent: constraint extensions are present but not supported")
|
||||||
|
}
|
||||||
|
|
||||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(listedKeys) != len(expectedKeys) {
|
if len(listedKeys) != len(expectedKeys) {
|
||||||
t.Fatalf("expeted %d key, got %d", len(expectedKeys), len(listedKeys))
|
t.Fatalf("expected %d key, got %d", len(expectedKeys), len(listedKeys))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actualKeys := make(map[string]bool)
|
actualKeys := make(map[string]bool)
|
||||||
@@ -120,3 +120,42 @@ func TestAddDuplicateKey(t *testing.T) {
|
|||||||
t.Fatal("key with the updated comment not found")
|
t.Fatal("key with the updated comment not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddKeyWithConstraints(t *testing.T) {
|
||||||
|
// Verifies the keyring refuses keys carrying constraint extensions it
|
||||||
|
// cannot enforce.
|
||||||
|
agent, cleanup := startKeyringAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
constraints := []ConstraintExtension{
|
||||||
|
{
|
||||||
|
ExtensionName: "extension1",
|
||||||
|
ExtensionDetails: []byte("details1"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key := testPrivateKeys["rsa"]
|
||||||
|
|
||||||
|
err := agent.Add(AddedKey{
|
||||||
|
PrivateKey: key,
|
||||||
|
ConstraintExtensions: constraints,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("adding a key with unsupported constraints succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddKeyWithConfirmBeforeUse(t *testing.T) {
|
||||||
|
agent, cleanup := startKeyringAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
key := testPrivateKeys["rsa"]
|
||||||
|
|
||||||
|
err := agent.Add(AddedKey{
|
||||||
|
PrivateKey: key,
|
||||||
|
ConfirmBeforeUse: true,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("adding a key with confirm before use constraint succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
-1
@@ -36,7 +36,7 @@ func (s *server) processRequestBytes(reqData []byte) []byte {
|
|||||||
return []byte{agentFailure}
|
return []byte{agentFailure}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && rep == nil {
|
if rep == nil {
|
||||||
return []byte{agentSuccess}
|
return []byte{agentSuccess}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +203,9 @@ func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse
|
|||||||
for len(constraints) != 0 {
|
for len(constraints) != 0 {
|
||||||
switch constraints[0] {
|
switch constraints[0] {
|
||||||
case agentConstrainLifetime:
|
case agentConstrainLifetime:
|
||||||
|
if len(constraints) < 5 {
|
||||||
|
return 0, false, nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
||||||
constraints = constraints[5:]
|
constraints = constraints[5:]
|
||||||
case agentConstrainConfirm:
|
case agentConstrainConfirm:
|
||||||
@@ -267,6 +270,9 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||||
|
}
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
|
|
||||||
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||||
@@ -333,6 +339,9 @@ func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||||
|
}
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
pseudorand "math/rand"
|
pseudorand "math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -258,9 +259,47 @@ func TestParseConstraints(t *testing.T) {
|
|||||||
t.Errorf("got extension %v, want %v", extensions, expect)
|
t.Errorf("got extension %v, want %v", extensions, expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Malformed Constraint
|
||||||
|
_, _, _, err = parseConstraints([]byte{1})
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Errorf("got %v, want %v", err, io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
|
|
||||||
// Test Unknown Constraint
|
// Test Unknown Constraint
|
||||||
_, _, _, err = parseConstraints([]byte{128})
|
_, _, _, err = parseConstraints([]byte{128})
|
||||||
if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
|
if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEd25519KeyShortPanic(t *testing.T) {
|
||||||
|
msg := ssh.Marshal(ed25519KeyMsg{
|
||||||
|
Type: ssh.KeyAlgoED25519,
|
||||||
|
Pub: []byte{1, 2, 3},
|
||||||
|
Priv: []byte{1, 2, 3, 4, 5},
|
||||||
|
})
|
||||||
|
|
||||||
|
a, b, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- ServeAgent(NewKeyring(), a) }()
|
||||||
|
|
||||||
|
c := NewClient(b)
|
||||||
|
_, err = c.(*client).call(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := c.List()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("agent died: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Error("short ed25519 key was accepted into keyring")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+17
-1
@@ -348,6 +348,9 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
|||||||
if cert.CertType != HostCert {
|
if cert.CertType != HostCert {
|
||||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||||
}
|
}
|
||||||
|
if c.IsHostAuthority == nil {
|
||||||
|
return errors.New("ssh: cannot verify certificate, IsHostAuthority not set")
|
||||||
|
}
|
||||||
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||||
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||||
}
|
}
|
||||||
@@ -375,6 +378,9 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||||||
if cert.CertType != UserCert {
|
if cert.CertType != UserCert {
|
||||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||||
}
|
}
|
||||||
|
if c.IsUserAuthority == nil {
|
||||||
|
return nil, errors.New("ssh: cannot verify certificate, IsUserAuthority not set")
|
||||||
|
}
|
||||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
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")
|
||||||
}
|
}
|
||||||
@@ -438,7 +444,17 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|||||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||||
return fmt.Errorf("ssh: cert has expired")
|
return fmt.Errorf("ssh: cert has expired")
|
||||||
}
|
}
|
||||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
// Match OpenSSH: the SK user-presence flag is never enforced on a
|
||||||
|
// certificate's CA signature. OpenSSH calls sshkey_verify with
|
||||||
|
// detailsp==NULL in sshkey.c:cert_parse, so the UP/UV flags are
|
||||||
|
// not even extracted. The UP bit on a CA signature reflects the
|
||||||
|
// CA operator's presence at signing time, which has no bearing on
|
||||||
|
// whether the user being authenticated is present now; enforcing
|
||||||
|
// it here would only break interop with certificates issued by
|
||||||
|
// non-interactive SK CAs. skKeyWithoutUP is a no-op for non-SK
|
||||||
|
// keys (the common case).
|
||||||
|
caKey := skKeyWithoutUP(cert.SignatureKey)
|
||||||
|
if err := caKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+102
-5
@@ -9,8 +9,10 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -128,6 +130,103 @@ func TestValidateCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// signSKCert builds an SK-ECDSA signature over cert.bytesForSigning()
|
||||||
|
// using caKey as the simulated hardware token. The SK signing flags
|
||||||
|
// byte (UP bit and others) is caller-controlled so tests can exercise
|
||||||
|
// the user-presence enforcement paths. caApp is the SK application
|
||||||
|
// string (typically "ssh:").
|
||||||
|
func signSKCert(t *testing.T, cert *Certificate, caKey *ecdsa.PrivateKey, caApp string, flags byte) {
|
||||||
|
t.Helper()
|
||||||
|
cert.SignatureKey = &skECDSAPublicKey{
|
||||||
|
application: caApp,
|
||||||
|
PublicKey: caKey.PublicKey,
|
||||||
|
}
|
||||||
|
cert.Nonce = make([]byte, 32)
|
||||||
|
if _, err := rand.Read(cert.Nonce); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(caApp))
|
||||||
|
appDigest := h.Sum(nil)
|
||||||
|
h.Reset()
|
||||||
|
h.Write(cert.bytesForSigning())
|
||||||
|
dataDigest := h.Sum(nil)
|
||||||
|
|
||||||
|
var counter uint32 = 1
|
||||||
|
blob := struct {
|
||||||
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
MessageDigest []byte `ssh:"rest"`
|
||||||
|
}{appDigest, flags, counter, dataDigest}
|
||||||
|
h.Reset()
|
||||||
|
h.Write(Marshal(blob))
|
||||||
|
digest := h.Sum(nil)
|
||||||
|
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, caKey, digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cert.Signature = &Signature{
|
||||||
|
Format: KeyAlgoSKECDSA256,
|
||||||
|
Blob: Marshal(struct{ R, S *big.Int }{r, s}),
|
||||||
|
Rest: Marshal(struct {
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
}{flags, counter}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCheckCertSKAuthorityNoUPRequired pins the OpenSSH-parity
|
||||||
|
// behavior of CertChecker.CheckCert for SK CA signatures: a
|
||||||
|
// certificate signed by an SK CA that produced a UP=0 signature is
|
||||||
|
// accepted, because the UP bit on a CA signature has no bearing on
|
||||||
|
// the current user authentication (see sshkey.c:cert_parse, which
|
||||||
|
// passes detailsp==NULL to sshkey_verify). Without this, certs issued
|
||||||
|
// by non-interactive SK CAs would fail to verify.
|
||||||
|
func TestCheckCertSKAuthorityNoUPRequired(t *testing.T) {
|
||||||
|
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
userPub, err := NewPublicKey(&userKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mkCert := func(flags byte) *Certificate {
|
||||||
|
c := &Certificate{
|
||||||
|
CertType: UserCert,
|
||||||
|
Key: userPub,
|
||||||
|
ValidBefore: CertTimeInfinity,
|
||||||
|
ValidPrincipals: []string{"user"},
|
||||||
|
}
|
||||||
|
signSKCert(t, c, caKey, "ssh:", flags)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
checker := CertChecker{
|
||||||
|
IsUserAuthority: func(k PublicKey) bool {
|
||||||
|
caSK := &skECDSAPublicKey{application: "ssh:", PublicKey: caKey.PublicKey}
|
||||||
|
return bytes.Equal(k.Marshal(), caSK.Marshal())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both UP=0 and UP=1 CA signatures verify: UP is not enforced
|
||||||
|
// here, matching OpenSSH.
|
||||||
|
if err := checker.CheckCert("user", mkCert(0)); err != nil {
|
||||||
|
t.Errorf("UP=0 CA signature must verify (OpenSSH parity): %v", err)
|
||||||
|
}
|
||||||
|
if err := checker.CheckCert("user", mkCert(flagUserPresence)); err != nil {
|
||||||
|
t.Errorf("UP=1 CA signature must verify: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateCertTime(t *testing.T) {
|
func TestValidateCertTime(t *testing.T) {
|
||||||
cert := Certificate{
|
cert := Certificate{
|
||||||
ValidPrincipals: []string{"user"},
|
ValidPrincipals: []string{"user"},
|
||||||
@@ -330,15 +429,13 @@ func TestCertTypes(t *testing.T) {
|
|||||||
go NewServerConn(c1, conf)
|
go NewServerConn(c1, conf)
|
||||||
|
|
||||||
priv := m.signer
|
priv := m.signer
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error generating ssh pubkey: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert := &Certificate{
|
cert := &Certificate{
|
||||||
CertType: UserCert,
|
CertType: UserCert,
|
||||||
Key: priv.PublicKey(),
|
Key: priv.PublicKey(),
|
||||||
}
|
}
|
||||||
cert.SignCert(rand.Reader, priv)
|
if err := cert.SignCert(rand.Reader, priv); err != nil {
|
||||||
|
t.Fatalf("error signing certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
certSigner, err := NewCertSigner(cert, priv)
|
certSigner, err := NewCertSigner(cert, priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+59
-6
@@ -11,6 +11,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -131,11 +132,17 @@ func (r RejectionReason) String() string {
|
|||||||
return fmt.Sprintf("unknown reason %d", int(r))
|
return fmt.Sprintf("unknown reason %d", int(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a uint32, b int) uint32 {
|
// minPayloadSize returns min(limit, length) clamped to a uint32. It is used
|
||||||
if a < uint32(b) {
|
// to compute the size of the next channel data packet from the remaining
|
||||||
return a
|
// payload. The comparison is done in int64 because length is an int — on
|
||||||
|
// 64-bit systems len(data) can exceed 2^32, and a direct uint32(length)
|
||||||
|
// cast would silently truncate to 0 at every multiple of 2^32, causing
|
||||||
|
// WriteExtended's loop to spin without making progress.
|
||||||
|
func minPayloadSize(limit uint32, length int) uint32 {
|
||||||
|
if int64(length) > int64(limit) {
|
||||||
|
return limit
|
||||||
}
|
}
|
||||||
return uint32(b)
|
return uint32(length)
|
||||||
}
|
}
|
||||||
|
|
||||||
type channelDirection uint8
|
type channelDirection uint8
|
||||||
@@ -177,6 +184,12 @@ type channel struct {
|
|||||||
// with WantReply=true outstanding. This lock is held by a
|
// with WantReply=true outstanding. This lock is held by a
|
||||||
// goroutine that has such an outgoing request pending.
|
// goroutine that has such an outgoing request pending.
|
||||||
sentRequestMu sync.Mutex
|
sentRequestMu sync.Mutex
|
||||||
|
// sentRequestPending is set to true while a SendRequest call with
|
||||||
|
// WantReply=true is in flight. handlePacket uses it as a gate: responses
|
||||||
|
// arriving while no request is pending are dropped to prevent a
|
||||||
|
// misbehaving peer from stalling the mux read loop by filling ch.msg
|
||||||
|
// with unsolicited channelRequestSuccess/Failure messages.
|
||||||
|
sentRequestPending atomic.Bool
|
||||||
|
|
||||||
incomingRequests chan *Request
|
incomingRequests chan *Request
|
||||||
|
|
||||||
@@ -251,7 +264,7 @@ func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err e
|
|||||||
ch.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
|
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
space := min(ch.maxRemotePayload, len(data))
|
space := minPayloadSize(ch.maxRemotePayload, len(data))
|
||||||
if space, err = ch.remoteWin.reserve(space); err != nil {
|
if space, err = ch.remoteWin.reserve(space); err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
@@ -460,6 +473,18 @@ func (ch *channel) handlePacket(packet []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch.incomingRequests <- &req
|
ch.incomingRequests <- &req
|
||||||
|
case *channelRequestSuccessMsg, *channelRequestFailureMsg:
|
||||||
|
// Drop responses that arrive when no SendRequest is waiting, to
|
||||||
|
// prevent a malicious peer from filling ch.msg and stalling the
|
||||||
|
// mux read loop. The non-blocking send additionally protects the
|
||||||
|
// loop if a well-behaved caller is slow to read.
|
||||||
|
if !ch.sentRequestPending.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case ch.msg <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ch.msg <- msg
|
ch.msg <- msg
|
||||||
}
|
}
|
||||||
@@ -530,7 +555,17 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
|
|||||||
Language: "en",
|
Language: "en",
|
||||||
}
|
}
|
||||||
ch.decided = true
|
ch.decided = true
|
||||||
return ch.sendMessage(reject)
|
err := ch.sendMessage(reject)
|
||||||
|
|
||||||
|
// Remove the channel from the mux to prevent memory leaks.
|
||||||
|
// Do not call ch.close() here: no goroutine holds a reference to a
|
||||||
|
// rejected channel's internal channels (msg, incomingRequests), so
|
||||||
|
// removing it from chanList is sufficient for GC. Calling close()
|
||||||
|
// would race with the mux loop goroutine (handlePacket or dropAll),
|
||||||
|
// causing a panic from closing an already-closed channel.
|
||||||
|
ch.mux.chanList.remove(ch.localId)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *channel) Read(data []byte) (int, error) {
|
func (ch *channel) Read(data []byte) (int, error) {
|
||||||
@@ -586,6 +621,24 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
|
|||||||
if wantReply {
|
if wantReply {
|
||||||
ch.sentRequestMu.Lock()
|
ch.sentRequestMu.Lock()
|
||||||
defer ch.sentRequestMu.Unlock()
|
defer ch.sentRequestMu.Unlock()
|
||||||
|
|
||||||
|
// Open the gate so that responses arriving while this request is in
|
||||||
|
// flight are allowed to reach ch.msg. Responses arriving while no
|
||||||
|
// request is pending are dropped by handlePacket.
|
||||||
|
ch.sentRequestPending.Store(true)
|
||||||
|
defer ch.sentRequestPending.Store(false)
|
||||||
|
|
||||||
|
// Drain any spurious responses that may have been buffered. This
|
||||||
|
// prevents a previously buffered unexpected response from being
|
||||||
|
// consumed instead of the actual response for this request.
|
||||||
|
drain:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ch.msg:
|
||||||
|
default:
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := channelRequestMsg{
|
msg := channelRequestMsg{
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/bits"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMinPayloadSize(t *testing.T) {
|
||||||
|
// 4 GiB (2^32). Declared as a var (not a const) so that int(bigPayload)
|
||||||
|
// is a runtime conversion: a constant conversion would fail to compile
|
||||||
|
// on 32-bit platforms with "constant 4294967296 overflows int". On
|
||||||
|
// 32-bit the value truncates to 0 at runtime, but the is64Bit cases
|
||||||
|
// that reference it are skipped by the runtime check below.
|
||||||
|
var bigPayload int64 = 1 << 32
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
maxPayload uint32
|
||||||
|
dataLen int
|
||||||
|
want uint32
|
||||||
|
is64Bit bool // Flag to run only on 64-bit architectures
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Normal Case - Data fits in payload",
|
||||||
|
maxPayload: 32768,
|
||||||
|
dataLen: 1000,
|
||||||
|
want: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Normal Case - Data larger than payload",
|
||||||
|
maxPayload: 32768,
|
||||||
|
dataLen: 50000,
|
||||||
|
want: 32768,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Boundary Case - Data zero",
|
||||||
|
maxPayload: 32768,
|
||||||
|
dataLen: 0,
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Overflow Case - Data is exactly 4GB (1<<32)",
|
||||||
|
maxPayload: 32768,
|
||||||
|
dataLen: int(bigPayload),
|
||||||
|
want: 32768,
|
||||||
|
is64Bit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Overflow Case - Data is 4GB + small amount",
|
||||||
|
maxPayload: 32768,
|
||||||
|
dataLen: int(bigPayload + 100),
|
||||||
|
want: 32768,
|
||||||
|
is64Bit: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
is64Bit := bits.UintSize == 64
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.is64Bit && !is64Bit {
|
||||||
|
t.Skip("Skipping test requiring 64-bit int")
|
||||||
|
}
|
||||||
|
got := minPayloadSize(tt.maxPayload, tt.dataLen)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("minPayloadSize(%d, %d) = %d; want %d", tt.maxPayload, tt.dataLen, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWriteExtendedNoInfiniteLoopOnLargeWrite is an end-to-end regression
|
||||||
|
// test for the integer-overflow bug in WriteExtended. Before the fix, a
|
||||||
|
// write whose len(data) was a multiple of 2^32 caused minPayloadSize to
|
||||||
|
// return 0; WriteExtended then spun forever, reserving 0 bytes per
|
||||||
|
// iteration and never advancing the data slice.
|
||||||
|
//
|
||||||
|
// We exercise the real WriteExtended path with a slice whose declared
|
||||||
|
// length is exactly 2^32. Allocating 4 GiB is unnecessary: each iteration
|
||||||
|
// only reads up to maxRemotePayload bytes from the head of the slice, and
|
||||||
|
// the loop blocks in remoteWin.reserve() once the channel window is
|
||||||
|
// exhausted — before the slice base advances past the underlying buffer.
|
||||||
|
//
|
||||||
|
// With the fix, the loop blocks in reserve(); we detect that via
|
||||||
|
// waitWriterBlocked(), then close the window to let WriteExtended return.
|
||||||
|
// With the bug, the loop never blocks and the test times out.
|
||||||
|
//
|
||||||
|
//go:nocheckptr
|
||||||
|
func TestWriteExtendedNoInfiniteLoopOnLargeWrite(t *testing.T) {
|
||||||
|
if bits.UintSize < 64 {
|
||||||
|
t.Skip("test requires 64-bit int to construct a slice with len >= 2^32")
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, writer, mux := channelPair(t)
|
||||||
|
defer reader.Close()
|
||||||
|
defer writer.Close()
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
// Sized to hold the full pre-update remote window so that no iteration
|
||||||
|
// reads past the backing buffer before reserve() blocks.
|
||||||
|
backing := make([]byte, channelWindowSize)
|
||||||
|
var bigLen int64 = 1 << 32
|
||||||
|
bigSlice := unsafe.Slice(&backing[0], int(bigLen))
|
||||||
|
|
||||||
|
done := make(chan int, 1)
|
||||||
|
go func() {
|
||||||
|
n, _ := writer.Write(bigSlice)
|
||||||
|
done <- n
|
||||||
|
}()
|
||||||
|
|
||||||
|
blocked := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
writer.remoteWin.waitWriterBlocked()
|
||||||
|
close(blocked)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-blocked:
|
||||||
|
// Good — the loop made progress and is now blocked in reserve().
|
||||||
|
// Close the window to let WriteExtended return.
|
||||||
|
writer.remoteWin.close()
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("WriteExtended did not block in reserve within 2s — minPayloadSize likely returned 0 (integer overflow regression)")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case n := <-done:
|
||||||
|
if n == 0 {
|
||||||
|
t.Fatalf("WriteExtended returned n=0; expected progress")
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("WriteExtended did not return after closing the window")
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
-29
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/des"
|
"crypto/des"
|
||||||
|
"crypto/fips140"
|
||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/crypto/chacha20"
|
"golang.org/x/crypto/chacha20"
|
||||||
"golang.org/x/crypto/internal/poly1305"
|
"golang.org/x/crypto/internal/poly1305"
|
||||||
@@ -93,41 +95,41 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||||
// are not supported and will not be negotiated, even if explicitly requested in
|
// are not supported and will not be negotiated, even if explicitly configured.
|
||||||
// ClientConfig.Crypto.Ciphers.
|
// When FIPS mode is enabled, only FIPS-approved algorithms are included.
|
||||||
var cipherModes = map[string]*cipherMode{
|
var cipherModes = map[string]*cipherMode{}
|
||||||
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
|
|
||||||
// are defined in the order specified in the RFC.
|
|
||||||
CipherAES128CTR: {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
|
||||||
CipherAES192CTR: {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
|
||||||
CipherAES256CTR: {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
|
||||||
|
|
||||||
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
|
func init() {
|
||||||
// They are defined in the order specified in the RFC.
|
cipherModes[CipherAES128CTR] = &cipherMode{16, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||||
InsecureCipherRC4128: {16, 0, streamCipherMode(1536, newRC4)},
|
cipherModes[CipherAES192CTR] = &cipherMode{24, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||||
InsecureCipherRC4256: {32, 0, streamCipherMode(1536, newRC4)},
|
cipherModes[CipherAES256CTR] = &cipherMode{32, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||||
|
// Use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode,
|
||||||
|
// we'll wire it up to NewGCMForSSH in Go 1.26.
|
||||||
|
//
|
||||||
|
// For now it means we'll work with fips140=on but not fips140=only.
|
||||||
|
cipherModes[CipherAES128GCM] = &cipherMode{16, 12, newGCMCipher}
|
||||||
|
cipherModes[CipherAES256GCM] = &cipherMode{32, 12, newGCMCipher}
|
||||||
|
|
||||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
if fips140.Enabled() {
|
||||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
defaultCiphers = slices.DeleteFunc(defaultCiphers, func(algo string) bool {
|
||||||
// RC4) has problems with weak keys, and should be used with caution."
|
_, ok := cipherModes[algo]
|
||||||
// RFC 4345 introduces improved versions of Arcfour.
|
return !ok
|
||||||
InsecureCipherRC4: {16, 0, streamCipherMode(0, newRC4)},
|
})
|
||||||
|
return
|
||||||
// AEAD ciphers
|
}
|
||||||
CipherAES128GCM: {16, 12, newGCMCipher},
|
|
||||||
CipherAES256GCM: {32, 12, newGCMCipher},
|
|
||||||
CipherChaCha20Poly1305: {64, 0, newChaCha20Cipher},
|
|
||||||
|
|
||||||
|
cipherModes[CipherChaCha20Poly1305] = &cipherMode{64, 0, newChaCha20Cipher}
|
||||||
|
// Insecure ciphers not included in the default configuration.
|
||||||
|
cipherModes[InsecureCipherRC4128] = &cipherMode{16, 0, streamCipherMode(1536, newRC4)}
|
||||||
|
cipherModes[InsecureCipherRC4256] = &cipherMode{32, 0, streamCipherMode(1536, newRC4)}
|
||||||
|
cipherModes[InsecureCipherRC4] = &cipherMode{16, 0, streamCipherMode(0, newRC4)}
|
||||||
// CBC mode is insecure and so is not included in the default config.
|
// CBC mode is insecure and so is not included in the default config.
|
||||||
// (See https://www.ieee-security.org/TC/SP2013/papers/4977a526.pdf). If absolutely
|
// (See https://www.ieee-security.org/TC/SP2013/papers/4977a526.pdf). If absolutely
|
||||||
// needed, it's possible to specify a custom Config to enable it.
|
// needed, it's possible to specify a custom Config to enable it.
|
||||||
// You should expect that an active attacker can recover plaintext if
|
// You should expect that an active attacker can recover plaintext if
|
||||||
// you do.
|
// you do.
|
||||||
InsecureCipherAES128CBC: {16, aes.BlockSize, newAESCBCCipher},
|
cipherModes[InsecureCipherAES128CBC] = &cipherMode{16, aes.BlockSize, newAESCBCCipher}
|
||||||
|
cipherModes[InsecureCipherTripleDESCBC] = &cipherMode{24, des.BlockSize, newTripleDESCBCCipher}
|
||||||
// 3des-cbc is insecure and is not included in the default
|
|
||||||
// config.
|
|
||||||
InsecureCipherTripleDESCBC: {24, des.BlockSize, newTripleDESCBCCipher},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefixLen is the length of the packet prefix that contains the packet length
|
// prefixLen is the length of the packet prefix that contains the packet length
|
||||||
@@ -405,7 +407,7 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
|
|||||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(padding+1) >= len(plain) {
|
if int(padding)+1 >= len(plain) {
|
||||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||||
}
|
}
|
||||||
plain = plain[1 : length-uint32(padding)]
|
plain = plain[1 : length-uint32(padding)]
|
||||||
@@ -584,7 +586,7 @@ func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader
|
|||||||
|
|
||||||
// Length of encrypted portion of the packet (header, payload, padding).
|
// Length of encrypted portion of the packet (header, payload, padding).
|
||||||
// Enforce minimum padding and packet size.
|
// Enforce minimum padding and packet size.
|
||||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize)
|
||||||
// Enforce block size.
|
// Enforce block size.
|
||||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||||
|
|
||||||
|
|||||||
+14
-18
@@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|||||||
// success
|
// success
|
||||||
return nil
|
return nil
|
||||||
} else if ok == authFailure {
|
} else if ok == authFailure {
|
||||||
if m := auth.method(); !contains(tried, m) {
|
if m := auth.method(); !slices.Contains(tried, m) {
|
||||||
tried = append(tried, m)
|
tried = append(tried, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|||||||
findNext:
|
findNext:
|
||||||
for _, a := range config.Auth {
|
for _, a := range config.Auth {
|
||||||
candidateMethod := a.method()
|
candidateMethod := a.method()
|
||||||
if contains(tried, candidateMethod) {
|
if slices.Contains(tried, candidateMethod) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, meth := range methods {
|
for _, meth := range methods {
|
||||||
@@ -117,15 +118,6 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|||||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
|
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(list []string, e string) bool {
|
|
||||||
for _, s := range list {
|
|
||||||
if s == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
||||||
type AuthMethod interface {
|
type AuthMethod interface {
|
||||||
// auth authenticates user over transport t.
|
// auth authenticates user over transport t.
|
||||||
@@ -255,7 +247,7 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
|||||||
// Fallback to use if there is no "server-sig-algs" extension or a
|
// Fallback to use if there is no "server-sig-algs" extension or a
|
||||||
// common algorithm cannot be found. We use the public key format if the
|
// common algorithm cannot be found. We use the public key format if the
|
||||||
// MultiAlgorithmSigner supports it, otherwise we return an error.
|
// MultiAlgorithmSigner supports it, otherwise we return an error.
|
||||||
if !contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
|
if !slices.Contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
|
||||||
return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v",
|
return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v",
|
||||||
underlyingAlgo(keyFormat), keyFormat, as.Algorithms())
|
underlyingAlgo(keyFormat), keyFormat, as.Algorithms())
|
||||||
}
|
}
|
||||||
@@ -282,10 +274,14 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
||||||
|
// Iterate over the signer's algorithms first to preserve its preference order.
|
||||||
|
supportedKeyAlgos := algorithmsForKeyFormat(keyFormat)
|
||||||
var keyAlgos []string
|
var keyAlgos []string
|
||||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
for _, signerAlgo := range as.Algorithms() {
|
||||||
if contains(as.Algorithms(), underlyingAlgo(algo)) {
|
if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool {
|
||||||
keyAlgos = append(keyAlgos, algo)
|
return underlyingAlgo(algo) == signerAlgo
|
||||||
|
}); idx >= 0 {
|
||||||
|
keyAlgos = append(keyAlgos, supportedKeyAlgos[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +330,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
|||||||
// the key try to use the obtained algorithm as if "server-sig-algs" had
|
// the key try to use the obtained algorithm as if "server-sig-algs" had
|
||||||
// not been implemented if supported from the algorithm signer.
|
// not been implemented if supported from the algorithm signer.
|
||||||
if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
|
if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
|
||||||
if contains(as.Algorithms(), KeyAlgoRSA) {
|
if slices.Contains(as.Algorithms(), KeyAlgoRSA) {
|
||||||
// We retry using the compat algorithm after all signers have
|
// We retry using the compat algorithm after all signers have
|
||||||
// been tried normally.
|
// been tried normally.
|
||||||
signers = append(signers, &multiAlgorithmSigner{
|
signers = append(signers, &multiAlgorithmSigner{
|
||||||
@@ -385,7 +381,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
|||||||
// contain the "publickey" method, do not attempt to authenticate with any
|
// contain the "publickey" method, do not attempt to authenticate with any
|
||||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||||
// additional authentication methods are required.
|
// additional authentication methods are required.
|
||||||
if success == authSuccess || !contains(methods, cb.method()) {
|
if success == authSuccess || !slices.Contains(methods, cb.method()) {
|
||||||
return success, methods, err
|
return success, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +430,7 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
|||||||
// servers send the key type instead. OpenSSH allows any algorithm
|
// servers send the key type instead. OpenSSH allows any algorithm
|
||||||
// that matches the public key, so we do the same.
|
// that matches the public key, so we do the same.
|
||||||
// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
|
// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
|
||||||
if !contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
|
if !slices.Contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if !bytes.Equal(msg.PubKey, pubKey) {
|
if !bytes.Equal(msg.PubKey, pubKey) {
|
||||||
|
|||||||
+49
-2
@@ -14,6 +14,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -1135,7 +1136,7 @@ func TestPickSignatureAlgorithm(t *testing.T) {
|
|||||||
t.Fatalf("error generating cert signer: %v", err)
|
t.Fatalf("error generating cert signer: %v", err)
|
||||||
}
|
}
|
||||||
// The signer supports the public key algorithm and the
|
// The signer supports the public key algorithm and the
|
||||||
// public key format is a certificate type so the cerificate
|
// public key format is a certificate type so the certificate
|
||||||
// algorithm matching the key format must be returned
|
// algorithm matching the key format must be returned
|
||||||
_, algo, err = pickSignatureAlgorithm(certSigner, c.extensions)
|
_, algo, err = pickSignatureAlgorithm(certSigner, c.extensions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1158,6 +1159,52 @@ func TestPickSignatureAlgorithm(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPickSignatureAlgorithmRespectsSignerPreference(t *testing.T) {
|
||||||
|
algoSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("rsa test signer does not implement the AlgorithmSigner interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
serverExtensions := map[string][]byte{
|
||||||
|
"server-sig-algs": []byte(KeyAlgoRSASHA256 + "," + KeyAlgoRSASHA512),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
signerPrefs []string
|
||||||
|
expectedAlgo string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Signer prefers SHA512 then SHA256",
|
||||||
|
signerPrefs: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
|
||||||
|
expectedAlgo: KeyAlgoRSASHA512,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Signer prefers SHA256 then SHA512",
|
||||||
|
signerPrefs: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
|
||||||
|
expectedAlgo: KeyAlgoRSASHA256,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
orderedSigner, err := NewSignerWithAlgorithms(algoSigner, tc.signerPrefs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ordered signer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, selectedAlgo, err := pickSignatureAlgorithm(orderedSigner, serverExtensions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedAlgo != tc.expectedAlgo {
|
||||||
|
t.Errorf("Algorithm mismatch; got %q want %q", selectedAlgo, tc.expectedAlgo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// configurablePublicKeyCallback is a public key callback that allows to
|
// configurablePublicKeyCallback is a public key callback that allows to
|
||||||
// configure the signature algorithm and format. This way we can emulate the
|
// configure the signature algorithm and format. This way we can emulate the
|
||||||
// behavior of buggy clients.
|
// behavior of buggy clients.
|
||||||
@@ -1214,7 +1261,7 @@ func (cb configurablePublicKeyCallback) auth(session []byte, user string, c pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return authFailure, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
if success == authSuccess || !contains(methods, cb.method()) {
|
if success == authSuccess || !slices.Contains(methods, cb.method()) {
|
||||||
return success, methods, err
|
return success, methods, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+66
-17
@@ -6,6 +6,7 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/fips140"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -256,6 +257,40 @@ type Algorithms struct {
|
|||||||
PublicKeyAuths []string
|
PublicKeyAuths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if fips140.Enabled() {
|
||||||
|
defaultHostKeyAlgos = slices.DeleteFunc(defaultHostKeyAlgos, func(algo string) bool {
|
||||||
|
_, err := hashFunc(underlyingAlgo(algo))
|
||||||
|
return err != nil
|
||||||
|
})
|
||||||
|
defaultPubKeyAuthAlgos = slices.DeleteFunc(defaultPubKeyAuthAlgos, func(algo string) bool {
|
||||||
|
_, err := hashFunc(underlyingAlgo(algo))
|
||||||
|
return err != nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashFunc(format string) (crypto.Hash, error) {
|
||||||
|
switch format {
|
||||||
|
case KeyAlgoRSASHA256, KeyAlgoECDSA256, KeyAlgoSKED25519, KeyAlgoSKECDSA256:
|
||||||
|
return crypto.SHA256, nil
|
||||||
|
case KeyAlgoECDSA384:
|
||||||
|
return crypto.SHA384, nil
|
||||||
|
case KeyAlgoRSASHA512, KeyAlgoECDSA521:
|
||||||
|
return crypto.SHA512, nil
|
||||||
|
case KeyAlgoED25519:
|
||||||
|
// KeyAlgoED25519 doesn't pre-hash.
|
||||||
|
return 0, nil
|
||||||
|
case KeyAlgoRSA, InsecureKeyAlgoDSA:
|
||||||
|
if fips140.Enabled() {
|
||||||
|
return 0, fmt.Errorf("ssh: hash algorithm for format %q not allowed in FIPS 140 mode", format)
|
||||||
|
}
|
||||||
|
return crypto.SHA1, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("ssh: hash algorithm for format %q not mapped", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SupportedAlgorithms returns algorithms currently implemented by this package,
|
// SupportedAlgorithms returns algorithms currently implemented by this package,
|
||||||
// excluding those with security issues, which are returned by
|
// excluding those with security issues, which are returned by
|
||||||
// InsecureAlgorithms. The algorithms listed here are in preference order.
|
// InsecureAlgorithms. The algorithms listed here are in preference order.
|
||||||
@@ -283,21 +318,6 @@ func InsecureAlgorithms() Algorithms {
|
|||||||
|
|
||||||
var supportedCompressions = []string{compressionNone}
|
var supportedCompressions = []string{compressionNone}
|
||||||
|
|
||||||
// hashFuncs keeps the mapping of supported signature algorithms to their
|
|
||||||
// respective hashes needed for signing and verification.
|
|
||||||
var hashFuncs = map[string]crypto.Hash{
|
|
||||||
KeyAlgoRSA: crypto.SHA1,
|
|
||||||
KeyAlgoRSASHA256: crypto.SHA256,
|
|
||||||
KeyAlgoRSASHA512: crypto.SHA512,
|
|
||||||
InsecureKeyAlgoDSA: crypto.SHA1,
|
|
||||||
KeyAlgoECDSA256: crypto.SHA256,
|
|
||||||
KeyAlgoECDSA384: crypto.SHA384,
|
|
||||||
KeyAlgoECDSA521: crypto.SHA512,
|
|
||||||
// KeyAlgoED25519 doesn't pre-hash.
|
|
||||||
KeyAlgoSKECDSA256: crypto.SHA256,
|
|
||||||
KeyAlgoSKED25519: crypto.SHA256,
|
|
||||||
}
|
|
||||||
|
|
||||||
// algorithmsForKeyFormat returns the supported signature algorithms for a given
|
// algorithmsForKeyFormat returns the supported signature algorithms for a given
|
||||||
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
|
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
|
||||||
// Section 2. See also the note in sendKexInit on backwards compatibility.
|
// Section 2. See also the note in sendKexInit on backwards compatibility.
|
||||||
@@ -312,11 +332,40 @@ func algorithmsForKeyFormat(keyFormat string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keyFormatForAlgorithm returns the key format corresponding to the given
|
||||||
|
// signature algorithm. It returns an empty string if the signature algorithm is
|
||||||
|
// invalid or unsupported.
|
||||||
|
func keyFormatForAlgorithm(sigAlgo string) string {
|
||||||
|
switch sigAlgo {
|
||||||
|
case KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512:
|
||||||
|
return KeyAlgoRSA
|
||||||
|
case CertAlgoRSAv01, CertAlgoRSASHA256v01, CertAlgoRSASHA512v01:
|
||||||
|
return CertAlgoRSAv01
|
||||||
|
case KeyAlgoED25519,
|
||||||
|
KeyAlgoSKED25519,
|
||||||
|
KeyAlgoSKECDSA256,
|
||||||
|
KeyAlgoECDSA256,
|
||||||
|
KeyAlgoECDSA384,
|
||||||
|
KeyAlgoECDSA521,
|
||||||
|
InsecureKeyAlgoDSA,
|
||||||
|
InsecureCertAlgoDSAv01,
|
||||||
|
CertAlgoECDSA256v01,
|
||||||
|
CertAlgoECDSA384v01,
|
||||||
|
CertAlgoECDSA521v01,
|
||||||
|
CertAlgoSKECDSA256v01,
|
||||||
|
CertAlgoED25519v01,
|
||||||
|
CertAlgoSKED25519v01:
|
||||||
|
return sigAlgo
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isRSA returns whether algo is a supported RSA algorithm, including certificate
|
// isRSA returns whether algo is a supported RSA algorithm, including certificate
|
||||||
// algorithms.
|
// algorithms.
|
||||||
func isRSA(algo string) bool {
|
func isRSA(algo string) bool {
|
||||||
algos := algorithmsForKeyFormat(KeyAlgoRSA)
|
algos := algorithmsForKeyFormat(KeyAlgoRSA)
|
||||||
return contains(algos, underlyingAlgo(algo))
|
return slices.Contains(algos, underlyingAlgo(algo))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRSACert(algo string) bool {
|
func isRSACert(algo string) bool {
|
||||||
@@ -515,7 +564,7 @@ func (c *Config) SetDefaults() {
|
|||||||
if kexAlgoMap[k] != nil {
|
if kexAlgoMap[k] != nil {
|
||||||
// Ignore the KEX if we have no kexAlgoMap definition.
|
// Ignore the KEX if we have no kexAlgoMap definition.
|
||||||
kexs = append(kexs, k)
|
kexs = append(kexs, k)
|
||||||
if k == KeyExchangeCurve25519 && !contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) {
|
if k == KeyExchangeCurve25519 && !slices.Contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) {
|
||||||
kexs = append(kexs, keyExchangeCurve25519LibSSH)
|
kexs = append(kexs, keyExchangeCurve25519LibSSH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,3 +176,21 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeyFormatAlgorithms(t *testing.T) {
|
||||||
|
supportedAlgos := SupportedAlgorithms()
|
||||||
|
insecureAlgos := InsecureAlgorithms()
|
||||||
|
algoritms := append(supportedAlgos.PublicKeyAuths, insecureAlgos.PublicKeyAuths...)
|
||||||
|
algoritms = append(algoritms, slices.Collect(maps.Keys(certKeyAlgoNames))...)
|
||||||
|
|
||||||
|
for _, algo := range algoritms {
|
||||||
|
keyFormat := keyFormatForAlgorithm(algo)
|
||||||
|
if keyFormat == "" {
|
||||||
|
t.Errorf("got empty key format for algorithm %q", algo)
|
||||||
|
}
|
||||||
|
if !slices.Contains(algorithmsForKeyFormat(keyFormat), algo) {
|
||||||
|
t.Errorf("algorithms for key format %q, does not contain %q", keyFormat, algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
@@ -17,8 +17,18 @@ References:
|
|||||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||||
[SSH-CERTS]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-cert-01
|
[SSH-CERTS]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-cert-01
|
||||||
|
[FIPS 140-3 mode]: https://go.dev/doc/security/fips140
|
||||||
|
|
||||||
This package does not fall under the stability promise of the Go language itself,
|
This package does not fall under the stability promise of the Go language itself,
|
||||||
so its API may be changed when pressing needs arise.
|
so its API may be changed when pressing needs arise.
|
||||||
|
|
||||||
|
# FIPS 140-3 mode
|
||||||
|
|
||||||
|
When the program is in [FIPS 140-3 mode], this package behaves as if only SP
|
||||||
|
800-140C and SP 800-140D approved cipher suites, signature algorithms,
|
||||||
|
certificate public key types and sizes, and key exchange and derivation
|
||||||
|
algorithms were implemented. Others are silently ignored and not negotiated, or
|
||||||
|
rejected. This set may depend on the algorithms supported by the FIPS 140-3 Go
|
||||||
|
Cryptographic Module selected with GOFIPS140, and may change across Go versions.
|
||||||
*/
|
*/
|
||||||
package ssh
|
package ssh
|
||||||
|
|||||||
+3
-3
@@ -155,7 +155,7 @@ func ExampleNewServerConn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleServerConfig() {
|
func ExampleServerConfig() {
|
||||||
// Minimal ServerConfig with SHA-1 algoritms disabled and supporting only
|
// Minimal ServerConfig with SHA-1 algorithms disabled and supporting only
|
||||||
// public key authentication.
|
// public key authentication.
|
||||||
|
|
||||||
// The algorithms returned by ssh.SupportedAlgorithms() are different from
|
// The algorithms returned by ssh.SupportedAlgorithms() are different from
|
||||||
@@ -344,7 +344,7 @@ func ExampleClientConfig() {
|
|||||||
log.Fatalf("unable to parse private key: %v", err)
|
log.Fatalf("unable to parse private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimal ClientConfig with SHA-1 algoritms disabled.
|
// Minimal ClientConfig with SHA-1 algorithms disabled.
|
||||||
// The algorithms returned by ssh.SupportedAlgorithms() are different from
|
// The algorithms returned by ssh.SupportedAlgorithms() are different from
|
||||||
// the default ones and do not include algorithms that are considered
|
// the default ones and do not include algorithms that are considered
|
||||||
// insecure, such as those using SHA-1, returned by
|
// insecure, such as those using SHA-1, returned by
|
||||||
@@ -361,7 +361,7 @@ func ExampleClientConfig() {
|
|||||||
ssh.PublicKeys(signer),
|
ssh.PublicKeys(signer),
|
||||||
},
|
},
|
||||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||||
// We should check that hostKey algorithm is inlcuded in
|
// We should check that hostKey algorithm is included in
|
||||||
// algorithms.HostKeys.
|
// algorithms.HostKeys.
|
||||||
HostKeyAlgorithms: algorithms.HostKeys,
|
HostKeyAlgorithms: algorithms.HostKeys,
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -10,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -527,7 +528,7 @@ func (t *handshakeTransport) sendKexInit() error {
|
|||||||
switch s := k.(type) {
|
switch s := k.(type) {
|
||||||
case MultiAlgorithmSigner:
|
case MultiAlgorithmSigner:
|
||||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||||
if contains(s.Algorithms(), underlyingAlgo(algo)) {
|
if slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||||
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
|
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,7 +680,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.sessionID == nil && ((isClient && contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && contains(clientInit.KexAlgos, kexStrictClient))) {
|
if t.sessionID == nil && ((isClient && slices.Contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && slices.Contains(clientInit.KexAlgos, kexStrictClient))) {
|
||||||
t.strictMode = true
|
t.strictMode = true
|
||||||
if err := t.conn.setStrictMode(); err != nil {
|
if err := t.conn.setStrictMode(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -736,7 +737,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
|||||||
// On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO
|
// On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO
|
||||||
// message with the server-sig-algs extension if the client supports it. See
|
// message with the server-sig-algs extension if the client supports it. See
|
||||||
// RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9.
|
// RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9.
|
||||||
if !isClient && firstKeyExchange && contains(clientInit.KexAlgos, "ext-info-c") {
|
if !isClient && firstKeyExchange && slices.Contains(clientInit.KexAlgos, "ext-info-c") {
|
||||||
supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",")
|
supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",")
|
||||||
extInfo := &extInfoMsg{
|
extInfo := &extInfoMsg{
|
||||||
NumExtensions: 2,
|
NumExtensions: 2,
|
||||||
@@ -790,7 +791,7 @@ func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, a
|
|||||||
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
|
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
|
||||||
for _, k := range hostKeys {
|
for _, k := range hostKeys {
|
||||||
if s, ok := k.(MultiAlgorithmSigner); ok {
|
if s, ok := k.(MultiAlgorithmSigner); ok {
|
||||||
if !contains(s.Algorithms(), underlyingAlgo(algo)) {
|
if !slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1330,7 +1330,7 @@ func TestAlgorithmNegotiationError(t *testing.T) {
|
|||||||
|
|
||||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("client connection succeded expected algorithm negotiation error")
|
t.Fatal("client connection succeeded expected algorithm negotiation error")
|
||||||
}
|
}
|
||||||
var negotiationError *AlgorithmNegotiationError
|
var negotiationError *AlgorithmNegotiationError
|
||||||
if !errors.As(err, &negotiationError) {
|
if !errors.As(err, &negotiationError) {
|
||||||
|
|||||||
+20
-4
@@ -8,12 +8,14 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"crypto/fips140"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
)
|
)
|
||||||
@@ -395,9 +397,27 @@ func ecHash(curve elliptic.Curve) crypto.Hash {
|
|||||||
return crypto.SHA512
|
return crypto.SHA512
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// kexAlgoMap defines the supported KEXs. KEXs not included are not supported
|
||||||
|
// and will not be negotiated, even if explicitly configured. When FIPS mode is
|
||||||
|
// enabled, only FIPS-approved algorithms are included.
|
||||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
var kexAlgoMap = map[string]kexAlgorithm{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// mlkem768x25519-sha256 we'll work with fips140=on but not fips140=only
|
||||||
|
// until Go 1.26.
|
||||||
|
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
|
||||||
|
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
|
||||||
|
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
|
||||||
|
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
|
||||||
|
|
||||||
|
if fips140.Enabled() {
|
||||||
|
defaultKexAlgos = slices.DeleteFunc(defaultKexAlgos, func(algo string) bool {
|
||||||
|
_, ok := kexAlgoMap[algo]
|
||||||
|
return !ok
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p, _ := new(big.Int).SetString(oakleyGroup2, 16)
|
p, _ := new(big.Int).SetString(oakleyGroup2, 16)
|
||||||
kexAlgoMap[InsecureKeyExchangeDH1SHA1] = &dhGroup{
|
kexAlgoMap[InsecureKeyExchangeDH1SHA1] = &dhGroup{
|
||||||
g: new(big.Int).SetInt64(2),
|
g: new(big.Int).SetInt64(2),
|
||||||
@@ -431,14 +451,10 @@ func init() {
|
|||||||
hashFunc: crypto.SHA512,
|
hashFunc: crypto.SHA512,
|
||||||
}
|
}
|
||||||
|
|
||||||
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
|
|
||||||
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
|
|
||||||
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
|
|
||||||
kexAlgoMap[KeyExchangeCurve25519] = &curve25519sha256{}
|
kexAlgoMap[KeyExchangeCurve25519] = &curve25519sha256{}
|
||||||
kexAlgoMap[keyExchangeCurve25519LibSSH] = &curve25519sha256{}
|
kexAlgoMap[keyExchangeCurve25519LibSSH] = &curve25519sha256{}
|
||||||
kexAlgoMap[InsecureKeyExchangeDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
|
kexAlgoMap[InsecureKeyExchangeDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
|
||||||
kexAlgoMap[KeyExchangeDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256}
|
kexAlgoMap[KeyExchangeDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256}
|
||||||
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// curve25519sha256 implements the curve25519-sha256 (formerly known as
|
// curve25519sha256 implements the curve25519-sha256 (formerly known as
|
||||||
|
|||||||
+113
-15
@@ -27,6 +27,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
|
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
|
||||||
@@ -89,6 +90,11 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
|
|||||||
}
|
}
|
||||||
return cert, nil, nil
|
return cert, nil, nil
|
||||||
}
|
}
|
||||||
|
if keyFormat := keyFormatForAlgorithm(algo); keyFormat != "" {
|
||||||
|
return nil, nil, fmt.Errorf("ssh: signature algorithm %q isn't a key format; key is malformed and should be re-encoded with type %q",
|
||||||
|
algo, keyFormat)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,9 +197,10 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
|
|||||||
return "", nil, nil, "", nil, io.EOF
|
return "", nil, nil, "", nil, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAuthorizedKey parses a public key from an authorized_keys
|
// ParseAuthorizedKey parses a public key from an authorized_keys file used in
|
||||||
// file used in OpenSSH according to the sshd(8) manual page.
|
// OpenSSH according to the sshd(8) manual page. Invalid lines are ignored.
|
||||||
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
||||||
|
var lastErr error
|
||||||
for len(in) > 0 {
|
for len(in) > 0 {
|
||||||
end := bytes.IndexByte(in, '\n')
|
end := bytes.IndexByte(in, '\n')
|
||||||
if end != -1 {
|
if end != -1 {
|
||||||
@@ -222,6 +229,8 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
|
|||||||
|
|
||||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||||
return out, comment, options, rest, nil
|
return out, comment, options, rest, nil
|
||||||
|
} else {
|
||||||
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No key type recognised. Maybe there's an options field at
|
// No key type recognised. Maybe there's an options field at
|
||||||
@@ -264,12 +273,18 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
|
|||||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||||
options = candidateOptions
|
options = candidateOptions
|
||||||
return out, comment, options, rest, nil
|
return out, comment, options, rest, nil
|
||||||
|
} else {
|
||||||
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
in = rest
|
in = rest
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, "", nil, nil, fmt.Errorf("ssh: no key found; last parsing error for ignored line: %w", lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, "", nil, nil, errors.New("ssh: no key found")
|
return nil, "", nil, nil, errors.New("ssh: no key found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,11 +410,11 @@ func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (Multi
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
if !contains(supportedAlgos, algo) {
|
if !slices.Contains(supportedAlgos, algo) {
|
||||||
return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q",
|
return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q",
|
||||||
algo, signer.PublicKey().Type())
|
algo, signer.PublicKey().Type())
|
||||||
}
|
}
|
||||||
if !contains(signerAlgos, algo) {
|
if !slices.Contains(signerAlgos, algo) {
|
||||||
return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo)
|
return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,6 +469,12 @@ 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
|
||||||
|
// signature verification:
|
||||||
|
// https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
|
||||||
|
if w.N.BitLen() > 8192 {
|
||||||
|
return nil, nil, errors.New("ssh: rsa modulus too large")
|
||||||
|
}
|
||||||
if w.E.BitLen() > 24 {
|
if w.E.BitLen() > 24 {
|
||||||
return nil, nil, errors.New("ssh: exponent too large")
|
return nil, nil, errors.New("ssh: exponent too large")
|
||||||
}
|
}
|
||||||
@@ -486,10 +507,13 @@ func (r *rsaPublicKey) Marshal() []byte {
|
|||||||
|
|
||||||
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
supportedAlgos := algorithmsForKeyFormat(r.Type())
|
supportedAlgos := algorithmsForKeyFormat(r.Type())
|
||||||
if !contains(supportedAlgos, sig.Format) {
|
if !slices.Contains(supportedAlgos, sig.Format) {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
||||||
}
|
}
|
||||||
hash := hashFuncs[sig.Format]
|
hash, err := hashFunc(sig.Format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
h := hash.New()
|
h := hash.New()
|
||||||
h.Write(data)
|
h.Write(data)
|
||||||
digest := h.Sum(nil)
|
digest := h.Sum(nil)
|
||||||
@@ -556,6 +580,24 @@ func checkDSAParams(param *dsa.Parameters) error {
|
|||||||
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
|
||||||
|
// this to prevent DoS attacks where an attacker sends a huge Q which makes
|
||||||
|
// verification slow.
|
||||||
|
if l := param.Q.BitLen(); l != 160 {
|
||||||
|
return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generator G is an element of the group, so it must be strictly less
|
||||||
|
// than the modulus P.
|
||||||
|
if param.G.Cmp(param.P) >= 0 {
|
||||||
|
return errors.New("ssh: DSA generator larger than modulus")
|
||||||
|
}
|
||||||
|
|
||||||
|
// G must be positive.
|
||||||
|
if param.G.Sign() <= 0 {
|
||||||
|
return errors.New("ssh: DSA generator must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,6 +620,14 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The public value Y must be a non-zero element of the group, i.e.
|
||||||
|
// strictly between 0 and P. crypto/dsa.Verify does not range-check Y,
|
||||||
|
// so we reject out-of-range values here to prevent a maliciously
|
||||||
|
// oversized Y from slowing verification.
|
||||||
|
if w.Y.Sign() <= 0 || w.Y.Cmp(w.P) >= 0 {
|
||||||
|
return nil, nil, errors.New("ssh: DSA public value Y out of range")
|
||||||
|
}
|
||||||
|
|
||||||
key := &dsaPublicKey{
|
key := &dsaPublicKey{
|
||||||
Parameters: param,
|
Parameters: param,
|
||||||
Y: w.Y,
|
Y: w.Y,
|
||||||
@@ -606,7 +656,11 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
if sig.Format != k.Type() {
|
if sig.Format != k.Type() {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||||
}
|
}
|
||||||
h := hashFuncs[sig.Format].New()
|
hash, err := hashFunc(sig.Format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
h.Write(data)
|
h.Write(data)
|
||||||
digest := h.Sum(nil)
|
digest := h.Sum(nil)
|
||||||
|
|
||||||
@@ -651,7 +705,11 @@ func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
|
|||||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hashFuncs[k.PublicKey().Type()].New()
|
hash, err := hashFunc(k.PublicKey().Type())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
h.Write(data)
|
h.Write(data)
|
||||||
digest := h.Sum(nil)
|
digest := h.Sum(nil)
|
||||||
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
||||||
@@ -801,8 +859,11 @@ func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
if sig.Format != k.Type() {
|
if sig.Format != k.Type() {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||||
}
|
}
|
||||||
|
hash, err := hashFunc(sig.Format)
|
||||||
h := hashFuncs[sig.Format].New()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
h.Write(data)
|
h.Write(data)
|
||||||
digest := h.Sum(nil)
|
digest := h.Sum(nil)
|
||||||
|
|
||||||
@@ -840,11 +901,25 @@ type skFields struct {
|
|||||||
Counter uint32
|
Counter uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flagUserPresence is the "user present" bit (UP) in the SK signature
|
||||||
|
// flags, matching the FIDO CTAP2 authenticatorData UP flag. See
|
||||||
|
// openssh/PROTOCOL.u2f.
|
||||||
|
const flagUserPresence = 0x01
|
||||||
|
|
||||||
|
// errSKMissingUserPresence is returned by SK key Verify methods when
|
||||||
|
// the signature does not assert user presence and the key was not
|
||||||
|
// marked as no-touch-required.
|
||||||
|
var errSKMissingUserPresence = errors.New("ssh: signature missing required user presence flag")
|
||||||
|
|
||||||
type skECDSAPublicKey struct {
|
type skECDSAPublicKey struct {
|
||||||
// application is a URL-like string, typically "ssh:" for SSH.
|
// application is a URL-like string, typically "ssh:" for SSH.
|
||||||
// see openssh/PROTOCOL.u2f for details.
|
// see openssh/PROTOCOL.u2f for details.
|
||||||
application string
|
application string
|
||||||
ecdsa.PublicKey
|
ecdsa.PublicKey
|
||||||
|
// noTouchRequired, when true, disables the default user-presence
|
||||||
|
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||||
|
// key, never on an instance shared across authentication attempts.
|
||||||
|
noTouchRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *skECDSAPublicKey) Type() string {
|
func (k *skECDSAPublicKey) Type() string {
|
||||||
@@ -905,8 +980,11 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
if sig.Format != k.Type() {
|
if sig.Format != k.Type() {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||||
}
|
}
|
||||||
|
hash, err := hashFunc(sig.Format)
|
||||||
h := hashFuncs[sig.Format].New()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
h.Write([]byte(k.application))
|
h.Write([]byte(k.application))
|
||||||
appDigest := h.Sum(nil)
|
appDigest := h.Sum(nil)
|
||||||
|
|
||||||
@@ -927,6 +1005,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||||
|
return errSKMissingUserPresence
|
||||||
|
}
|
||||||
|
|
||||||
blob := struct {
|
blob := struct {
|
||||||
ApplicationDigest []byte `ssh:"rest"`
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
Flags byte
|
Flags byte
|
||||||
@@ -960,6 +1042,10 @@ type skEd25519PublicKey struct {
|
|||||||
// see openssh/PROTOCOL.u2f for details.
|
// see openssh/PROTOCOL.u2f for details.
|
||||||
application string
|
application string
|
||||||
ed25519.PublicKey
|
ed25519.PublicKey
|
||||||
|
// noTouchRequired, when true, disables the default user-presence
|
||||||
|
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||||
|
// key, never on an instance shared across authentication attempts.
|
||||||
|
noTouchRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *skEd25519PublicKey) Type() string {
|
func (k *skEd25519PublicKey) Type() string {
|
||||||
@@ -1009,7 +1095,11 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hashFuncs[sig.Format].New()
|
hash, err := hashFunc(sig.Format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
h.Write([]byte(k.application))
|
h.Write([]byte(k.application))
|
||||||
appDigest := h.Sum(nil)
|
appDigest := h.Sum(nil)
|
||||||
|
|
||||||
@@ -1030,6 +1120,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||||
|
return errSKMissingUserPresence
|
||||||
|
}
|
||||||
|
|
||||||
blob := struct {
|
blob := struct {
|
||||||
ApplicationDigest []byte `ssh:"rest"`
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
Flags byte
|
Flags byte
|
||||||
@@ -1112,11 +1206,14 @@ func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
|
|||||||
algorithm = s.pubKey.Type()
|
algorithm = s.pubKey.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !contains(s.Algorithms(), algorithm) {
|
if !slices.Contains(s.Algorithms(), algorithm) {
|
||||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
|
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
hashFunc := hashFuncs[algorithm]
|
hashFunc, err := hashFunc(algorithm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var digest []byte
|
var digest []byte
|
||||||
if hashFunc != 0 {
|
if hashFunc != 0 {
|
||||||
h := hashFunc.New()
|
h := hashFunc.New()
|
||||||
@@ -1451,6 +1548,7 @@ type openSSHEncryptedPrivateKey struct {
|
|||||||
NumKeys uint32
|
NumKeys uint32
|
||||||
PubKey []byte
|
PubKey []byte
|
||||||
PrivKeyBlock []byte
|
PrivKeyBlock []byte
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type openSSHPrivateKey struct {
|
type openSSHPrivateKey struct {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -19,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -59,6 +61,17 @@ func TestKeyMarshalParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParsePublicKeyWithSigningAlgoAsKeyFormat(t *testing.T) {
|
||||||
|
key := []byte(`rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAEBAJ7qMyjLXEJCCJmRknuCLo0uPi5GrPY5pQYr84lhlN8Gor5KVL2LKYCW4e70r5xzj7SrHHSCft1FMlYg1KDO9xrprJh733kQqAPWETmSuH0EfRtGtcH6EarKyVxk6As076/yNiiMKVBtG0RPa1L7FviTfcYK4vnCCVrbv3RmA5CCzuG5BSMbRLxzVb4Ri3p8jhxYT8N4QGe/2yqvJLys5vQ9szpZR3tcFp3DJIVZhBRfR6LnoY23XZniAAMQaUVBX86dXQ++dNwAwZSXSt9Og+AniOCiBYqhNVa5n3DID/H7YtEtG+CbZr3r2KD3fv8AfSLRar4XOp8rsRdD31h/kr8=`)
|
||||||
|
_, _, _, _, err := ParseAuthorizedKey(key)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("parsing a public key using a signature algorithm as the key format succeeded unexpectedly")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), `signature algorithm "rsa-sha2-256" isn't a key format`) {
|
||||||
|
t.Errorf(`got %v, expected 'signature algorithm "rsa-sha2-256" isn't a key format'`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnsupportedCurves(t *testing.T) {
|
func TestUnsupportedCurves(t *testing.T) {
|
||||||
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -205,6 +218,20 @@ func TestParseRSAPrivateKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRSAModulusTooLarge(t *testing.T) {
|
||||||
|
rsa16384 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAIAQC0vyqtnpzMZ8Th6gv1t3+7dDVd2X+7MwjqKe08/wrKZaIuCAHX7jglC8FdHfiOcPhLHAreSJLZGXiSzCTUExbp9Zdt7tHluKMQxZCnbk02V89ggc4KpptQqfcmjMNgrEPF4PGJBe3eSYu9m7A1ptm0buWxaKtA17O1c2q2CVNYmuajSUAfi8+AZcdyRAX8eqga3u77DEj4O+CCuNA908NYv9Pc7SbaCwCgX3FXh7MvlaYcruTk529psNk1SK6uwBNak3CLRrXUBo5yTO7cCO0gmlvd8SwtY5mPaDLLCJa+Ed2t9OCt8HCPrdkH3aiQBTP3k/Iofy3NRv+/1lenJP1qWB2rnyagoj+osrLc6m8PRd7GBje7KdO6nyY1D+q1MvX4zV1qNQfD+38N6PixVX1MGKw72sD+oLCDjHKS24BHfnfaE7f8n+Alp2mxnSz9tYqsN3JXrbbwblzH8YFOz9MMKkTY5lQNQFoKdTHoPVXBqcMlGQGgC/kkH9TH110MDE3L+Z0i9kG53QLcHNVC0cJzo6X77PQF1WoS33Ssf1s21kUB6fe/5G2L2Tuy635ingtnQMUazwJMli+y/3U7QqqcuJzITbLw/zozRzbqFRE4HHOyGkJSptraH4/3cjmYro8JL20A7EUDiXxmn+z+eoxaBWaEP2Rs5LH0ocrwYmBmSUIrPy4e5ESlRRVuv+prNUB1pYGnFZUI9HRRFxRFg+SxXZZp4mq8jzroRLZFAqKY1/eDgsCGoV1HG3QW1t0jGNkv8BL/Zpz7Hhmt0lRr7rVN+PJaTQx8NNUJZ/l7nMXp/+7aTbkyI8PiNyemko0+joDrkBWdcK0vi8Du6k3oQRBUsJ0aM0iPxMWQq/oYyHqpf6revycYTUdOGeckGWXuXLNRIZQ/PsKuw/teqDaBkkMKaQH2oXWMsacL6WDu5VYUQidOtaTgHX8AKVHF/vobTQfw7HgPKlyRN41dAX+YIjRABYoqU7M2MYPuoPYbqzTz7iLZyFP8FCJ3dRQBTWSeer/s+Ih6Ev1EsznVmAw9SKb5q8QCSYk5mMugUTDVETOQo10CGAF6094/PcJRXgag/uWs4k+13c+/HaeNzrHQyqzWnf9bFFHIvs7Wqov9k+nWv/TwSGplkyBk4bHfhEZNbl+b6T8sZXsv8mTT+INzKT4DCVZ5a/G+NQ6Xo26Aa6Se5DOdzdXOLZ7yIhOi+olip8AOXIIFLb7Vee1+q7Ri6ScUJEKxdEvec5EceXEtWs0U4AnuHgiH0ewJSX9yUp8T4ImCzu00JhTMOH2nXU3PJuGgZgrDifNWXGpFruIBjHWRKpESpy+9HPGoNoxSO/hzSfuM5LKvwX+I96cMK5OC+fKsBh59cMpU4YnqFNh59ZP/kaMDxAXgrVku5s9oB3Rgc7t1rfm6dqycBiwn4QnYJpk7M+gzAbrF/nty8y84ajNdhINXgMgvv3JLC5YTqznBPm3koHSOEF8f4xtCZtzjNN6jNjX8gEvxoRpC9Z+X15c8XBSFyDMBoi0FMDor619XJKgTxNiOYmeH/DLhKEb8MIaug8IFu34FKbG81IsWD+zwA1A5xqXEVlgsfEOcML+Oe2rulkpwQBWnsmY+f/Z80004Mytreo/ME2TDuPJPOi15D0j0CbwG7xzE8qrn0EomBE9mrwH3uuH7df3lzx1HiLAmh2s4MO5R64AoWeAPQW9lWZPzp+2dNgk+0qWpoEsMN+r91yWxUZHgeoC+GJKs13LItH32sGInyUrquMYQjEh44fAEFrMREcS4A7l+875GUmWC6i2MSLyvtAzuPS2IV0t9GU0ooH8cfG5JUeDnsJcVanJTR+XuXPetM+cDyGFTGri3lsCndE9maLgs9iDHtJxzKUBUUakaIPLsXtZD51cS4jhSbbHuzgC21BRtnBhCE2phVYbz4Tj0t90wBmemBaP2eVwv9p4s/JJAHEELvV7k+Gro4FozOoC2WBdqdTPDFB00la08O6ADBdjd2el2pRA7HCTG6v3qwA6RQJfm4UHOXaFYhhiNngxHB4VHZMvpd+YMEqNmtYOb4lzWwI13iHU0Rh0Sj4IaY9EwEy/KjR5dc2DjWGj11SFFCP1uG24fccF+LhtdMNDItI1mBxfaeRelYlsCZwtbbVLnuVQ5izuTzXa48x7CBaHnD3i09BZCuQITLX4d7KkD6CLWWTHrc5onLFJKxRF/p5AEFoqtN3vP9CsLXSYNKxFV5UbxAY5TnAoLFCCo0WbgvtAV77jWSbru9Oq/7ORN9smog4zNuYBZE1uXjxM2xszDduOym7+CxwpJMc7XPknt2XwiANaf2QANMMMH6lmnjTH1RVR7oH4+ts4xVcsdiO/QOlMp+Th9/KIMYyUevR18vHbs+88uxzIG28/58xLZTs6rZc71g9mGw9Q21ugL2sfTc7e0VDMnAmdg+92s2RXQMvkmx+oRc+IsFqgOZzjYcTmnJxZaXsoIsydDVcTalgmhK6/dD0grPLaGgaeXMEw2hN8p8seAHrGRW8+6WBD63NBYaAG0/zwuGOCHUo/BeG9bz39Hsz9Yvs5KvJiLhmM5K+8RlnxIY329REqdFZVyuXyy0NpDueFQelnd0j47Quc7GJGX3QycJiKpLolMtDnpnjgYOvdfycM+JEMZGwpLsBBE8R6vJ3RVczT6DdMtpVQ4l7kOzsPSYlp4qAv5fiqUboyv5eP7G7MOD/qSUwBnMS1p9Vm4Wr8B9w=="
|
||||||
|
_, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedError := "rsa modulus too large"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseECPrivateKey(t *testing.T) {
|
func TestParseECPrivateKey(t *testing.T) {
|
||||||
key := testPrivateKeys["ecdsa"]
|
key := testPrivateKeys["ecdsa"]
|
||||||
|
|
||||||
@@ -260,6 +287,21 @@ func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEncryptedPrivateKeysWithUnsupportedCiphers(t *testing.T) {
|
||||||
|
for _, tt := range testdata.UnsupportedCipherData {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected 'unknown cipher' error for %q, got nil", tt.Name)
|
||||||
|
// If this cipher is now supported, remove it from testdata.UnsupportedCipherData
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "unknown cipher") {
|
||||||
|
t.Errorf("wanted 'unknown cipher' error, got %v", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
|
func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
|
||||||
pem := testdata.PEMEncryptedKeys[0].PEMBytes
|
pem := testdata.PEMEncryptedKeys[0].PEMBytes
|
||||||
for i := 0; i < 4096; i++ {
|
for i := 0; i < 4096; i++ {
|
||||||
@@ -330,6 +372,72 @@ func TestMarshalParsePublicKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseDSAHugeQ(t *testing.T) {
|
||||||
|
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
||||||
|
Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
|
||||||
|
// G and Y: Dummy values, just needs to be < P to pass that specific check
|
||||||
|
G := big.NewInt(2)
|
||||||
|
Y := big.NewInt(5)
|
||||||
|
|
||||||
|
rawKey := struct {
|
||||||
|
P, Q, G, Y *big.Int
|
||||||
|
}{
|
||||||
|
P: P,
|
||||||
|
Q: Q,
|
||||||
|
G: G,
|
||||||
|
Y: Y,
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBytes := Marshal(&rawKey)
|
||||||
|
|
||||||
|
_, _, err := parseDSA(inputBytes)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("parseDSA accepted a DSA key with large Q")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedError := "ssh: unsupported DSA sub-prime size"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDSAYOutOfRange(t *testing.T) {
|
||||||
|
// Valid 1024/160 parameters (values don't need to be a real DSA group,
|
||||||
|
// they only need to pass the checkDSAParams bit-length checks and the
|
||||||
|
// G < P / G > 0 checks).
|
||||||
|
P := new(big.Int).Lsh(big.NewInt(1), 1023)
|
||||||
|
P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
|
||||||
|
Q := new(big.Int).Lsh(big.NewInt(1), 159)
|
||||||
|
Q.SetBit(Q, 0, 1)
|
||||||
|
G := big.NewInt(2)
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
Y *big.Int
|
||||||
|
}{
|
||||||
|
{"Y_zero", big.NewInt(0)},
|
||||||
|
{"Y_negative", big.NewInt(-1)},
|
||||||
|
{"Y_equals_P", new(big.Int).Set(P)},
|
||||||
|
{"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
|
||||||
|
{"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
rawKey := struct {
|
||||||
|
P, Q, G, Y *big.Int
|
||||||
|
}{P: P, Q: Q, G: G, Y: tc.Y}
|
||||||
|
|
||||||
|
_, _, err := parseDSA(Marshal(&rawKey))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("parseDSA accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
|
||||||
|
}
|
||||||
|
expectedError := "DSA public value Y out of range"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalPrivateKey(t *testing.T) {
|
func TestMarshalPrivateKey(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -734,6 +842,296 @@ func TestSKKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skTestHarness builds SK-formatted signatures over a fixed payload
|
||||||
|
// using a caller-supplied signing function, letting tests vary the UP
|
||||||
|
// flag byte without duplicating the wire-format scaffolding.
|
||||||
|
type skTestHarness struct {
|
||||||
|
format string
|
||||||
|
application string
|
||||||
|
data []byte
|
||||||
|
// sign takes the SHA-256 digest of the marshalled SK blob and
|
||||||
|
// returns the value to embed in Signature.Blob. For ECDSA keys the
|
||||||
|
// helper feeds the digest to ecdsa.Sign; for ed25519 the helper
|
||||||
|
// feeds the raw marshalled blob to ed25519.Sign.
|
||||||
|
signDigest func(digest []byte) []byte
|
||||||
|
signBlob func(blob []byte) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h skTestHarness) sign(t *testing.T, flags byte) *Signature {
|
||||||
|
t.Helper()
|
||||||
|
hsh := sha256.New()
|
||||||
|
hsh.Write([]byte(h.application))
|
||||||
|
appDigest := hsh.Sum(nil)
|
||||||
|
|
||||||
|
hsh.Reset()
|
||||||
|
hsh.Write(h.data)
|
||||||
|
dataDigest := hsh.Sum(nil)
|
||||||
|
|
||||||
|
var counter uint32 = 1
|
||||||
|
blob := struct {
|
||||||
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
MessageDigest []byte `ssh:"rest"`
|
||||||
|
}{appDigest, flags, counter, dataDigest}
|
||||||
|
marshalled := Marshal(blob)
|
||||||
|
|
||||||
|
var sigBlob []byte
|
||||||
|
if h.signDigest != nil {
|
||||||
|
hsh.Reset()
|
||||||
|
hsh.Write(marshalled)
|
||||||
|
sigBlob = h.signDigest(hsh.Sum(nil))
|
||||||
|
} else {
|
||||||
|
sigBlob = h.signBlob(marshalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Signature{
|
||||||
|
Format: h.format,
|
||||||
|
Blob: sigBlob,
|
||||||
|
Rest: Marshal(struct {
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
}{flags, counter}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSKUserPresence(t *testing.T) {
|
||||||
|
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := skTestHarness{
|
||||||
|
format: "sk-ecdsa-sha2-nistp256@openssh.com",
|
||||||
|
application: "ssh:",
|
||||||
|
data: []byte("test data"),
|
||||||
|
signDigest: func(digest []byte) []byte {
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, ecKey, digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return Marshal(struct{ R, S *big.Int }{r, s})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := &skECDSAPublicKey{
|
||||||
|
application: h.application,
|
||||||
|
PublicKey: ecKey.PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid signature with UP=1 should pass.
|
||||||
|
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
|
||||||
|
t.Errorf("Verify failed with UP=1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid signature with UP=0 should fail with the user-presence sentinel.
|
||||||
|
sigNoUP := h.sign(t, 0)
|
||||||
|
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
|
||||||
|
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UV set but UP clear must still fail: we only waive UP, never UV-only.
|
||||||
|
if err := pk.Verify(h.data, h.sign(t, 0x04)); !errors.Is(err, errSKMissingUserPresence) {
|
||||||
|
t.Errorf("UV-only (flags=0x04): expected errSKMissingUserPresence, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With noTouchRequired, UP=0 passes; UP=1+UV=1 also passes.
|
||||||
|
pk.noTouchRequired = true
|
||||||
|
if err := pk.Verify(h.data, sigNoUP); err != nil {
|
||||||
|
t.Errorf("Verify with noTouchRequired failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := pk.Verify(h.data, h.sign(t, flagUserPresence|0x04)); err != nil {
|
||||||
|
t.Errorf("Verify UP|UV with noTouchRequired failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSKKeyWithoutUP(t *testing.T) {
|
||||||
|
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
edPub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
skEC := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
|
||||||
|
skED := &skEd25519PublicKey{PublicKey: edPub}
|
||||||
|
certEC := &Certificate{Key: &skECDSAPublicKey{PublicKey: ecKey.PublicKey}}
|
||||||
|
certED := &Certificate{Key: &skEd25519PublicKey{PublicKey: edPub}}
|
||||||
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rsaPub, err := NewPublicKey(&rsaKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
certRSA := &Certificate{Key: rsaPub}
|
||||||
|
|
||||||
|
// SK raw keys: return a clone with the flag set, original untouched.
|
||||||
|
gotEC := skKeyWithoutUP(skEC)
|
||||||
|
if gotEC == skEC {
|
||||||
|
t.Error("skECDSAPublicKey: expected a clone, got the original pointer")
|
||||||
|
}
|
||||||
|
if !gotEC.(*skECDSAPublicKey).noTouchRequired {
|
||||||
|
t.Error("skECDSAPublicKey clone: noTouchRequired not set")
|
||||||
|
}
|
||||||
|
if skEC.noTouchRequired {
|
||||||
|
t.Error("skECDSAPublicKey: original mutated")
|
||||||
|
}
|
||||||
|
|
||||||
|
gotED := skKeyWithoutUP(skED)
|
||||||
|
if gotED == skED {
|
||||||
|
t.Error("skEd25519PublicKey: expected a clone, got the original pointer")
|
||||||
|
}
|
||||||
|
if !gotED.(*skEd25519PublicKey).noTouchRequired {
|
||||||
|
t.Error("skEd25519PublicKey clone: noTouchRequired not set")
|
||||||
|
}
|
||||||
|
if skED.noTouchRequired {
|
||||||
|
t.Error("skEd25519PublicKey: original mutated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate wrapping SK: return a clone of the cert with a cloned
|
||||||
|
// SK key inside. Neither the original cert nor the original inner
|
||||||
|
// key must be mutated.
|
||||||
|
originalInnerEC := certEC.Key
|
||||||
|
gotCertEC := skKeyWithoutUP(certEC)
|
||||||
|
if gotCertEC == certEC {
|
||||||
|
t.Error("*Certificate(SK ecdsa): expected a clone, got the original pointer")
|
||||||
|
}
|
||||||
|
if got := gotCertEC.(*Certificate).Key.(*skECDSAPublicKey); !got.noTouchRequired {
|
||||||
|
t.Error("*Certificate(SK ecdsa): inner clone missing noTouchRequired")
|
||||||
|
}
|
||||||
|
if certEC.Key != originalInnerEC {
|
||||||
|
t.Error("*Certificate(SK ecdsa): original cert's Key pointer mutated")
|
||||||
|
}
|
||||||
|
if originalInnerEC.(*skECDSAPublicKey).noTouchRequired {
|
||||||
|
t.Error("*Certificate(SK ecdsa): original inner key mutated")
|
||||||
|
}
|
||||||
|
|
||||||
|
gotCertED := skKeyWithoutUP(certED)
|
||||||
|
if gotCertED == certED {
|
||||||
|
t.Error("*Certificate(SK ed25519): expected a clone, got the original pointer")
|
||||||
|
}
|
||||||
|
if got := gotCertED.(*Certificate).Key.(*skEd25519PublicKey); !got.noTouchRequired {
|
||||||
|
t.Error("*Certificate(SK ed25519): inner clone missing noTouchRequired")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-SK key inside a cert: return original unchanged (nothing to clone).
|
||||||
|
if got := skKeyWithoutUP(certRSA); got != certRSA {
|
||||||
|
t.Error("*Certificate(RSA): expected the original pointer back")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain non-SK key: return original unchanged.
|
||||||
|
if got := skKeyWithoutUP(rsaPub); got != rsaPub {
|
||||||
|
t.Error("rsaPublicKey: expected the original pointer back")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pathological: *Certificate whose Key is itself a *Certificate.
|
||||||
|
// The SSH cert format forbids this and parseCert rejects it, but
|
||||||
|
// a Go caller can still construct such a value. skKeyWithoutUP
|
||||||
|
// must not recurse into it (or panic); it returns the input
|
||||||
|
// unchanged. This also defends against a hypothetical cycle built
|
||||||
|
// from hand-constructed Certificate pointers.
|
||||||
|
nestedCert := &Certificate{Key: &Certificate{Key: skEC}}
|
||||||
|
if got := skKeyWithoutUP(nestedCert); got != nestedCert {
|
||||||
|
t.Error("*Certificate wrapping *Certificate: expected the original pointer back")
|
||||||
|
}
|
||||||
|
selfCycle := &Certificate{}
|
||||||
|
selfCycle.Key = selfCycle
|
||||||
|
if got := skKeyWithoutUP(selfCycle); got != selfCycle {
|
||||||
|
t.Error("self-referential *Certificate: expected the original pointer back")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoTouchAllowed(t *testing.T) {
|
||||||
|
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sk := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
|
||||||
|
certNoExt := &Certificate{Key: sk}
|
||||||
|
certOptOut := &Certificate{
|
||||||
|
Key: sk,
|
||||||
|
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": ""}},
|
||||||
|
}
|
||||||
|
// Non-empty value: OpenSSH writes "" but any value must be accepted
|
||||||
|
// because the check is presence-only.
|
||||||
|
certOptOutNonEmpty := &Certificate{
|
||||||
|
Key: sk,
|
||||||
|
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": "yes"}},
|
||||||
|
}
|
||||||
|
// no-touch-required belongs in Extensions, never CriticalOptions.
|
||||||
|
// Putting it in CriticalOptions must NOT be treated as opt-out.
|
||||||
|
certCritOnly := &Certificate{
|
||||||
|
Key: sk,
|
||||||
|
Permissions: Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}},
|
||||||
|
}
|
||||||
|
|
||||||
|
permsEmpty := &Permissions{}
|
||||||
|
permsOptOut := &Permissions{Extensions: map[string]string{"no-touch-required": ""}}
|
||||||
|
permsCritOnly := &Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
pub PublicKey
|
||||||
|
perms *Permissions
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"nil perms, no cert", sk, nil, false},
|
||||||
|
{"empty perms, no cert", sk, permsEmpty, false},
|
||||||
|
{"perms opt-out, raw key", sk, permsOptOut, true},
|
||||||
|
{"nil perms, cert opt-out", certOptOut, nil, true},
|
||||||
|
{"nil perms, cert opt-out non-empty value", certOptOutNonEmpty, nil, true},
|
||||||
|
{"nil perms, cert no ext", certNoExt, nil, false},
|
||||||
|
{"perms opt-out, cert no ext", certNoExt, permsOptOut, true},
|
||||||
|
{"empty perms, cert opt-out", certOptOut, permsEmpty, true},
|
||||||
|
// Negative controls: CriticalOptions must not waive UP.
|
||||||
|
{"critical-options only, raw key", sk, permsCritOnly, false},
|
||||||
|
{"critical-options only, cert", certCritOnly, nil, false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := noTouchAllowed(tc.pub, tc.perms); got != tc.want {
|
||||||
|
t.Errorf("noTouchAllowed = %v, want %v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSKUserPresenceEd25519(t *testing.T) {
|
||||||
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h := skTestHarness{
|
||||||
|
format: "sk-ssh-ed25519@openssh.com",
|
||||||
|
application: "ssh:",
|
||||||
|
data: []byte("test data"),
|
||||||
|
signBlob: func(blob []byte) []byte {
|
||||||
|
return ed25519.Sign(priv, blob)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := &skEd25519PublicKey{
|
||||||
|
application: h.application,
|
||||||
|
PublicKey: pub,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
|
||||||
|
t.Errorf("Verify failed with UP=1: %v", err)
|
||||||
|
}
|
||||||
|
sigNoUP := h.sign(t, 0)
|
||||||
|
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
|
||||||
|
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
|
||||||
|
}
|
||||||
|
pk.noTouchRequired = true
|
||||||
|
if err := pk.Verify(h.data, sigNoUP); err != nil {
|
||||||
|
t.Errorf("Verify with noTouchRequired failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewSignerWithAlgos(t *testing.T) {
|
func TestNewSignerWithAlgos(t *testing.T) {
|
||||||
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -160,8 +160,13 @@ func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool
|
|||||||
|
|
||||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
// 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 {
|
||||||
_, ok := db.revoked[string(key.Marshal())]
|
if _, ok := db.revoked[string(key.Marshal())]; ok {
|
||||||
return ok
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const markerCert = "@cert-authority"
|
const markerCert = "@cert-authority"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package knownhosts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -272,7 +274,45 @@ func TestWildcardMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(hanwen): test coverage for certificates.
|
func TestRevokedCA(t *testing.T) {
|
||||||
|
_, caPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
caSigner, err := ssh.NewSignerFromKey(caPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
caKey := caSigner.PublicKey()
|
||||||
|
|
||||||
|
_, hostPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hostKey, err := ssh.NewPublicKey(hostPriv.Public())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := &ssh.Certificate{
|
||||||
|
CertType: ssh.HostCert,
|
||||||
|
Key: hostKey,
|
||||||
|
ValidBefore: ssh.CertTimeInfinity,
|
||||||
|
ValidPrincipals: []string{"server.org"},
|
||||||
|
}
|
||||||
|
if err := cert.SignCert(rand.Reader, caSigner); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caLine := "ssh-ed25519 " + serialize(caKey)[len("ssh-ed25519 "):]
|
||||||
|
knownHostsData := "@revoked server.org " + caLine + "\n" +
|
||||||
|
"@cert-authority server.org " + caLine + "\n"
|
||||||
|
db := testDB(t, knownHostsData)
|
||||||
|
|
||||||
|
if !db.IsRevoked(cert) {
|
||||||
|
t.Error("IsRevoked returned false for certificate signed by revoked CA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testHostname = "hostname"
|
const testHostname = "hostname"
|
||||||
|
|
||||||
|
|||||||
+29
-13
@@ -7,11 +7,13 @@ package ssh
|
|||||||
// Message authentication support
|
// Message authentication support
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/fips140"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"hash"
|
"hash"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type macMode struct {
|
type macMode struct {
|
||||||
@@ -46,23 +48,37 @@ func (t truncatingMAC) Size() int {
|
|||||||
|
|
||||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||||
|
|
||||||
var macModes = map[string]*macMode{
|
// macModes defines the supported MACs. MACs not included are not supported
|
||||||
HMACSHA512ETM: {64, true, func(key []byte) hash.Hash {
|
// and will not be negotiated, even if explicitly configured. When FIPS mode is
|
||||||
|
// enabled, only FIPS-approved algorithms are included.
|
||||||
|
var macModes = map[string]*macMode{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
macModes[HMACSHA512ETM] = &macMode{64, true, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha512.New, key)
|
return hmac.New(sha512.New, key)
|
||||||
}},
|
}}
|
||||||
HMACSHA256ETM: {32, true, func(key []byte) hash.Hash {
|
macModes[HMACSHA256ETM] = &macMode{32, true, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha256.New, key)
|
return hmac.New(sha256.New, key)
|
||||||
}},
|
}}
|
||||||
HMACSHA512: {64, false, func(key []byte) hash.Hash {
|
macModes[HMACSHA512] = &macMode{64, false, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha512.New, key)
|
return hmac.New(sha512.New, key)
|
||||||
}},
|
}}
|
||||||
HMACSHA256: {32, false, func(key []byte) hash.Hash {
|
macModes[HMACSHA256] = &macMode{32, false, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha256.New, key)
|
return hmac.New(sha256.New, key)
|
||||||
}},
|
}}
|
||||||
HMACSHA1: {20, false, func(key []byte) hash.Hash {
|
|
||||||
|
if fips140.Enabled() {
|
||||||
|
defaultMACs = slices.DeleteFunc(defaultMACs, func(algo string) bool {
|
||||||
|
_, ok := macModes[algo]
|
||||||
|
return !ok
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
macModes[HMACSHA1] = &macMode{20, false, func(key []byte) hash.Hash {
|
||||||
return hmac.New(sha1.New, key)
|
return hmac.New(sha1.New, key)
|
||||||
}},
|
}}
|
||||||
InsecureHMACSHA196: {20, false, func(key []byte) hash.Hash {
|
macModes[InsecureHMACSHA196] = &macMode{20, false, func(key []byte) hash.Hash {
|
||||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
||||||
}},
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -792,7 +792,7 @@ func marshalString(to []byte, s []byte) []byte {
|
|||||||
return to[len(s):]
|
return to[len(s):]
|
||||||
}
|
}
|
||||||
|
|
||||||
var bigIntType = reflect.TypeOf((*big.Int)(nil))
|
var bigIntType = reflect.TypeFor[*big.Int]()
|
||||||
|
|
||||||
// Decode a packet into its corresponding message.
|
// Decode a packet into its corresponding message.
|
||||||
func decode(packet []byte) (interface{}, error) {
|
func decode(packet []byte) (interface{}, error) {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ func TestDecode(t *testing.T) {
|
|||||||
userAuthSuccess, err := decode([]byte{msgUserAuthSuccess})
|
userAuthSuccess, err := decode([]byte{msgUserAuthSuccess})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error decoding userAuthSuccessMsg")
|
t.Errorf("error decoding userAuthSuccessMsg")
|
||||||
} else if reflect.TypeOf(userAuthSuccess) != reflect.TypeOf((*userAuthSuccessMsg)(nil)) {
|
} else if _, ok := userAuthSuccess.(*userAuthSuccessMsg); !ok {
|
||||||
t.Errorf("error decoding userAuthSuccessMsg, unexpected %T", userAuthSuccess)
|
t.Errorf("error decoding userAuthSuccessMsg, unexpected %T", userAuthSuccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-4
@@ -91,9 +91,10 @@ type mux struct {
|
|||||||
|
|
||||||
incomingChannels chan NewChannel
|
incomingChannels chan NewChannel
|
||||||
|
|
||||||
globalSentMu sync.Mutex
|
globalSentMu sync.Mutex
|
||||||
globalResponses chan interface{}
|
globalSentPending atomic.Bool
|
||||||
incomingRequests chan *Request
|
globalResponses chan interface{}
|
||||||
|
incomingRequests chan *Request
|
||||||
|
|
||||||
errCond *sync.Cond
|
errCond *sync.Cond
|
||||||
err error
|
err error
|
||||||
@@ -141,6 +142,24 @@ func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []
|
|||||||
if wantReply {
|
if wantReply {
|
||||||
m.globalSentMu.Lock()
|
m.globalSentMu.Lock()
|
||||||
defer m.globalSentMu.Unlock()
|
defer m.globalSentMu.Unlock()
|
||||||
|
|
||||||
|
// Open the gate so that responses arriving while this request is in
|
||||||
|
// flight are allowed to reach globalResponses. Any response arriving
|
||||||
|
// while no request is pending is dropped by handleGlobalPacket.
|
||||||
|
m.globalSentPending.Store(true)
|
||||||
|
defer m.globalSentPending.Store(false)
|
||||||
|
|
||||||
|
// Drain any spurious responses that may have been buffered. This prevents
|
||||||
|
// a previously buffered unexpected response from being consumed instead
|
||||||
|
// of the actual response for this request.
|
||||||
|
drain:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.globalResponses:
|
||||||
|
default:
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.sendMessage(globalRequestMsg{
|
if err := m.sendMessage(globalRequestMsg{
|
||||||
@@ -267,7 +286,16 @@ func (m *mux) handleGlobalPacket(packet []byte) error {
|
|||||||
mux: m,
|
mux: m,
|
||||||
}
|
}
|
||||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||||
m.globalResponses <- msg
|
// Drop responses that arrive when no SendRequest is waiting, to
|
||||||
|
// prevent a malicious peer from staging responses for a future
|
||||||
|
// caller.
|
||||||
|
if !m.globalSentPending.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case m.globalResponses <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("not a global message %#v", msg))
|
panic(fmt.Sprintf("not a global message %#v", msg))
|
||||||
}
|
}
|
||||||
|
|||||||
+529
@@ -825,6 +825,50 @@ func TestMuxChannelWindowDeferredUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMuxChannelRejectRemovesFromMux(t *testing.T) {
|
||||||
|
serverMux, clientMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
t.Cleanup(wg.Wait)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// The server waits for the channel creation request
|
||||||
|
newCh, ok := <-serverMux.incomingChannels
|
||||||
|
if !ok {
|
||||||
|
t.Error("failed to accept channel")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch := newCh.(*channel)
|
||||||
|
|
||||||
|
if serverMux.chanList.getChan(ch.localId) == nil {
|
||||||
|
t.Errorf("channel %d is not in the chanList before Reject", ch.localId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil {
|
||||||
|
t.Errorf("Reject failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverMux.chanList.getChan(ch.localId) != nil {
|
||||||
|
t.Errorf("channel %d is still in the chanList after Reject", ch.localId)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, _, err := clientMux.OpenChannel("test_leak", nil)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error (channel rejected), but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := err.(*OpenChannelError); !ok {
|
||||||
|
t.Errorf("expected *OpenChannelError, got: %T", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't ship code with debug=true.
|
// Don't ship code with debug=true.
|
||||||
func TestDebug(t *testing.T) {
|
func TestDebug(t *testing.T) {
|
||||||
if debugMux {
|
if debugMux {
|
||||||
@@ -837,3 +881,488 @@ func TestDebug(t *testing.T) {
|
|||||||
t.Error("transport debug switched on")
|
t.Error("transport debug switched on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMuxUnexpectedGlobalResponsesDiscarded(t *testing.T) {
|
||||||
|
clientPipe, serverPipe := memPipe()
|
||||||
|
client := newMux(clientPipe)
|
||||||
|
defer serverPipe.Close()
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// Send multiple unexpected global responses, this should not block the
|
||||||
|
// globalResponses channel.
|
||||||
|
for i := range 5 {
|
||||||
|
err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
|
||||||
|
Data: []byte{byte(i)},
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
done <- fmt.Errorf("send success msg %d: %w", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range 5 {
|
||||||
|
err := serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
||||||
|
Data: []byte{byte(i)},
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
done <- fmt.Errorf("send failure msg %d: %w", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now send a global request and wait for the response. This
|
||||||
|
// verifies the mux is still processing packets.
|
||||||
|
err := serverPipe.writePacket(Marshal(globalRequestMsg{
|
||||||
|
Type: "keepalive@golang.org",
|
||||||
|
WantReply: true,
|
||||||
|
Data: nil,
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
done <- fmt.Errorf("send global request: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packet, err := serverPipe.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
done <- fmt.Errorf("read packet: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decoded, err := decode(packet)
|
||||||
|
if err != nil {
|
||||||
|
done <- fmt.Errorf("decode: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch decoded.(type) {
|
||||||
|
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||||
|
// Expected response
|
||||||
|
default:
|
||||||
|
done <- fmt.Errorf("unexpected packet type: %T", decoded)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Handle the incoming request from the server and reply
|
||||||
|
req, ok := <-client.incomingRequests
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("incomingRequests channel closed unexpectedly")
|
||||||
|
}
|
||||||
|
if req.Type != "keepalive@golang.org" {
|
||||||
|
t.Fatalf("unexpected request type: %s", req.Type)
|
||||||
|
}
|
||||||
|
if err := req.Reply(true, nil); err != nil {
|
||||||
|
t.Fatalf("Reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxConcurrentGlobalRequests(t *testing.T) {
|
||||||
|
clientMux, serverMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
const numRequests = 50
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for r := range serverMux.incomingRequests {
|
||||||
|
if r.WantReply {
|
||||||
|
replyData := append([]byte("reply:"), r.Payload...)
|
||||||
|
r.Reply(true, replyData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var clientWg sync.WaitGroup
|
||||||
|
clientWg.Add(numRequests)
|
||||||
|
|
||||||
|
errCh := make(chan error, numRequests)
|
||||||
|
|
||||||
|
for i := range numRequests {
|
||||||
|
go func(id int) {
|
||||||
|
defer clientWg.Done()
|
||||||
|
|
||||||
|
payloadStr := fmt.Sprintf("req-%d", id)
|
||||||
|
payload := []byte(payloadStr)
|
||||||
|
|
||||||
|
// This call blocks until the globalSentMu is acquired.
|
||||||
|
// The mutex ensures that even with many concurrent attempts,
|
||||||
|
// the "drain" and "send" logic happens atomically per request.
|
||||||
|
ok, data, err := clientMux.SendRequest("echo", true, payload)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- fmt.Errorf("req %d error: %v", id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
errCh <- fmt.Errorf("req %d failed (want success)", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "reply:" + payloadStr
|
||||||
|
if string(data) != expected {
|
||||||
|
errCh <- fmt.Errorf("req %d mismatch: got %q, want %q", id, string(data), expected)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientWg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
for err := range errCh {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientMux.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxGlobalResponseDroppedWhenIdle(t *testing.T) {
|
||||||
|
clientPipe, serverPipe := memPipe()
|
||||||
|
clientMux := newMux(clientPipe)
|
||||||
|
defer serverPipe.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// Send a spurious response while no SendRequest is pending.
|
||||||
|
if err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
|
||||||
|
Data: []byte("spurious"),
|
||||||
|
})); err != nil {
|
||||||
|
errCh <- fmt.Errorf("send spurious: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Follow with a global request; once the client observes this on
|
||||||
|
// incomingRequests, the mux loop has necessarily processed (and
|
||||||
|
// dropped) the prior spurious response.
|
||||||
|
if err := serverPipe.writePacket(Marshal(globalRequestMsg{
|
||||||
|
Type: "sync@example.com",
|
||||||
|
WantReply: false,
|
||||||
|
})); err != nil {
|
||||||
|
errCh <- fmt.Errorf("send sync request: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := <-errCh; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, ok := <-clientMux.incomingRequests
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("incomingRequests closed unexpectedly")
|
||||||
|
}
|
||||||
|
if req.Type != "sync@example.com" {
|
||||||
|
t.Fatalf("unexpected sync request type %q", req.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The spurious response preceded the sync request, so by now the mux
|
||||||
|
// loop has processed it. The pending-gate must have caused it to be
|
||||||
|
// dropped rather than buffered.
|
||||||
|
if n := len(clientMux.globalResponses); n != 0 {
|
||||||
|
t.Fatalf("globalResponses buffer should be empty after idle drop, has %d entries", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxStaleResponseDrained(t *testing.T) {
|
||||||
|
// Simulate a stale response sitting in globalResponses (e.g. a response
|
||||||
|
// that slipped in through the pending-gate on a prior SendRequest that
|
||||||
|
// exited without consuming it). The drain step in the next SendRequest
|
||||||
|
// must discard it so the caller receives the correct reply.
|
||||||
|
clientMux, serverMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
clientMux.globalResponses <- &globalRequestSuccessMsg{Data: []byte("stale")}
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for req := range serverMux.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, append([]byte("reply:"), req.Payload...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, data, err := clientMux.SendRequest("test", true, []byte("hello"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("request failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected success response")
|
||||||
|
}
|
||||||
|
if string(data) != "reply:hello" {
|
||||||
|
t.Fatalf("got %q, want %q (drain did not remove stale response)", data, "reply:hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientMux.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxGlobalResponseAcceptedWhilePending(t *testing.T) {
|
||||||
|
// Positive control: when a SendRequest is actually pending, the
|
||||||
|
// response must be delivered (the gate is open).
|
||||||
|
clientMux, serverMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for req := range serverMux.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, []byte("pong"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, data, err := clientMux.SendRequest("ping", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRequest: %v", err)
|
||||||
|
}
|
||||||
|
if !ok || string(data) != "pong" {
|
||||||
|
t.Fatalf("unexpected response: ok=%v data=%q", ok, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientMux.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
|
||||||
|
// A malicious peer that spams channelRequestSuccess/Failure messages
|
||||||
|
// for an open, idle channel must not be able to stall the mux read
|
||||||
|
// loop by filling ch.msg. After the flood, the channel must still be
|
||||||
|
// usable: a subsequent legitimate SendRequest receives its reply.
|
||||||
|
clientMux, serverMux := muxPair()
|
||||||
|
defer serverMux.Close()
|
||||||
|
defer clientMux.Close()
|
||||||
|
|
||||||
|
serverRes := make(chan *channel, 1)
|
||||||
|
go func() {
|
||||||
|
newCh, ok := <-serverMux.incomingChannels
|
||||||
|
if !ok {
|
||||||
|
close(serverRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, _, err := newCh.Accept()
|
||||||
|
if err != nil {
|
||||||
|
close(serverRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverRes <- c.(*channel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientCh, err := clientMux.openChannel("chan", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("openChannel: %v", err)
|
||||||
|
}
|
||||||
|
serverCh := <-serverRes
|
||||||
|
if serverCh == nil {
|
||||||
|
t.Fatal("server did not accept channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spam many unsolicited success/failure responses. More than chanSize
|
||||||
|
// to ensure ch.msg would overflow without the pending-gate.
|
||||||
|
const spam = chanSize * 4
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
for i := range spam {
|
||||||
|
if err := serverCh.ackRequest(i%2 == 0); err != nil {
|
||||||
|
done <- fmt.Errorf("ackRequest %d: %w", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Echo any legitimate request back.
|
||||||
|
for req := range serverCh.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
if err := req.Reply(true, append([]byte("reply:"), req.Payload...)); err != nil {
|
||||||
|
done <- fmt.Errorf("reply: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If the flood had wedged the mux loop, this SendRequest would never
|
||||||
|
// receive a reply.
|
||||||
|
ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRequest: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected success reply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up so the server goroutine can exit.
|
||||||
|
clientCh.Close()
|
||||||
|
serverCh.Close()
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelConcurrentRequests(t *testing.T) {
|
||||||
|
writer, reader, mux := channelPair(t)
|
||||||
|
defer writer.Close()
|
||||||
|
defer reader.Close()
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for req := range writer.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, append([]byte("reply:"), req.Payload...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
const numRequests = 50
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(numRequests)
|
||||||
|
errCh := make(chan error, numRequests)
|
||||||
|
|
||||||
|
for i := 0; i < numRequests; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
defer wg.Done()
|
||||||
|
payload := []byte(fmt.Sprintf("req-%d", id))
|
||||||
|
ok, err := reader.SendRequest("echo", true, payload)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- fmt.Errorf("req %d: %v", id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
errCh <- fmt.Errorf("req %d: expected success", id)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
for err := range errCh {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Close()
|
||||||
|
writer.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelResponseDroppedWhenIdle(t *testing.T) {
|
||||||
|
// A spurious response arriving while no SendRequest is pending must
|
||||||
|
// be dropped rather than buffered in ch.msg.
|
||||||
|
writer, reader, mux := channelPair(t)
|
||||||
|
defer writer.Close()
|
||||||
|
defer reader.Close()
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
// Server sends an unsolicited reply, then a request so we can
|
||||||
|
// synchronise: once the client observes the request, the mux loop has
|
||||||
|
// necessarily processed (and dropped) the prior spurious reply.
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
if err := writer.ackRequest(true); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := writer.SendRequest("sync", false, nil); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
req := <-reader.incomingRequests
|
||||||
|
if req.Type != "sync" {
|
||||||
|
t.Fatalf("unexpected request type %q", req.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(reader.msg); n != 0 {
|
||||||
|
t.Fatalf("ch.msg should be empty after idle drop, has %d entries", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := <-errCh; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelStaleResponseDrained(t *testing.T) {
|
||||||
|
// Simulate a stale response sitting in ch.msg (e.g. a response that
|
||||||
|
// slipped through the pending-gate on a prior SendRequest that exited
|
||||||
|
// without consuming it). The drain step in the next SendRequest must
|
||||||
|
// discard it so the caller receives the correct reply.
|
||||||
|
writer, reader, mux := channelPair(t)
|
||||||
|
defer writer.Close()
|
||||||
|
defer reader.Close()
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
reader.msg <- &channelRequestSuccessMsg{PeersID: reader.remoteId}
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for req := range writer.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, append([]byte("nack:"), req.Payload...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, err := reader.SendRequest("test", true, []byte("hello"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRequest: %v", err)
|
||||||
|
}
|
||||||
|
// If the stale success had been consumed, ok would be true.
|
||||||
|
if ok {
|
||||||
|
t.Fatal("got stale success response; drain did not remove it")
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Close()
|
||||||
|
writer.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelResponseAcceptedWhilePending(t *testing.T) {
|
||||||
|
// Positive control: when a SendRequest is actually pending, the
|
||||||
|
// response must be delivered (the gate is open).
|
||||||
|
writer, reader, mux := channelPair(t)
|
||||||
|
defer writer.Close()
|
||||||
|
defer reader.Close()
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(serverDone)
|
||||||
|
for req := range writer.incomingRequests {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, err := reader.SendRequest("ping", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRequest: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected success")
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Close()
|
||||||
|
writer.Close()
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|||||||
+146
-17
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,16 +34,24 @@ type Permissions struct {
|
|||||||
// or not supported.
|
// or not supported.
|
||||||
CriticalOptions map[string]string
|
CriticalOptions map[string]string
|
||||||
|
|
||||||
// Extensions are extra functionality that the server may
|
// Extensions are extra functionality that the server may offer on
|
||||||
// offer on authenticated connections. Lack of support for an
|
// authenticated connections. Lack of support for an extension does not
|
||||||
// extension does not preclude authenticating a user. Common
|
// preclude authenticating a user. Common extensions are
|
||||||
// extensions are "permit-agent-forwarding",
|
// "permit-agent-forwarding", "permit-X11-forwarding". In general the Go
|
||||||
// "permit-X11-forwarding". The Go SSH library currently does
|
// SSH library does not act on extensions and it is up to server
|
||||||
// not act on any extension, and it is up to server
|
// implementations to honor them; extensions can also be used to pass data
|
||||||
// implementations to honor them. Extensions can be used to
|
// from the authentication callbacks to the server application layer.
|
||||||
// pass data from the authentication callbacks to the server
|
//
|
||||||
// application layer.
|
// The one extension acted upon by this library is "no-touch-required",
|
||||||
|
// which applies only to security-key public keys
|
||||||
|
// (sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com).
|
||||||
|
// When present, it waives the default requirement that SK signatures
|
||||||
|
// assert user presence (i.e. a physical touch of the authenticator)
|
||||||
|
// during signature verification.
|
||||||
Extensions map[string]string
|
Extensions map[string]string
|
||||||
|
|
||||||
|
// ExtraData allows to store user defined data.
|
||||||
|
ExtraData map[any]any
|
||||||
}
|
}
|
||||||
|
|
||||||
type GSSAPIWithMICConfig struct {
|
type GSSAPIWithMICConfig struct {
|
||||||
@@ -80,6 +89,79 @@ type ServerPreAuthConn interface {
|
|||||||
SendAuthBanner(string) error
|
SendAuthBanner(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noTouchRequiredExtension is the extension name used by OpenSSH in
|
||||||
|
// authorized_keys options and certificate extensions to mark keys
|
||||||
|
// whose signatures do not need to assert user presence (touch). See
|
||||||
|
// ssh-keygen(1) and sshd(8).
|
||||||
|
const noTouchRequiredExtension = "no-touch-required"
|
||||||
|
|
||||||
|
// noTouchAllowed reports whether the user presence requirement on
|
||||||
|
// SK signatures should be waived for this authentication attempt. The
|
||||||
|
// requirement is waived when the "no-touch-required" extension is
|
||||||
|
// present either in the Permissions returned by the auth callback
|
||||||
|
// (authorized_keys-level opt-out) or in the certificate's own
|
||||||
|
// Extensions (CA-level opt-out), matching OpenSSH behavior. OpenSSH
|
||||||
|
// reads the per-key opt-out only from cert Extensions and
|
||||||
|
// authorized_keys options (never from CriticalOptions); we follow the
|
||||||
|
// same rule.
|
||||||
|
func noTouchAllowed(pubKey PublicKey, perms *Permissions) bool {
|
||||||
|
if perms != nil {
|
||||||
|
if _, ok := perms.Extensions[noTouchRequiredExtension]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cert, ok := pubKey.(*Certificate); ok {
|
||||||
|
if _, ok := cert.Extensions[noTouchRequiredExtension]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// skKeyWithoutUP returns a PublicKey equivalent to pubKey but whose
|
||||||
|
// Verify accepts SK signatures with the user-presence flag clear. If
|
||||||
|
// pubKey is not (and does not wrap) an SK key, pubKey is returned
|
||||||
|
// unchanged. The returned value never mutates pubKey: for SK keys a
|
||||||
|
// shallow copy is made so that the noTouchRequired flag is set only on
|
||||||
|
// the clone.
|
||||||
|
//
|
||||||
|
// The implementation is iterative rather than recursive. When pubKey
|
||||||
|
// is a *Certificate we unwrap exactly one level to look at the inner
|
||||||
|
// key. The SSH cert format forbids Certificate.Key from being another
|
||||||
|
// Certificate (parseCert rejects it), but nothing stops callers from
|
||||||
|
// constructing such a value directly in Go; a recursive descent could
|
||||||
|
// otherwise be driven to unbounded depth by a hand-crafted or cyclic
|
||||||
|
// Certificate. A malformed input of that shape simply returns
|
||||||
|
// unchanged here.
|
||||||
|
func skKeyWithoutUP(pubKey PublicKey) PublicKey {
|
||||||
|
cert, isCert := pubKey.(*Certificate)
|
||||||
|
target := pubKey
|
||||||
|
if isCert {
|
||||||
|
target = cert.Key
|
||||||
|
}
|
||||||
|
var cloned PublicKey
|
||||||
|
switch k := target.(type) {
|
||||||
|
case *skECDSAPublicKey:
|
||||||
|
c := *k
|
||||||
|
c.noTouchRequired = true
|
||||||
|
cloned = &c
|
||||||
|
case *skEd25519PublicKey:
|
||||||
|
c := *k
|
||||||
|
c.noTouchRequired = true
|
||||||
|
cloned = &c
|
||||||
|
default:
|
||||||
|
// Not an SK key (or a pathological *Certificate wrapping
|
||||||
|
// another *Certificate): pubKey is already usable for Verify.
|
||||||
|
return pubKey
|
||||||
|
}
|
||||||
|
if !isCert {
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
c := *cert
|
||||||
|
c.Key = cloned
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
// ServerConfig holds server specific configuration data.
|
// ServerConfig holds server specific configuration data.
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
// Config contains configuration shared between client and server.
|
// Config contains configuration shared between client and server.
|
||||||
@@ -126,6 +208,21 @@ type ServerConfig struct {
|
|||||||
// Permissions.Extensions entry.
|
// Permissions.Extensions entry.
|
||||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||||
|
|
||||||
|
// VerifiedPublicKeyCallback, if non-nil, is called after a client
|
||||||
|
// successfully confirms having control over a key that was previously
|
||||||
|
// approved by PublicKeyCallback. The permissions object passed to the
|
||||||
|
// callback is the one returned by PublicKeyCallback for the given public
|
||||||
|
// key and its ownership is transferred to the callback. The returned
|
||||||
|
// Permissions object can be the same object, optionally modified, or a
|
||||||
|
// completely new object. If VerifiedPublicKeyCallback is non-nil,
|
||||||
|
// PublicKeyCallback is not allowed to return a PartialSuccessError, which
|
||||||
|
// can instead be returned by VerifiedPublicKeyCallback.
|
||||||
|
//
|
||||||
|
// VerifiedPublicKeyCallback does not affect which authentication methods
|
||||||
|
// are included in the list of methods that can be attempted by the client.
|
||||||
|
VerifiedPublicKeyCallback func(conn ConnMetadata, key PublicKey, permissions *Permissions,
|
||||||
|
signatureAlgorithm string) (*Permissions, error)
|
||||||
|
|
||||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
// KeyboardInteractiveCallback, if non-nil, is called when
|
||||||
// keyboard-interactive authentication is selected (RFC
|
// keyboard-interactive authentication is selected (RFC
|
||||||
// 4256). The client object's Challenge function should be
|
// 4256). The client object's Challenge function should be
|
||||||
@@ -223,8 +320,10 @@ func (c *pubKeyCache) add(candidate cachedPubKey) {
|
|||||||
type ServerConn struct {
|
type ServerConn struct {
|
||||||
Conn
|
Conn
|
||||||
|
|
||||||
// If the succeeding authentication callback returned a
|
// If the succeeding authentication callback returned a non-nil Permissions
|
||||||
// non-nil Permissions pointer, it is stored here.
|
// pointer, it is stored here. These are the permissions from the final,
|
||||||
|
// successful authentication method. Permissions returned by callbacks that
|
||||||
|
// return PartialSuccessError are not preserved and must be nil.
|
||||||
Permissions *Permissions
|
Permissions *Permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +345,7 @@ func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewCha
|
|||||||
fullConf.PublicKeyAuthAlgorithms = defaultPubKeyAuthAlgos
|
fullConf.PublicKeyAuthAlgorithms = defaultPubKeyAuthAlgos
|
||||||
} else {
|
} else {
|
||||||
for _, algo := range fullConf.PublicKeyAuthAlgorithms {
|
for _, algo := range fullConf.PublicKeyAuthAlgorithms {
|
||||||
if !contains(SupportedAlgorithms().PublicKeyAuths, algo) && !contains(InsecureAlgorithms().PublicKeyAuths, algo) {
|
if !slices.Contains(SupportedAlgorithms().PublicKeyAuths, algo) && !slices.Contains(InsecureAlgorithms().PublicKeyAuths, algo) {
|
||||||
c.Close()
|
c.Close()
|
||||||
return nil, nil, nil, fmt.Errorf("ssh: unsupported public key authentication algorithm %s", algo)
|
return nil, nil, nil, fmt.Errorf("ssh: unsupported public key authentication algorithm %s", algo)
|
||||||
}
|
}
|
||||||
@@ -631,7 +730,7 @@ userAuthLoop:
|
|||||||
return nil, parseError(msgUserAuthRequest)
|
return nil, parseError(msgUserAuthRequest)
|
||||||
}
|
}
|
||||||
algo := string(algoBytes)
|
algo := string(algoBytes)
|
||||||
if !contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
|
if !slices.Contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
|
||||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
|
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -652,6 +751,9 @@ userAuthLoop:
|
|||||||
candidate.pubKeyData = pubKeyData
|
candidate.pubKeyData = pubKeyData
|
||||||
candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey)
|
candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey)
|
||||||
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||||
|
if isPartialSuccessError && config.VerifiedPublicKeyCallback != nil {
|
||||||
|
return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined")
|
||||||
|
}
|
||||||
|
|
||||||
if (candidate.result == nil || isPartialSuccessError) &&
|
if (candidate.result == nil || isPartialSuccessError) &&
|
||||||
candidate.perms != nil &&
|
candidate.perms != nil &&
|
||||||
@@ -695,7 +797,7 @@ userAuthLoop:
|
|||||||
// ssh-rsa-cert-v01@openssh.com algorithm with ssh-rsa public
|
// ssh-rsa-cert-v01@openssh.com algorithm with ssh-rsa public
|
||||||
// key type. The algorithm and public key type must be
|
// key type. The algorithm and public key type must be
|
||||||
// consistent: both must be certificate algorithms, or neither.
|
// consistent: both must be certificate algorithms, or neither.
|
||||||
if !contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
|
if !slices.Contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
|
||||||
authErr = fmt.Errorf("ssh: public key type %q not compatible with selected algorithm %q",
|
authErr = fmt.Errorf("ssh: public key type %q not compatible with selected algorithm %q",
|
||||||
pubKey.Type(), algo)
|
pubKey.Type(), algo)
|
||||||
break
|
break
|
||||||
@@ -705,7 +807,7 @@ userAuthLoop:
|
|||||||
// algorithm name that corresponds to algo with
|
// algorithm name that corresponds to algo with
|
||||||
// sig.Format. This is usually the same, but
|
// sig.Format. This is usually the same, but
|
||||||
// for certs, the names differ.
|
// for certs, the names differ.
|
||||||
if !contains(config.PublicKeyAuthAlgorithms, sig.Format) {
|
if !slices.Contains(config.PublicKeyAuthAlgorithms, sig.Format) {
|
||||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -715,13 +817,33 @@ userAuthLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
|
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
|
||||||
|
// pubKey is reused below for VerifiedPublicKeyCallback and
|
||||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
// must remain the key as presented by the client; derive a
|
||||||
|
// separate value for Verify that carries any applicable
|
||||||
|
// no-touch-required opt-out.
|
||||||
|
pubKeyForVerify := pubKey
|
||||||
|
if noTouchAllowed(pubKey, candidate.perms) {
|
||||||
|
pubKeyForVerify = skKeyWithoutUP(pubKey)
|
||||||
|
}
|
||||||
|
if err := pubKeyForVerify.Verify(signedData, sig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authErr = candidate.result
|
authErr = candidate.result
|
||||||
perms = candidate.perms
|
perms = candidate.perms
|
||||||
|
if authErr == nil && config.VerifiedPublicKeyCallback != nil {
|
||||||
|
// Only call VerifiedPublicKeyCallback after the key has been accepted
|
||||||
|
// and successfully verified. If authErr is non-nil, the key is not
|
||||||
|
// considered verified and the callback must not run.
|
||||||
|
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
||||||
|
}
|
||||||
|
if authErr == nil && perms != nil && perms.CriticalOptions != nil {
|
||||||
|
if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" {
|
||||||
|
if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil {
|
||||||
|
authErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "gssapi-with-mic":
|
case "gssapi-with-mic":
|
||||||
if authConfig.GSSAPIWithMICConfig == nil {
|
if authConfig.GSSAPIWithMICConfig == nil {
|
||||||
@@ -796,6 +918,13 @@ userAuthLoop:
|
|||||||
var failureMsg userAuthFailureMsg
|
var failureMsg userAuthFailureMsg
|
||||||
|
|
||||||
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
||||||
|
// Permissions are not preserved between authentication steps. To
|
||||||
|
// avoid confusion about the final state of the connection, we
|
||||||
|
// disallow returning non-nil Permissions combined with
|
||||||
|
// PartialSuccessError.
|
||||||
|
if perms != nil {
|
||||||
|
return nil, errors.New("ssh: permissions must be nil when returning PartialSuccessError")
|
||||||
|
}
|
||||||
// After a partial success error we don't allow changing the user
|
// After a partial success error we don't allow changing the user
|
||||||
// name and execute the NoClientAuthCallback.
|
// name and execute the NoClientAuthCallback.
|
||||||
partialSuccessReturned = true
|
partialSuccessReturned = true
|
||||||
|
|||||||
@@ -6,9 +6,14 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -434,6 +439,472 @@ func TestPreAuthConnAndBanners(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicKeyCallback(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
extraKey := "extra"
|
||||||
|
extraDataString := "just a string"
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if permissions != nil && permissions.ExtraData != nil {
|
||||||
|
if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, permissions.ExtraData) {
|
||||||
|
t.Errorf("expected extra data: %v; got: %v", extraDataString, permissions.ExtraData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("expected extra data is missing")
|
||||||
|
}
|
||||||
|
if signatureAlgorithm != KeyAlgoRSASHA256 {
|
||||||
|
t.Errorf("expected signature algorithm: %q; got: %q", KeyAlgoRSASHA256, signatureAlgorithm)
|
||||||
|
}
|
||||||
|
return permissions, nil
|
||||||
|
},
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
return &Permissions{ExtraData: map[any]any{extraKey: extraDataString}}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
conn, _, _, err := NewServerConn(c1, serverConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected server error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, conn.Permissions.ExtraData) {
|
||||||
|
t.Errorf("expected extra data: %v; got: %v", extraDataString, conn.Permissions.ExtraData)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, &PartialSuccessError{
|
||||||
|
Next: ServerAuthCallbacks{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
if string(password) == clientPassword {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
Password(clientPassword),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("client login error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicKeyCallbackPwdAndKey(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
if string(password) == clientPassword {
|
||||||
|
return nil, &PartialSuccessError{
|
||||||
|
Next: ServerAuthCallbacks{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
Password(clientPassword),
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("client login error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPubKeyCallbackAuthMethods(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("client login succeed with only VerifiedPublicKeyCallback defined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPubKeyCallbackError(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("client login succeed with VerifiedPublicKeyCallback returning an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
return &Permissions{
|
||||||
|
CriticalOptions: map[string]string{
|
||||||
|
sourceAddressCriticalOption: "192.168.99.99",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("client login succeed with VerifiedPublicKeyCallback returning mismatching source-address")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
// Returning PartialSuccessError is not permitted when
|
||||||
|
// VerifiedPublicKeyCallback is defined. This callback is
|
||||||
|
// invoked for both query requests and real authentications,
|
||||||
|
// while VerifiedPublicKeyCallback is only triggered if the
|
||||||
|
// client has proven control of the key.
|
||||||
|
return nil, &PartialSuccessError{
|
||||||
|
Next: ServerAuthCallbacks{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
if string(password) == clientPassword {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, &PartialSuccessError{
|
||||||
|
Next: ServerAuthCallbacks{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
if string(password) == clientPassword {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
Password(clientPassword),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("authentication succeeded with PartialSuccess returned from PublicKeyCallback and VerifiedPublicKeyCallback defined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicKeyCallbackOnError(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
var verifiedCallbackCalled bool
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
verifiedCallbackCalled = true
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
return nil, errors.New("invalid key")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
NewServerConn(c1, serverConf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("authentication should fail")
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
if verifiedCallbackCalled {
|
||||||
|
t.Error("VerifiedPublicKeyCallback called after PublicKeyCallback returned an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifiedPublicKeyCallbackOnly(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
NewServerConn(c1, serverConf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("authentication succeeded with only VerifiedPublicKeyCallback defined")
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialSuccessWithNonNilPerms(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
// Intentionally return non-nil Permissions along with a
|
||||||
|
// PartialSuccessError. Since permissions are reset between
|
||||||
|
// authentication steps, this constitutes invalid library usage
|
||||||
|
// and the server is expected to reject the connection.
|
||||||
|
return &Permissions{Extensions: map[string]string{"permit-port-forwarding": ""}}, &PartialSuccessError{
|
||||||
|
Next: ServerAuthCallbacks{
|
||||||
|
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||||
|
if string(password) == clientPassword {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid credentials")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{
|
||||||
|
PublicKeys(testSigners["rsa"]),
|
||||||
|
Password(clientPassword),
|
||||||
|
},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go NewServerConn(c1, serverConf)
|
||||||
|
|
||||||
|
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("authentication succeeded unexpectedly; server should have rejected non-nil Permissions combined with PartialSuccessError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type markerConn struct {
|
type markerConn struct {
|
||||||
closed uint32
|
closed uint32
|
||||||
used uint32
|
used uint32
|
||||||
@@ -476,3 +947,126 @@ func (*markerConn) RemoteAddr() net.Addr { return nil }
|
|||||||
func (*markerConn) SetDeadline(t time.Time) error { return nil }
|
func (*markerConn) SetDeadline(t time.Time) error { return nil }
|
||||||
func (*markerConn) SetReadDeadline(t time.Time) error { return nil }
|
func (*markerConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
func (*markerConn) SetWriteDeadline(t time.Time) error { return nil }
|
func (*markerConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
// skTestSigner is a Signer that produces SK-ECDSA signatures over
|
||||||
|
// user-auth data, simulating a FIDO/U2F authenticator. The flags
|
||||||
|
// byte (UP and other bits) is caller-controlled so tests can exercise
|
||||||
|
// the server's user-presence enforcement and its opt-out paths. This
|
||||||
|
// is test-only: the real Signer is hardware-backed.
|
||||||
|
type skTestSigner struct {
|
||||||
|
priv *ecdsa.PrivateKey
|
||||||
|
pub PublicKey
|
||||||
|
flags byte
|
||||||
|
application string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *skTestSigner) PublicKey() PublicKey { return s.pub }
|
||||||
|
|
||||||
|
func (s *skTestSigner) Sign(r io.Reader, data []byte) (*Signature, error) {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(s.application))
|
||||||
|
appDigest := h.Sum(nil)
|
||||||
|
h.Reset()
|
||||||
|
h.Write(data)
|
||||||
|
dataDigest := h.Sum(nil)
|
||||||
|
var counter uint32 = 1
|
||||||
|
blob := struct {
|
||||||
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
MessageDigest []byte `ssh:"rest"`
|
||||||
|
}{appDigest, s.flags, counter, dataDigest}
|
||||||
|
h.Reset()
|
||||||
|
h.Write(Marshal(blob))
|
||||||
|
digest := h.Sum(nil)
|
||||||
|
x, y, err := ecdsa.Sign(r, s.priv, digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Signature{
|
||||||
|
Format: KeyAlgoSKECDSA256,
|
||||||
|
Blob: Marshal(struct{ R, S *big.Int }{x, y}),
|
||||||
|
Rest: Marshal(struct {
|
||||||
|
Flags byte
|
||||||
|
Counter uint32
|
||||||
|
}{s.flags, counter}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestServerAuthSKUserPresence drives the full userAuthLoop with an SK
|
||||||
|
// public-key client and verifies the server's wiring of the UP check
|
||||||
|
// and its two opt-out paths: the per-key Permissions.Extensions route
|
||||||
|
// and (via cert) the cert-level route. It also confirms that non-SK
|
||||||
|
// clients are unaffected by the new code path.
|
||||||
|
func TestServerAuthSKUserPresence(t *testing.T) {
|
||||||
|
userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
skPub := &skECDSAPublicKey{application: "ssh:", PublicKey: userKey.PublicKey}
|
||||||
|
|
||||||
|
runAuth := func(t *testing.T, signer Signer, perms *Permissions) error {
|
||||||
|
t.Helper()
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
serverConf := &ServerConfig{
|
||||||
|
PublicKeyCallback: func(ConnMetadata, PublicKey) (*Permissions, error) {
|
||||||
|
return perms, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||||
|
|
||||||
|
serverErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, _, _, err := NewServerConn(c1, serverConf)
|
||||||
|
serverErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConf := &ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
Auth: []AuthMethod{PublicKeys(signer)},
|
||||||
|
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
_, _, _, clientErr := NewClientConn(c2, "", clientConf)
|
||||||
|
<-serverErr
|
||||||
|
return clientErr
|
||||||
|
}
|
||||||
|
|
||||||
|
optOut := &Permissions{Extensions: map[string]string{noTouchRequiredExtension: ""}}
|
||||||
|
|
||||||
|
t.Run("UP=1, default perms accepts", func(t *testing.T) {
|
||||||
|
s := &skTestSigner{priv: userKey, pub: skPub, flags: flagUserPresence, application: "ssh:"}
|
||||||
|
if err := runAuth(t, s, nil); err != nil {
|
||||||
|
t.Errorf("expected auth to succeed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("UP=0, default perms rejects", func(t *testing.T) {
|
||||||
|
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||||
|
if err := runAuth(t, s, nil); err == nil {
|
||||||
|
t.Error("expected auth to fail with UP=0")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("UP=0, perms opt-out accepts", func(t *testing.T) {
|
||||||
|
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||||
|
if err := runAuth(t, s, optOut); err != nil {
|
||||||
|
t.Errorf("expected auth to succeed with opt-out: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("UP=0, perms CriticalOptions does NOT opt out", func(t *testing.T) {
|
||||||
|
s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
|
||||||
|
critOnly := &Permissions{CriticalOptions: map[string]string{noTouchRequiredExtension: ""}}
|
||||||
|
if err := runAuth(t, s, critOnly); err == nil {
|
||||||
|
t.Error("no-touch-required in CriticalOptions must not waive UP")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("non-SK RSA signer unaffected", func(t *testing.T) {
|
||||||
|
if err := runAuth(t, testSigners["rsa"], nil); err != nil {
|
||||||
|
t.Errorf("plain RSA auth must still work: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+7
-1
@@ -106,6 +106,13 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("parse uint32 failed")
|
return nil, errors.New("parse uint32 failed")
|
||||||
}
|
}
|
||||||
|
// Each ASN.1 encoded OID must have a minimum
|
||||||
|
// of 2 bytes; 64 maximum mechanisms is an
|
||||||
|
// arbitrary, but reasonable ceiling.
|
||||||
|
const maxMechs = 64
|
||||||
|
if n > maxMechs || int(n)*2 > len(rest) {
|
||||||
|
return nil, errors.New("invalid mechanism count")
|
||||||
|
}
|
||||||
s := &userAuthRequestGSSAPI{
|
s := &userAuthRequestGSSAPI{
|
||||||
N: n,
|
N: n,
|
||||||
OIDS: make([]asn1.ObjectIdentifier, n),
|
OIDS: make([]asn1.ObjectIdentifier, n),
|
||||||
@@ -122,7 +129,6 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
|
|||||||
if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil {
|
if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,37 @@ func TestParseGSSAPIPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseDubiousGSSAPIPayload(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
payload []byte
|
||||||
|
wanterr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"num mechanisms is unrealistic",
|
||||||
|
[]byte{0xFF, 0x00, 0x00, 0xFF,
|
||||||
|
0x00, 0x00, 0x00, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"num mechanisms greater than payload",
|
||||||
|
[]byte{0x00, 0x00, 0x00, 0x40, // 64, |rest| too small
|
||||||
|
0x00, 0x00, 0x00, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := parseGSSAPIPayload(tc.payload)
|
||||||
|
if tc.wanterr && err == nil {
|
||||||
|
t.Errorf("got nil, want error")
|
||||||
|
}
|
||||||
|
if !tc.wanterr && err != nil {
|
||||||
|
t.Errorf("got %v, want nil", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildMIC(t *testing.T) {
|
func TestBuildMIC(t *testing.T) {
|
||||||
sessionID := []byte{134, 180, 134, 194, 62, 145, 171, 82, 119, 149, 254, 196, 125, 173, 177, 145, 187, 85, 53,
|
sessionID := []byte{134, 180, 134, 194, 62, 145, 171, 82, 119, 149, 254, 196, 125, 173, 177, 145, 187, 85, 53,
|
||||||
183, 44, 150, 219, 129, 166, 195, 19, 33, 209, 246, 175, 121}
|
183, 44, 150, 219, 129, 166, 195, 19, 33, 209, 246, 175, 121}
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
|
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
|
||||||
}
|
}
|
||||||
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
|
ch := c.forwards.add("unix", socketPath)
|
||||||
|
|
||||||
return &unixListener{socketPath, c, ch}, nil
|
return &unixListener{socketPath, c, ch}, nil
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (l *unixListener) Accept() (net.Conn, error) {
|
|||||||
// Close closes the listener.
|
// Close closes the listener.
|
||||||
func (l *unixListener) Close() error {
|
func (l *unixListener) Close() error {
|
||||||
// this also closes the listener.
|
// this also closes the listener.
|
||||||
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
|
l.conn.forwards.remove("unix", l.socketPath)
|
||||||
m := streamLocalChannelForwardMsg{
|
m := streamLocalChannelForwardMsg{
|
||||||
l.socketPath,
|
l.socketPath,
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-44
@@ -11,6 +11,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -22,14 +23,21 @@ import (
|
|||||||
// the returned net.Listener. The listener must be serviced, or the
|
// the returned net.Listener. The listener must be serviced, or the
|
||||||
// SSH connection may hang.
|
// SSH connection may hang.
|
||||||
// N must be "tcp", "tcp4", "tcp6", or "unix".
|
// N must be "tcp", "tcp4", "tcp6", or "unix".
|
||||||
|
//
|
||||||
|
// If the address is a hostname, it is sent to the remote peer as-is, without
|
||||||
|
// being resolved locally, and the Listener Addr method will return a zero IP.
|
||||||
func (c *Client) Listen(n, addr string) (net.Listener, error) {
|
func (c *Client) Listen(n, addr string) (net.Listener, error) {
|
||||||
switch n {
|
switch n {
|
||||||
case "tcp", "tcp4", "tcp6":
|
case "tcp", "tcp4", "tcp6":
|
||||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c.ListenTCP(laddr)
|
port, err := strconv.ParseInt(portStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.listenTCPInternal(host, int(port))
|
||||||
case "unix":
|
case "unix":
|
||||||
return c.ListenUnix(addr)
|
return c.ListenUnix(addr)
|
||||||
default:
|
default:
|
||||||
@@ -102,15 +110,24 @@ func (c *Client) handleForwards() {
|
|||||||
// ListenTCP requests the remote peer open a listening socket
|
// ListenTCP requests the remote peer open a listening socket
|
||||||
// on laddr. Incoming connections will be available by calling
|
// on laddr. Incoming connections will be available by calling
|
||||||
// Accept on the returned net.Listener.
|
// Accept on the returned net.Listener.
|
||||||
|
//
|
||||||
|
// ListenTCP accepts an IP address, to provide a hostname use [Client.Listen]
|
||||||
|
// with "tcp", "tcp4", or "tcp6" network instead.
|
||||||
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
||||||
c.handleForwardsOnce.Do(c.handleForwards)
|
c.handleForwardsOnce.Do(c.handleForwards)
|
||||||
if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
|
if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
|
||||||
return c.autoPortListenWorkaround(laddr)
|
return c.autoPortListenWorkaround(laddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return c.listenTCPInternal(laddr.IP.String(), laddr.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) listenTCPInternal(host string, port int) (net.Listener, error) {
|
||||||
|
c.handleForwardsOnce.Do(c.handleForwards)
|
||||||
|
|
||||||
m := channelForwardMsg{
|
m := channelForwardMsg{
|
||||||
laddr.IP.String(),
|
host,
|
||||||
uint32(laddr.Port),
|
uint32(port),
|
||||||
}
|
}
|
||||||
// send message
|
// send message
|
||||||
ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))
|
ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))
|
||||||
@@ -123,20 +140,33 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
|||||||
|
|
||||||
// If the original port was 0, then the remote side will
|
// If the original port was 0, then the remote side will
|
||||||
// supply a real port number in the response.
|
// supply a real port number in the response.
|
||||||
if laddr.Port == 0 {
|
if port == 0 {
|
||||||
var p struct {
|
var p struct {
|
||||||
Port uint32
|
Port uint32
|
||||||
}
|
}
|
||||||
if err := Unmarshal(resp, &p); err != nil {
|
if err := Unmarshal(resp, &p); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
laddr.Port = int(p.Port)
|
port = int(p.Port)
|
||||||
}
|
}
|
||||||
|
// Construct a local address placeholder for the remote listener. If the
|
||||||
|
// original host is an IP address, preserve it so that Listener.Addr()
|
||||||
|
// reports the same IP. If the host is a hostname or cannot be parsed as an
|
||||||
|
// IP, fall back to IPv4zero. The port field is always set, even if the
|
||||||
|
// original port was 0, because in that case the remote server will assign
|
||||||
|
// one, allowing callers to determine which port was selected.
|
||||||
|
ip := net.IPv4zero
|
||||||
|
if parsed, err := netip.ParseAddr(host); err == nil {
|
||||||
|
ip = net.IP(parsed.AsSlice())
|
||||||
|
}
|
||||||
|
laddr := &net.TCPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
addr := net.JoinHostPort(host, strconv.FormatInt(int64(port), 10))
|
||||||
|
ch := c.forwards.add("tcp", addr)
|
||||||
|
|
||||||
// Register this forward, using the port number we obtained.
|
return &tcpListener{laddr, addr, c, ch}, nil
|
||||||
ch := c.forwards.add(laddr)
|
|
||||||
|
|
||||||
return &tcpListener{laddr, c, ch}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// forwardList stores a mapping between remote
|
// forwardList stores a mapping between remote
|
||||||
@@ -149,8 +179,9 @@ type forwardList struct {
|
|||||||
// forwardEntry represents an established mapping of a laddr on a
|
// forwardEntry represents an established mapping of a laddr on a
|
||||||
// remote ssh server to a channel connected to a tcpListener.
|
// remote ssh server to a channel connected to a tcpListener.
|
||||||
type forwardEntry struct {
|
type forwardEntry struct {
|
||||||
laddr net.Addr
|
addr string // host:port or socket path
|
||||||
c chan forward
|
network string // tcp or unix
|
||||||
|
c chan forward
|
||||||
}
|
}
|
||||||
|
|
||||||
// forward represents an incoming forwarded tcpip connection. The
|
// forward represents an incoming forwarded tcpip connection. The
|
||||||
@@ -161,12 +192,13 @@ type forward struct {
|
|||||||
raddr net.Addr // the raddr of the incoming connection
|
raddr net.Addr // the raddr of the incoming connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *forwardList) add(addr net.Addr) chan forward {
|
func (l *forwardList) add(n, addr string) chan forward {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
f := forwardEntry{
|
f := forwardEntry{
|
||||||
laddr: addr,
|
addr: addr,
|
||||||
c: make(chan forward, 1),
|
network: n,
|
||||||
|
c: make(chan forward, 1),
|
||||||
}
|
}
|
||||||
l.entries = append(l.entries, f)
|
l.entries = append(l.entries, f)
|
||||||
return f.c
|
return f.c
|
||||||
@@ -185,19 +217,20 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
|
|||||||
if port == 0 || port > 65535 {
|
if port == 0 || port > 65535 {
|
||||||
return nil, fmt.Errorf("ssh: port number out of range: %d", port)
|
return nil, fmt.Errorf("ssh: port number out of range: %d", port)
|
||||||
}
|
}
|
||||||
ip := net.ParseIP(string(addr))
|
ip, err := netip.ParseAddr(addr)
|
||||||
if ip == nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr)
|
return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr)
|
||||||
}
|
}
|
||||||
return &net.TCPAddr{IP: ip, Port: int(port)}, nil
|
return &net.TCPAddr{IP: net.IP(ip.AsSlice()), Port: int(port)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
||||||
for ch := range in {
|
for ch := range in {
|
||||||
var (
|
var (
|
||||||
laddr net.Addr
|
addr string
|
||||||
raddr net.Addr
|
network string
|
||||||
err error
|
raddr net.Addr
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
switch channelType := ch.ChannelType(); channelType {
|
switch channelType := ch.ChannelType(); channelType {
|
||||||
case "forwarded-tcpip":
|
case "forwarded-tcpip":
|
||||||
@@ -207,40 +240,34 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 4254 section 7.2 specifies that incoming
|
// RFC 4254 section 7.2 specifies that incoming addresses should
|
||||||
// addresses should list the address, in string
|
// list the address that was connected, in string format. It is the
|
||||||
// format. It is implied that this should be an IP
|
// same address used in the tcpip-forward request. The originator
|
||||||
// address, as it would be impossible to connect to it
|
// address is an IP address instead.
|
||||||
// otherwise.
|
addr = net.JoinHostPort(payload.Addr, strconv.FormatUint(uint64(payload.Port), 10))
|
||||||
laddr, err = parseTCPAddr(payload.Addr, payload.Port)
|
|
||||||
if err != nil {
|
|
||||||
ch.Reject(ConnectionFailed, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch.Reject(ConnectionFailed, err.Error())
|
ch.Reject(ConnectionFailed, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
network = "tcp"
|
||||||
case "forwarded-streamlocal@openssh.com":
|
case "forwarded-streamlocal@openssh.com":
|
||||||
var payload forwardedStreamLocalPayload
|
var payload forwardedStreamLocalPayload
|
||||||
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||||
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
|
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
laddr = &net.UnixAddr{
|
addr = payload.SocketPath
|
||||||
Name: payload.SocketPath,
|
|
||||||
Net: "unix",
|
|
||||||
}
|
|
||||||
raddr = &net.UnixAddr{
|
raddr = &net.UnixAddr{
|
||||||
Name: "@",
|
Name: "@",
|
||||||
Net: "unix",
|
Net: "unix",
|
||||||
}
|
}
|
||||||
|
network = "unix"
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
|
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
|
||||||
}
|
}
|
||||||
if ok := l.forward(laddr, raddr, ch); !ok {
|
if ok := l.forward(network, addr, raddr, ch); !ok {
|
||||||
// Section 7.2, implementations MUST reject spurious incoming
|
// Section 7.2, implementations MUST reject spurious incoming
|
||||||
// connections.
|
// connections.
|
||||||
ch.Reject(Prohibited, "no forward for address")
|
ch.Reject(Prohibited, "no forward for address")
|
||||||
@@ -252,11 +279,11 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
|||||||
|
|
||||||
// remove removes the forward entry, and the channel feeding its
|
// remove removes the forward entry, and the channel feeding its
|
||||||
// listener.
|
// listener.
|
||||||
func (l *forwardList) remove(addr net.Addr) {
|
func (l *forwardList) remove(n, addr string) {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
for i, f := range l.entries {
|
for i, f := range l.entries {
|
||||||
if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
|
if n == f.network && addr == f.addr {
|
||||||
l.entries = append(l.entries[:i], l.entries[i+1:]...)
|
l.entries = append(l.entries[:i], l.entries[i+1:]...)
|
||||||
close(f.c)
|
close(f.c)
|
||||||
return
|
return
|
||||||
@@ -274,11 +301,11 @@ func (l *forwardList) closeAll() {
|
|||||||
l.entries = nil
|
l.entries = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
|
func (l *forwardList) forward(n, addr string, raddr net.Addr, ch NewChannel) bool {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
for _, f := range l.entries {
|
for _, f := range l.entries {
|
||||||
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
|
if n == f.network && addr == f.addr {
|
||||||
f.c <- forward{newCh: ch, raddr: raddr}
|
f.c <- forward{newCh: ch, raddr: raddr}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -288,6 +315,7 @@ func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
|
|||||||
|
|
||||||
type tcpListener struct {
|
type tcpListener struct {
|
||||||
laddr *net.TCPAddr
|
laddr *net.TCPAddr
|
||||||
|
addr string
|
||||||
|
|
||||||
conn *Client
|
conn *Client
|
||||||
in <-chan forward
|
in <-chan forward
|
||||||
@@ -314,13 +342,21 @@ func (l *tcpListener) Accept() (net.Conn, error) {
|
|||||||
|
|
||||||
// Close closes the listener.
|
// Close closes the listener.
|
||||||
func (l *tcpListener) Close() error {
|
func (l *tcpListener) Close() error {
|
||||||
|
host, port, err := net.SplitHostPort(l.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rport, err := strconv.ParseUint(port, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
m := channelForwardMsg{
|
m := channelForwardMsg{
|
||||||
l.laddr.IP.String(),
|
host,
|
||||||
uint32(l.laddr.Port),
|
uint32(rport),
|
||||||
}
|
}
|
||||||
|
|
||||||
// this also closes the listener.
|
// this also closes the listener.
|
||||||
l.conn.forwards.remove(l.laddr)
|
l.conn.forwards.remove("tcp", l.addr)
|
||||||
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
|
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
|
||||||
if err == nil && !ok {
|
if err == nil && !ok {
|
||||||
err = errors.New("ssh: cancel-tcpip-forward failed")
|
err = errors.New("ssh: cancel-tcpip-forward failed")
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ func TestAgentForward(t *testing.T) {
|
|||||||
t.Fatalf("Error adding key: %s", err)
|
t.Fatalf("Error adding key: %s", err)
|
||||||
}
|
}
|
||||||
if err := keyring.Add(agent.AddedKey{
|
if err := keyring.Add(agent.AddedKey{
|
||||||
PrivateKey: testPrivateKeys["ecdsa"],
|
PrivateKey: testPrivateKeys["ecdsa"],
|
||||||
ConfirmBeforeUse: true,
|
LifetimeSecs: 3600,
|
||||||
LifetimeSecs: 3600,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("Error adding key with constraints: %s", err)
|
t.Fatalf("Error adding key with constraints: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,6 @@
|
|||||||
|
|
||||||
// Package test contains integration tests for the
|
// Package test contains integration tests for the
|
||||||
// golang.org/x/crypto/ssh package.
|
// golang.org/x/crypto/ssh package.
|
||||||
|
//
|
||||||
|
// Deprecated: this package is for internal use only.
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// The forwarded address match the listen address because we run the tests
|
||||||
|
// on the same host.
|
||||||
forwardedAddr := sshListener.Addr().String()
|
forwardedAddr := sshListener.Addr().String()
|
||||||
netConn, err := net.Dial(n, forwardedAddr)
|
netConn, err := net.Dial(n, forwardedAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,6 +113,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPortForwardTCP(t *testing.T) {
|
func TestPortForwardTCP(t *testing.T) {
|
||||||
|
testPortForward(t, "tcp", ":0")
|
||||||
|
testPortForward(t, "tcp", "[::]:0")
|
||||||
testPortForward(t, "tcp", "localhost:0")
|
testPortForward(t, "tcp", "localhost:0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func TestClientKeyExchanges(t *testing.T) {
|
|||||||
|
|
||||||
var keyExchanges []string
|
var keyExchanges []string
|
||||||
for _, kex := range config.KeyExchanges {
|
for _, kex := range config.KeyExchanges {
|
||||||
// Exclude ecdh for now, to make them determistic we should use see a
|
// Exclude ecdh for now, to make them deterministic we should use see a
|
||||||
// stream of fixed bytes as the random source.
|
// stream of fixed bytes as the random source.
|
||||||
if !strings.HasPrefix(kex, "ecdh-") {
|
if !strings.HasPrefix(kex, "ecdh-") {
|
||||||
keyExchanges = append(keyExchanges, kex)
|
keyExchanges = append(keyExchanges, kex)
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func TestServerKeyExchanges(t *testing.T) {
|
|||||||
|
|
||||||
var keyExchanges []string
|
var keyExchanges []string
|
||||||
for _, kex := range config.KeyExchanges {
|
for _, kex := range config.KeyExchanges {
|
||||||
// Exclude ecdh for now, to make them determistic we should use see a
|
// Exclude ecdh for now, to make them deterministic we should use see a
|
||||||
// stream of fixed bytes as the random source.
|
// stream of fixed bytes as the random source.
|
||||||
// Exclude ML-KEM because server side is not deterministic.
|
// Exclude ML-KEM because server side is not deterministic.
|
||||||
if !strings.HasPrefix(kex, "ecdh-") && !strings.HasPrefix(kex, "mlkem") {
|
if !strings.HasPrefix(kex, "ecdh-") && !strings.HasPrefix(kex, "mlkem") {
|
||||||
|
|||||||
Vendored
+47
@@ -310,6 +310,53 @@ gbDGyT3bXMQtagvCwoW+/oMTKXiZP5jCJpEO8=
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var UnsupportedCipherData = []struct {
|
||||||
|
Name string
|
||||||
|
EncryptionKey string
|
||||||
|
PEMBytes []byte
|
||||||
|
} {
|
||||||
|
0: {
|
||||||
|
Name: "ed25519-encrypted-chacha20-poly1305",
|
||||||
|
EncryptionKey: "password",
|
||||||
|
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm
|
||||||
|
JjcnlwdAAAABgAAAAQdPyPIjXDRAVHskY0yp9SWwAAAGQAAAABAAAAMwAAAAtzc2gtZWQy
|
||||||
|
NTUxOQAAACBi6qXITEUrmNce/c2lfozxALlKH3o/6sll8G7wzl1lvQAAAJDNlW1sEkvnK0
|
||||||
|
8EecF1vHdPk85yClbh3KkHv09mbGAX/Gk6cJpYEGgJSkO7OEF4kG9DVGGd17+TZbTnM4LD
|
||||||
|
vYAJZExx2XLgJFEtHCVmJjYzwxx7yC7+s6u/XjrSlZS60RHunOPKyq+C+s48sejXvmX+t5
|
||||||
|
0ZoVCI8aftT0ycis3gvLU9sCwJ2UnF6kAV226Z4g2aLkuJbgCDTEcYCRD64K1r
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
Name: "ed25519-encrypted-aes128-gcm",
|
||||||
|
EncryptionKey: "password",
|
||||||
|
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
|
||||||
|
AAGAAAABBeMJIOqiyFwNCvDv6f8tQeAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
|
||||||
|
IGYpUcb3tGp9kF6pppcUdq3EPMr85BaSUdhiXGbhS5YNAAAAkNBtMEu0UlLgToThuQc+4m
|
||||||
|
/o0DfFIERu0sspQivn5RJHCtulVKfU9BMiEnF0+LOMOABMlYesgLOtoMxwm4ZCSWH54kZk
|
||||||
|
vaFyyvvxY+RLDuWNQZCryffIA4+iLCUQR1EdxMDiJweKnGJuD64a+9xTJt47A3Vq4SYzji
|
||||||
|
EuVmM0FqS8lbT2ynYSe3va0Qyw13jEO5qbtCuyG+C5GejL7kX4Z64=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
Name: "ed25519-encrypted-aes256-gcm",
|
||||||
|
EncryptionKey: "password",
|
||||||
|
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
|
||||||
|
AAGAAAABBR1p3vH2Wr/HPL+q20L2rjAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
|
||||||
|
IM3tT1xrAuOHcrBdoLRo/ojWZsAw2lHfF5hJgFEOts5MAAAAkH/YGrDhDw8u+F8e4P+84B
|
||||||
|
tAzvp55Lf1Yl7y34BrVmqlWqw/7boqahOp6iYJHNpcuanzc5T6s7Z3wSSYodbY1uvFOfbj
|
||||||
|
rtP6rIHQIY5J2C40WOYJN8IkZlkwDXwZY0qoE9699ZYmWdwsXRZ7QDhjd2W8ziyZBsttiB
|
||||||
|
kv2ceuJMLT04TrKc2+RUkj4CQYnz7p8EkgZlUozx8wBSxKFGnkP7k=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// SKData contains a list of PubKeys backed by U2F/FIDO2 Security Keys and their test data.
|
// SKData contains a list of PubKeys backed by U2F/FIDO2 Security Keys and their test data.
|
||||||
var SKData = []struct {
|
var SKData = []struct {
|
||||||
Name string
|
Name string
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
@@ -254,6 +255,9 @@ var (
|
|||||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||||
func newPacketCipher(d direction, algs DirectionAlgorithms, kex *kexResult) (packetCipher, error) {
|
func newPacketCipher(d direction, algs DirectionAlgorithms, kex *kexResult) (packetCipher, error) {
|
||||||
cipherMode := cipherModes[algs.Cipher]
|
cipherMode := cipherModes[algs.Cipher]
|
||||||
|
if cipherMode == nil {
|
||||||
|
return nil, fmt.Errorf("ssh: unsupported cipher %v", algs.Cipher)
|
||||||
|
}
|
||||||
|
|
||||||
iv := make([]byte, cipherMode.ivSize)
|
iv := make([]byte, cipherMode.ivSize)
|
||||||
key := make([]byte, cipherMode.keySize)
|
key := make([]byte, cipherMode.keySize)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user