Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45460e0797 | ||
|
|
d37c95e27d | ||
|
|
e2ffffe738 | ||
|
|
60e158ad30 | ||
|
|
1b77d23dc8 | ||
|
|
3872a2bc69 | ||
|
|
9f72eccdf6 | ||
|
|
8f405a4109 | ||
|
|
bb41b3dbe6 | ||
|
|
e04e721997 | ||
|
|
b315afd69a | ||
|
|
d4a85f4749 | ||
|
|
6f39c5226a | ||
|
|
4c4d20b72c | ||
|
|
e3e62d9601 | ||
|
|
5adb68b3e6 | ||
|
|
9beb694f97 | ||
|
|
e5306b2f3e | ||
|
|
d2fe592df1 | ||
|
|
d59570d1a3 | ||
|
|
4aab0d9d6a | ||
|
|
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 | ||
|
|
5307a0ce6d | ||
|
|
9d779377cf | ||
|
|
8f580defa0 | ||
|
|
a4d1237429 | ||
|
|
b8d8dae13d | ||
|
|
f5a2eabcab | ||
|
|
44ecf3af99 | ||
|
|
ef5341b706 | ||
|
|
b999374650 | ||
|
|
c247dead11 | ||
|
|
1fda73153f | ||
|
|
1b4c3d2e8c | ||
|
|
b903b535d3 | ||
|
|
459a9db11b | ||
|
|
74e709ad8a | ||
|
|
b3790b8d91 | ||
|
|
1dc4269656 | ||
|
|
97bf787255 | ||
|
|
952517d181 | ||
|
|
c6fce02826 | ||
|
|
0ae49b8145 | ||
|
|
3bf9d2afd4 | ||
|
|
9bab96736c | ||
|
|
4f9f0ca9fc | ||
|
|
eac7cf0d78 | ||
|
|
18228cd6f1 | ||
|
|
73f6362471 | ||
|
|
ebc8e46315 | ||
|
|
e944286e33 | ||
|
|
78a1fd7893 | ||
|
|
ac58737d25 | ||
|
|
9dbbcf002b | ||
|
|
9f6bf8449a | ||
|
|
aae6e61070 | ||
|
|
9c1aa6af88 | ||
|
|
88199028d7 | ||
|
|
3f311e442e | ||
|
|
1f7c62cd66 | ||
|
|
a5f804889e | ||
|
|
958cde86ef | ||
|
|
51f005cfd4 | ||
|
|
7c358664da | ||
|
|
0091fc8e7c | ||
|
|
388684e50b | ||
|
|
953e809589 | ||
|
|
18f770732f | ||
|
|
2ebaafcdf5 | ||
|
|
4bc0711281 | ||
|
|
c96bba2ced | ||
|
|
6b13eef40b | ||
|
|
959f8f3db0 | ||
|
|
769bcd6997 | ||
|
|
d0a798f774 | ||
|
|
acbcbef23f | ||
|
|
376eb14006 | ||
|
|
b369b723c8 | ||
|
|
6b853fbea3 | ||
|
|
49bf5b80c8 | ||
|
|
24852b6b3f | ||
|
|
bbc689cf5c | ||
|
|
7292932d45 | ||
|
|
f66f74b0a4 | ||
|
|
b0784b7bfb | ||
|
|
911360c8a4 | ||
|
|
89ff08d67c | ||
|
|
e47973b1c1 | ||
|
|
9290511cd2 | ||
|
|
fa5273e461 | ||
|
|
a8ea4be81f | ||
|
|
71d3a4cfdb | ||
|
|
8929309228 | ||
|
|
4a75ba54c2 | ||
|
|
b4f1988a35 | ||
|
|
7042ebcbe0 | ||
|
|
3e90321ac7 | ||
|
|
8c4e668694 | ||
|
|
6018723c74 | ||
|
|
71ed71b4fa | ||
|
|
750a45fe5e | ||
|
|
36b172546b | ||
|
|
80ea76eb17 | ||
|
|
c17aa50fbd | ||
|
|
7cfb9161e8 | ||
|
|
b61b08db44 | ||
|
|
6c21748958 |
@@ -2,20 +2,17 @@
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/crypto)
|
||||
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
## Download/Install
|
||||
|
||||
The easiest way to install is to run `go get -u golang.org/x/crypto/...`. You
|
||||
can also manually git clone the repository to `$GOPATH/src/golang.org/x/crypto`.
|
||||
This repository holds supplementary Go cryptography packages.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
this repository, see https://go.dev/doc/contribute.
|
||||
|
||||
The git repository is https://go.googlesource.com/crypto.
|
||||
|
||||
The main issue tracker for the crypto repository is located at
|
||||
https://github.com/golang/go/issues. Prefix your issue with "x/crypto:" in the
|
||||
https://go.dev/issues. Prefix your issue with "x/crypto:" in the
|
||||
subject line, so it is easy to find.
|
||||
|
||||
Note that contributions to the cryptography package receive additional scrutiny
|
||||
|
||||
+46
-56
@@ -31,12 +31,11 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -353,6 +352,10 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dir.AuthzURL == "" {
|
||||
// Pre-Authorization is unsupported
|
||||
return nil, errPreAuthorizationNotSupported
|
||||
}
|
||||
|
||||
type authzID struct {
|
||||
Type string `json:"type"`
|
||||
@@ -467,7 +470,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
|
||||
// while waiting for a final authorization status.
|
||||
d := retryAfter(res.Header.Get("Retry-After"))
|
||||
if d == 0 {
|
||||
// Given that the fastest challenges TLS-SNI and HTTP-01
|
||||
// Given that the fastest challenges TLS-ALPN and HTTP-01
|
||||
// require a CA to make at least 1 network round trip
|
||||
// and most likely persist a challenge state,
|
||||
// this default delay seems reasonable.
|
||||
@@ -514,7 +517,11 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus(
|
||||
payload := json.RawMessage("{}")
|
||||
if len(chal.Payload) != 0 {
|
||||
payload = chal.Payload
|
||||
}
|
||||
res, err := c.post(ctx, nil, chal.URI, payload, wantStatus(
|
||||
http.StatusOK, // according to the spec
|
||||
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||
))
|
||||
@@ -564,50 +571,28 @@ func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Always returns an error.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
h := hex.EncodeToString(b[:])
|
||||
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||
cert, err = tlsChallengeCert([]string{name}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, name, nil
|
||||
// Deprecated: This challenge type was only present in pre-standardized ACME
|
||||
// protocol drafts and is insecure for use in shared hosting environments.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
|
||||
return tls.Certificate{}, "", errPreRFC
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Always returns an error.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b = sha256.Sum256([]byte(ka))
|
||||
h = hex.EncodeToString(b[:])
|
||||
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||
|
||||
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, sanA, nil
|
||||
// Deprecated: This challenge type was only present in pre-standardized ACME
|
||||
// protocol drafts and is insecure for use in shared hosting environments.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
|
||||
return tls.Certificate{}, "", errPreRFC
|
||||
}
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
// over an identifier (either a DNS name or the textual form of an IPv4 or IPv6
|
||||
// address). For more details on TLS-ALPN-01 see
|
||||
// https://www.rfc-editor.org/rfc/rfc8737 and https://www.rfc-editor.org/rfc/rfc8738
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
@@ -615,9 +600,13 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tl
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// the server name in the TLS ClientHello matches the identifier, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
//
|
||||
// Validation requests for IP address identifiers will use the reverse DNS form in the server name
|
||||
// in the TLS ClientHello since the SNI extension is not supported for IP addresses.
|
||||
// See RFC 8738 Section 6 for more information.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, identifier string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
@@ -647,7 +636,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
return tlsChallengeCert(identifier, newOpt)
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
@@ -701,7 +690,7 @@ func (c *Client) addNonce(h http.Header) {
|
||||
}
|
||||
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
@@ -765,11 +754,15 @@ func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
}
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-ALPN challenges
|
||||
// for the given identifier, using an auto-generated public/private key pair.
|
||||
//
|
||||
// If the provided identifier is a domain name, it will be used as a DNS type SAN and for the
|
||||
// subject common name. If the provided identifier is an IP address it will be used as an IP type
|
||||
// SAN.
|
||||
//
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
func tlsChallengeCert(identifier string, opt []CertOption) (tls.Certificate, error) {
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
@@ -793,9 +786,12 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
|
||||
if ip := net.ParseIP(identifier); ip != nil {
|
||||
tmpl.IPAddresses = []net.IP{ip}
|
||||
} else {
|
||||
tmpl.DNSNames = []string{identifier}
|
||||
tmpl.Subject.CommonName = identifier
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
@@ -808,11 +804,5 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodePEM returns b encoded as PEM with block of type typ.
|
||||
func encodePEM(typ string, b []byte) []byte {
|
||||
pb := &pem.Block{Type: typ, Bytes: b}
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is time.Now, except in tests which can mess with it.
|
||||
var timeNow = time.Now
|
||||
|
||||
+87
-111
@@ -9,20 +9,18 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -254,6 +252,57 @@ func TestAuthorizeValid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeUnsupported(t *testing.T) {
|
||||
const (
|
||||
nonce = "https://example.com/acme/new-nonce"
|
||||
reg = "https://example.com/acme/new-acct"
|
||||
order = "https://example.com/acme/new-order"
|
||||
revoke = "https://example.com/acme/revoke-cert"
|
||||
keychange = "https://example.com/acme/key-change"
|
||||
metaTerms = "https://example.com/acme/terms/2017-5-30"
|
||||
metaWebsite = "https://www.example.com/"
|
||||
metaCAA = "example.com"
|
||||
)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/": // Directory
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{
|
||||
"newNonce": %q,
|
||||
"newAccount": %q,
|
||||
"newOrder": %q,
|
||||
"revokeCert": %q,
|
||||
"keyChange": %q,
|
||||
"meta": {
|
||||
"termsOfService": %q,
|
||||
"website": %q,
|
||||
"caaIdentities": [%q],
|
||||
"externalAccountRequired": true
|
||||
}
|
||||
}`, nonce, reg, order, revoke, keychange, metaTerms, metaWebsite, metaCAA)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/acme/new-authz":
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
client := &Client{Key: testKey, DirectoryURL: ts.URL}
|
||||
dir, err := client.Discover(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dir.AuthzURL != "" {
|
||||
t.Fatalf("expected AuthzURL to be empty, got %q", dir.AuthzURL)
|
||||
}
|
||||
if _, err := client.Authorize(context.Background(), "example.com"); !errors.Is(err, errPreAuthorizationNotSupported) {
|
||||
t.Errorf("expected err to indicate pre-authorization is unsupported, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitAuthorization(t *testing.T) {
|
||||
t.Run("wait loop", func(t *testing.T) {
|
||||
var count int
|
||||
@@ -640,71 +689,6 @@ func TestLinkHeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSSNI01ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
|
||||
// echo -n <token.testKeyECThumbprint> | shasum -a 256
|
||||
san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
|
||||
)
|
||||
|
||||
tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n := len(tlscert.Certificate); n != 1 {
|
||||
t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(tlscert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
|
||||
t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
|
||||
}
|
||||
if cert.DNSNames[0] != name {
|
||||
t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
|
||||
}
|
||||
if cn := cert.Subject.CommonName; cn != san {
|
||||
t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSSNI02ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
|
||||
// echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
|
||||
sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
|
||||
// echo -n <token.testKeyECThumbprint> | shasum -a 256
|
||||
sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
|
||||
)
|
||||
|
||||
tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n := len(tlscert.Certificate); n != 1 {
|
||||
t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(tlscert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names := []string{sanA, sanB}
|
||||
if !reflect.DeepEqual(cert.DNSNames, names) {
|
||||
t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
|
||||
}
|
||||
sort.Strings(cert.DNSNames)
|
||||
i := sort.SearchStrings(cert.DNSNames, name)
|
||||
if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
|
||||
t.Errorf("%v doesn't have %q", cert.DNSNames, name)
|
||||
}
|
||||
if cn := cert.Subject.CommonName; cn != sanA {
|
||||
t.Errorf("CommonName = %q; want %q", cn, sanA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
|
||||
@@ -757,10 +741,11 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
domain := "example.com"
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Subject: pkix.Name{Organization: []string{"Test"}},
|
||||
@@ -769,52 +754,43 @@ func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
|
||||
|
||||
client := newTestClient()
|
||||
cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
|
||||
cert, err := client.TLSALPN01ChallengeCert("token", domain, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, tlscert := range []tls.Certificate{cert1, cert2} {
|
||||
// verify generated cert private key
|
||||
tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey)
|
||||
continue
|
||||
}
|
||||
if tlskey.D.Cmp(key.D) != 0 {
|
||||
t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D)
|
||||
}
|
||||
// verify generated cert public key
|
||||
x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey)
|
||||
continue
|
||||
}
|
||||
if tlspub.N.Cmp(key.N) != 0 {
|
||||
t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
|
||||
}
|
||||
// verify template option
|
||||
sn := big.NewInt(2)
|
||||
if x509Cert.SerialNumber.Cmp(sn) != 0 {
|
||||
t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
|
||||
}
|
||||
org := []string{"Test"}
|
||||
if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
|
||||
t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
|
||||
}
|
||||
for _, v := range x509Cert.DNSNames {
|
||||
if !strings.HasSuffix(v, ".acme.invalid") {
|
||||
t.Errorf("%d: invalid DNSNames element: %q", i, v)
|
||||
}
|
||||
// verify generated cert private key
|
||||
tlskey, ok := cert.PrivateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Fatalf("tlscert.PrivateKey is %T; want *rsa.PrivateKey", cert.PrivateKey)
|
||||
}
|
||||
if tlskey.D.Cmp(key.D) != 0 {
|
||||
t.Errorf("tlskey.D = %v; want %v", tlskey.D, key.D)
|
||||
}
|
||||
// verify generated cert public key
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("x509Cert.PublicKey is %T; want *rsa.PublicKey", x509Cert.PublicKey)
|
||||
}
|
||||
if tlspub.N.Cmp(key.N) != 0 {
|
||||
t.Errorf("tlspub.N = %v; want %v", tlspub.N, key.N)
|
||||
}
|
||||
// verify template option
|
||||
sn := big.NewInt(2)
|
||||
if x509Cert.SerialNumber.Cmp(sn) != 0 {
|
||||
t.Errorf("SerialNumber = %v; want %v", x509Cert.SerialNumber, sn)
|
||||
}
|
||||
org := []string{"Test"}
|
||||
if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
|
||||
t.Errorf("Subject.Organization = %+v; want %+v", x509Cert.Subject.Organization, org)
|
||||
}
|
||||
for _, v := range x509Cert.DNSNames {
|
||||
if v != domain {
|
||||
t.Errorf("invalid DNSNames element: %q", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-19
@@ -134,7 +134,8 @@ type Manager struct {
|
||||
// RenewBefore optionally specifies how early certificates should
|
||||
// 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
|
||||
|
||||
// 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
|
||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
|
||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if m.Prompt == nil {
|
||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
||||
}
|
||||
|
||||
name := hello.ServerName
|
||||
if name == "" {
|
||||
return nil, errors.New("acme/autocert: missing server name")
|
||||
@@ -292,6 +289,10 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// regular domain
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ck := certKey{
|
||||
domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
|
||||
isRSA: !supportsECDSA(hello),
|
||||
@@ -305,9 +306,6 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// first-time
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err = m.createCert(ctx, ck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -463,7 +461,7 @@ func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error
|
||||
leaf: cert.Leaf,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -609,7 +607,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
|
||||
}
|
||||
state.cert = der
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -907,7 +905,7 @@ func httpTokenCacheKey(tokenPath string) string {
|
||||
//
|
||||
// The key argument is a certificate private key.
|
||||
// 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()
|
||||
defer m.renewalMu.Unlock()
|
||||
if m.renewal[ck] != nil {
|
||||
@@ -919,7 +917,7 @@ func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
|
||||
}
|
||||
dr := &domainRenewal{m: m, ck: ck, key: key}
|
||||
m.renewal[ck] = dr
|
||||
dr.start(exp)
|
||||
dr.start(notBefore, notAfter)
|
||||
}
|
||||
|
||||
// stopRenew stops all currently running cert renewal timers.
|
||||
@@ -1027,13 +1025,6 @@ func (m *Manager) hostPolicy() HostPolicy {
|
||||
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 {
|
||||
if m.nowFunc != nil {
|
||||
return m.nowFunc()
|
||||
|
||||
@@ -201,7 +201,7 @@ func TestGetCertificate(t *testing.T) {
|
||||
prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
|
||||
man.Prompt = nil
|
||||
},
|
||||
expectError: "Manager.Prompt not set",
|
||||
expectError: "missing Manager.Prompt",
|
||||
},
|
||||
{
|
||||
name: "trailingDot",
|
||||
@@ -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: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://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::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]:1234", 302, "https://[2602:d1:abcd::c60a]:443/"},
|
||||
{"GET", "http://[2602:d1:abcd::c60a]", 302, "https://[2602:d1:abcd::c60a]/"},
|
||||
{"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/foo", 302, "https://example.org/foo"},
|
||||
{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
||||
@@ -619,7 +619,7 @@ func TestCache(t *testing.T) {
|
||||
PrivateKey: ecdsaKey,
|
||||
}
|
||||
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -694,7 +694,7 @@ func TestValidCert(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key3, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
key3, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func (ca *CAServer) URL() string {
|
||||
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 {
|
||||
if ca.url == "" {
|
||||
panic("Roots called before Start")
|
||||
@@ -239,7 +239,8 @@ type discovery struct {
|
||||
}
|
||||
|
||||
type discoveryMeta struct {
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
Terms string `json:"termsOfService,omitempty"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
@@ -281,6 +282,7 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
NewAccount: ca.serverURL("/new-account"),
|
||||
NewOrder: ca.serverURL("/new-order"),
|
||||
Meta: discoveryMeta{
|
||||
Terms: ca.serverURL("/terms"),
|
||||
ExternalAccountRequired: ca.eabRequired,
|
||||
},
|
||||
}
|
||||
@@ -782,15 +784,3 @@ func decodePayload(v interface{}, r io.Reader) error {
|
||||
func challengeToken(domain, challType string, authzID int) string {
|
||||
return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
|
||||
}
|
||||
|
||||
func unique(a []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
var res []string
|
||||
for _, s := range a {
|
||||
if s != "" && !seen[s] {
|
||||
seen[s] = true
|
||||
res = append(res, s)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -124,32 +123,13 @@ func (ln *listener) Close() error {
|
||||
return ln.tcpListener.Close()
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
}
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func cacheDir() string {
|
||||
const base = "golang-autocert"
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||
case "windows":
|
||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||
if v := os.Getenv(ev); v != "" {
|
||||
return filepath.Join(v, base)
|
||||
}
|
||||
}
|
||||
// Worst case:
|
||||
return filepath.Join(homeDir(), base)
|
||||
cache, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
// Fall back to the root directory.
|
||||
cache = "/.cache"
|
||||
}
|
||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||
return filepath.Join(xdg, base)
|
||||
}
|
||||
return filepath.Join(homeDir(), ".cache", base)
|
||||
|
||||
return filepath.Join(cache, base)
|
||||
}
|
||||
|
||||
+19
-17
@@ -11,9 +11,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
||||
const renewJitter = time.Hour
|
||||
|
||||
// domainRenewal tracks the state used by the periodic timers
|
||||
// renewing a single domain's cert.
|
||||
type domainRenewal struct {
|
||||
@@ -30,13 +27,13 @@ type domainRenewal struct {
|
||||
// defined by the certificate expiration time exp.
|
||||
//
|
||||
// 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()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer != nil {
|
||||
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
|
||||
@@ -79,7 +76,7 @@ func (dr *domainRenewal) renew() {
|
||||
// TODO: rotate dr.key at some point?
|
||||
next, err := dr.do(ctx)
|
||||
if err != nil {
|
||||
next = renewJitter / 2
|
||||
next = time.Hour / 2
|
||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||
}
|
||||
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
|
||||
// but we try nonetheless
|
||||
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||
next := dr.next(tlscert.Leaf.NotAfter)
|
||||
if next > dr.m.renewBefore()+renewJitter {
|
||||
next := dr.next(tlscert.Leaf.NotBefore, tlscert.Leaf.NotAfter)
|
||||
if next > 0 {
|
||||
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||
if ok {
|
||||
state := &certState{
|
||||
@@ -139,18 +136,23 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
return 0, err
|
||||
}
|
||||
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 {
|
||||
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
|
||||
// add a bit of randomness to renew deadline
|
||||
n := pseudoRand.int63n(int64(renewJitter))
|
||||
d -= time.Duration(n)
|
||||
if d < 0 {
|
||||
return 0
|
||||
// next returns the wait time before the next renewal should start.
|
||||
// If manager.RenewBefore is set, it uses that capped at 30 days,
|
||||
// otherwise it uses a default of 1/3 of the cert lifetime.
|
||||
// It builds in a jitter of 10% of the renew threshold, capped at 1 hour.
|
||||
func (dr *domainRenewal) next(notBefore, notAfter time.Time) time.Duration {
|
||||
threshold := min(notAfter.Sub(notBefore)/3, 30*24*time.Hour)
|
||||
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) {}
|
||||
|
||||
@@ -17,27 +17,60 @@ import (
|
||||
|
||||
func TestRenewalNext(t *testing.T) {
|
||||
now := time.Now()
|
||||
man := &Manager{
|
||||
RenewBefore: 7 * 24 * time.Hour,
|
||||
nowFunc: func() time.Time { return now },
|
||||
}
|
||||
defer man.stopRenew()
|
||||
nowFn := func() time.Time { return now }
|
||||
tt := []struct {
|
||||
expiry time.Time
|
||||
min, max time.Duration
|
||||
name string
|
||||
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},
|
||||
{now.Add(time.Hour), 0, 1},
|
||||
{now, 0, 1},
|
||||
{now.Add(-time.Hour), 0, 1},
|
||||
{"default renewal, 1h cert, valid",
|
||||
0, now, time.Hour, 40 * time.Minute, 50 * time.Minute},
|
||||
{"default renewal, 1h cert, should renew",
|
||||
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 i, test := range tt {
|
||||
next := dr.next(test.expiry)
|
||||
if next < test.min || test.max < next {
|
||||
t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max)
|
||||
}
|
||||
for _, test := range tt {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
dr := &domainRenewal{m: &Manager{RenewBefore: test.renewBefore, nowFunc: nowFn}}
|
||||
defer dr.m.stopRenew()
|
||||
|
||||
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
|
||||
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -239,7 +269,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
}
|
||||
|
||||
// trigger renew
|
||||
man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter)
|
||||
man.startRenew(exampleCertKey, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
|
||||
<-renewed
|
||||
func() {
|
||||
man.renewalMu.Lock()
|
||||
|
||||
+4
-7
@@ -66,7 +66,7 @@ func (c *Client) retryTimer() *retryTimer {
|
||||
// The n argument is always bounded between 1 and 30.
|
||||
// The returned value is always greater than 0.
|
||||
func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
const max = 10 * time.Second
|
||||
const maxVal = 10 * time.Second
|
||||
var jitter time.Duration
|
||||
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
|
||||
// Set the minimum to 1ms to avoid a case where
|
||||
@@ -86,10 +86,7 @@ func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
n = 30
|
||||
}
|
||||
d := time.Duration(1<<uint(n-1))*time.Second + jitter
|
||||
if d > max {
|
||||
return max
|
||||
}
|
||||
return d
|
||||
return min(d, maxVal)
|
||||
}
|
||||
|
||||
// retryAfter parses a Retry-After HTTP header value,
|
||||
@@ -131,7 +128,7 @@ func wantStatus(codes ...int) resOkay {
|
||||
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,7 +228,7 @@ func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string,
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, ur
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
|
||||
phead := base64.RawURLEncoding.EncodeToString(phJSON)
|
||||
var payload string
|
||||
if val, ok := claimset.(string); ok {
|
||||
payload = val
|
||||
|
||||
@@ -0,0 +1,809 @@
|
||||
// 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 acme_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
// pebbleModVersion is the module version used for Pebble and Pebble's
|
||||
// challenge test server. It is ignored if `-pebble-local-dir` is provided.
|
||||
pebbleModVersion = "v2.7.0"
|
||||
// startingPort is the first port number used for binding interface
|
||||
// addresses. Each call to takeNextPort() will increment a port number
|
||||
// starting at this value.
|
||||
startingPort = 5555
|
||||
)
|
||||
|
||||
var (
|
||||
pebbleLocalDir = flag.String(
|
||||
"pebble-local-dir",
|
||||
"",
|
||||
"Local Pebble to use, instead of fetching from source",
|
||||
)
|
||||
nextPort atomic.Uint32
|
||||
)
|
||||
|
||||
func init() {
|
||||
nextPort.Store(startingPort)
|
||||
}
|
||||
|
||||
func TestWithPebble(t *testing.T) {
|
||||
// We want to use process groups w/ syscall.Kill, and the acme package
|
||||
// is very platform-agnostic, so skip on non-Linux.
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("skipping pebble tests on non-linux OS")
|
||||
}
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping pebble tests in short mode")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
challSrv func(*environment) (challengeServer, string)
|
||||
}{
|
||||
{
|
||||
name: "TLSALPN01-Issuance",
|
||||
challSrv: func(env *environment) (challengeServer, string) {
|
||||
bindAddr := fmt.Sprintf(":%d", env.config.TLSPort)
|
||||
return newChallTLSServer(bindAddr), bindAddr
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "HTTP01-Issuance",
|
||||
challSrv: func(env *environment) (challengeServer, string) {
|
||||
bindAddr := fmt.Sprintf(":%d", env.config.HTTPPort)
|
||||
return newChallHTTPServer(bindAddr), bindAddr
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := startPebbleEnvironment(t, nil)
|
||||
challSrv, challSrvAddr := tt.challSrv(&env)
|
||||
challSrv.Run()
|
||||
|
||||
t.Cleanup(func() {
|
||||
challSrv.Shutdown()
|
||||
})
|
||||
|
||||
waitForServer(t, challSrvAddr)
|
||||
testIssuance(t, &env, challSrv)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// challengeServer abstracts over the details of running a challenge response
|
||||
// server for some supported acme.Challenge type. Responses are provisioned
|
||||
// during the test issuance process to be presented to the ACME server's
|
||||
// validation authority.
|
||||
type challengeServer interface {
|
||||
Run()
|
||||
Shutdown() error
|
||||
Supported(chal *acme.Challenge) bool
|
||||
Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error
|
||||
}
|
||||
|
||||
// challTLSServer is a simple challenge response server that listens for TLS
|
||||
// connections on a specific port and if they are TLS-ALPN-01 challenge
|
||||
// requests, completes the handshake using the configured challenge response
|
||||
// certificate for the SNI value provided.
|
||||
type challTLSServer struct {
|
||||
*http.Server
|
||||
// mu protects challCerts.
|
||||
mu sync.RWMutex
|
||||
// challCerts is a map from SNI domain name to challenge response certificate.
|
||||
challCerts map[string]*tls.Certificate
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc8737#section-4
|
||||
const acmeTLSAlpnProtocol = "acme-tls/1"
|
||||
|
||||
func newChallTLSServer(address string) *challTLSServer {
|
||||
challServer := &challTLSServer{Server: &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
}, challCerts: make(map[string]*tls.Certificate)}
|
||||
|
||||
// Configure the server to support the TLS-ALPN-01 challenge protocol
|
||||
// and to use a callback for selecting the handshake certificate.
|
||||
challServer.Server.TLSConfig = &tls.Config{
|
||||
NextProtos: []string{acmeTLSAlpnProtocol},
|
||||
GetCertificate: challServer.getCertificate,
|
||||
}
|
||||
|
||||
return challServer
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Shutdown() error {
|
||||
log.Printf("challTLSServer: shutting down")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10)
|
||||
defer cancel()
|
||||
return c.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Run() {
|
||||
go func() {
|
||||
// Note: certFile and keyFile are empty because our config uses a
|
||||
// GetCertificate callback.
|
||||
if err := c.Server.ListenAndServeTLS("", ""); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("challTLSServer error: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Supported(chal *acme.Challenge) bool {
|
||||
return chal.Type == "tls-alpn-01"
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error {
|
||||
respCert, err := client.TLSALPN01ChallengeCert(chal.Token, ident.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("challTLSServer: failed to generate challlenge response cert for %s: %w",
|
||||
ident.Value, err)
|
||||
}
|
||||
|
||||
log.Printf("challTLSServer: setting challenge response certificate for %s", ident.Value)
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.challCerts[ident.Value] = &respCert
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *challTLSServer) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// Verify the request looks like a TLS-ALPN-01 challenge request.
|
||||
if len(clientHello.SupportedProtos) != 1 || clientHello.SupportedProtos[0] != acmeTLSAlpnProtocol {
|
||||
return nil, fmt.Errorf(
|
||||
"challTLSServer: non-TLS-ALPN-01 challenge request received with SupportedProtos: %s",
|
||||
clientHello.SupportedProtos)
|
||||
}
|
||||
|
||||
serverName := clientHello.ServerName
|
||||
|
||||
// TLS-ALPN-01 challenge requests for IP addresses are encoded in the SNI
|
||||
// using the reverse-DNS notation. See RFC 8738 Section 6:
|
||||
// https://www.rfc-editor.org/rfc/rfc8738.html#section-6
|
||||
if strings.HasSuffix(serverName, ".in-addr.arpa") {
|
||||
serverName = strings.TrimSuffix(serverName, ".in-addr.arpa")
|
||||
parts := strings.Split(serverName, ".")
|
||||
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
|
||||
parts[i], parts[j] = parts[j], parts[i]
|
||||
}
|
||||
serverName = strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
log.Printf("challTLSServer: selecting certificate for request from %s for %s",
|
||||
clientHello.Conn.RemoteAddr(), serverName)
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
cert := c.challCerts[serverName]
|
||||
if cert == nil {
|
||||
return nil, fmt.Errorf("challTLSServer: no challenge response certificate configured for %s", serverName)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// challHTTPServer is a simple challenge response server that listens for HTTP
|
||||
// connections on a specific port and if they are HTTP-01 challenge requests,
|
||||
// serves the challenge response key authorization.
|
||||
type challHTTPServer struct {
|
||||
*http.Server
|
||||
// mu protects challMap
|
||||
mu sync.RWMutex
|
||||
// challMap is a mapping from request path to response body.
|
||||
challMap map[string]string
|
||||
}
|
||||
|
||||
func newChallHTTPServer(address string) *challHTTPServer {
|
||||
challServer := &challHTTPServer{
|
||||
Server: &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
},
|
||||
challMap: make(map[string]string),
|
||||
}
|
||||
|
||||
challServer.Server.Handler = challServer
|
||||
|
||||
return challServer
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Supported(chal *acme.Challenge) bool {
|
||||
return chal.Type == "http-01"
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Provision(client *acme.Client, ident acme.AuthzID, chall *acme.Challenge) error {
|
||||
path := client.HTTP01ChallengePath(chall.Token)
|
||||
body, err := client.HTTP01ChallengeResponse(chall.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate HTTP-01 challenge response for %v challenge %s token %s: %w",
|
||||
ident, chall.URI, chall.Token, err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
log.Printf("challHTTPServer: setting challenge response for %s", path)
|
||||
c.challMap[path] = body
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("challHTTPServer: handling %s to %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
response, exists := c.challMap[r.URL.Path]
|
||||
|
||||
if !exists {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Shutdown() error {
|
||||
log.Printf("challHTTPServer: shutting down")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
return c.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Run() {
|
||||
go func() {
|
||||
if err := c.Server.ListenAndServe(); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("challHTTPServer error: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
|
||||
t.Helper()
|
||||
|
||||
// Bound the total issuance process by a timeout of 60 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Create a new ACME account.
|
||||
client := env.client
|
||||
acct, err := client.Register(ctx, &acme.Account{}, acme.AcceptTOS)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register account: %v", err)
|
||||
}
|
||||
if acct.Status != acme.StatusValid {
|
||||
t.Fatalf("expected new account status to be valid, got %v", acct.Status)
|
||||
}
|
||||
log.Printf("registered account: %s", acct.URI)
|
||||
|
||||
// Create a new order for some example identifiers
|
||||
identifiers := []acme.AuthzID{
|
||||
{
|
||||
Type: "dns",
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: "dns",
|
||||
Value: "www.example.com",
|
||||
},
|
||||
{
|
||||
Type: "ip",
|
||||
Value: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
order, err := client.AuthorizeOrder(ctx, identifiers)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create order for %v: %v", identifiers, err)
|
||||
}
|
||||
if order.Status != acme.StatusPending {
|
||||
t.Fatalf("expected new order status to be pending, got %v", order.Status)
|
||||
}
|
||||
orderURL := order.URI
|
||||
log.Printf("created order: %v", orderURL)
|
||||
|
||||
// For each pending authz provision a supported challenge type's response
|
||||
// with the test challenge server, and tell the ACME server to verify it.
|
||||
for _, authzURL := range order.AuthzURLs {
|
||||
authz, err := client.GetAuthorization(ctx, authzURL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get order %s authorization %s: %v",
|
||||
orderURL, authzURL, err)
|
||||
}
|
||||
|
||||
if authz.Status != acme.StatusPending {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, challenge := range authz.Challenges {
|
||||
if challenge.Status != acme.StatusPending || !challSrv.Supported(challenge) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := challSrv.Provision(client, authz.Identifier, challenge); err != nil {
|
||||
t.Fatalf("failed to provision challenge %s: %v", challenge.URI, err)
|
||||
}
|
||||
|
||||
_, err = client.Accept(ctx, challenge)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to accept order %s challenge %s: %v",
|
||||
orderURL, challenge.URI, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the order to become ready for finalization.
|
||||
order, err = client.WaitOrder(ctx, order.URI)
|
||||
if err != nil {
|
||||
var orderErr *acme.OrderError
|
||||
if errors.Is(err, orderErr) {
|
||||
t.Fatalf("failed to wait for order %s: %s: %s", orderURL, err, orderErr.Problem)
|
||||
} else {
|
||||
t.Fatalf("failed to wait for order %s: %s", orderURL, err)
|
||||
}
|
||||
}
|
||||
if order.Status != acme.StatusReady {
|
||||
t.Fatalf("expected order %s status to be ready, got %v",
|
||||
orderURL, order.Status)
|
||||
}
|
||||
|
||||
// Generate a certificate keypair and a CSR for the order identifiers.
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate certificate key: %v", err)
|
||||
}
|
||||
var dnsNames []string
|
||||
var ipAddresses []net.IP
|
||||
for _, ident := range identifiers {
|
||||
switch ident.Type {
|
||||
case "dns":
|
||||
dnsNames = append(dnsNames, ident.Value)
|
||||
case "ip":
|
||||
ipAddresses = append(ipAddresses, net.ParseIP(ident.Value))
|
||||
default:
|
||||
t.Fatalf("unsupported identifier type: %s", ident.Type)
|
||||
}
|
||||
}
|
||||
csrDer, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ipAddresses,
|
||||
}, certKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create CSR: %v", err)
|
||||
}
|
||||
|
||||
// Finalize the order by creating a certificate with our CSR.
|
||||
chain, _, err := client.CreateOrderCert(ctx, order.FinalizeURL, csrDer, true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to finalize order %s with finalize URL %s: %v",
|
||||
orderURL, order.FinalizeURL, err)
|
||||
}
|
||||
|
||||
// Split the chain into the leaf and any intermediates.
|
||||
leaf := chain[0]
|
||||
intermediatesDER := chain[1:]
|
||||
leafCert, err := x509.ParseCertificate(leaf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse order %s leaf certificate: %v", orderURL, err)
|
||||
}
|
||||
intermediates := x509.NewCertPool()
|
||||
for i, intermediateDER := range intermediatesDER {
|
||||
intermediate, err := x509.ParseCertificate(intermediateDER)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse intermediate %d: %v", i, err)
|
||||
}
|
||||
intermediates.AddCert(intermediate)
|
||||
}
|
||||
|
||||
// Verify there is a valid path from the leaf certificate to Pebble's
|
||||
// issuing root using the provided intermediate certificates.
|
||||
roots, err := env.RootCert()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get Pebble issuer root certs: %v", err)
|
||||
}
|
||||
paths, err := leafCert.Verify(x509.VerifyOptions{
|
||||
Intermediates: intermediates,
|
||||
Roots: roots,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate: %v", orderURL, err)
|
||||
}
|
||||
log.Printf("verified %d path(s) from issued leaf certificate to Pebble root CA", len(paths))
|
||||
|
||||
// Also verify that the leaf cert is valid for each of the DNS names
|
||||
// and IP addresses from our order's identifiers.
|
||||
for _, name := range dnsNames {
|
||||
if err := leafCert.VerifyHostname(name); err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate for order DNS name %s: %v",
|
||||
orderURL, name, err)
|
||||
}
|
||||
}
|
||||
for _, ip := range ipAddresses {
|
||||
if err := leafCert.VerifyHostname(ip.String()); err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate for order IP address %s: %v",
|
||||
orderURL, ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type environment struct {
|
||||
config *environmentConfig
|
||||
client *acme.Client
|
||||
}
|
||||
|
||||
// RootCert returns the Pebble CA's primary issuing hierarchy root certificate.
|
||||
// This is generated randomly at each startup and can be used to verify
|
||||
// certificate chains issued by Pebble's ACME interface. Note that this
|
||||
// is separate from the static root certificate used by the Pebble ACME
|
||||
// HTTPS interface.
|
||||
func (e *environment) RootCert() (*x509.CertPool, error) {
|
||||
// NOTE: in the future we may want to consider the alternative chains
|
||||
// returned as Link alternative headers.
|
||||
rootURL := fmt.Sprintf("https://%s/roots/0", e.config.pebbleConfig.ManagementListenAddress)
|
||||
resp, err := e.client.HTTPClient.Get(rootURL)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to GET Pebble root CA from %s: %v", rootURL, err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
rootPEM, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Pebble root CA PEM: %v", err)
|
||||
}
|
||||
rootDERBlock, _ := pem.Decode(rootPEM)
|
||||
rootCA, err := x509.ParseCertificate(rootDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Pebble root CA DER: %v", err)
|
||||
}
|
||||
roots.AddCert(rootCA)
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
// environmentConfig describes the Pebble configuration, and configuration
|
||||
// shared between pebble and pebble-challtestsrv.
|
||||
type environmentConfig struct {
|
||||
pebbleConfig
|
||||
dnsPort uint32
|
||||
}
|
||||
|
||||
// defaultConfig returns an environmentConfig populated with default values.
|
||||
// The provided pebbleDir is used to specify certificate/private key paths
|
||||
// for the HTTPS ACME interface.
|
||||
func defaultConfig(pebbleDir string) environmentConfig {
|
||||
return environmentConfig{
|
||||
pebbleConfig: pebbleConfig{
|
||||
ListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
|
||||
ManagementListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
|
||||
HTTPPort: takeNextPort(),
|
||||
TLSPort: takeNextPort(),
|
||||
Certificate: fmt.Sprintf("%s/test/certs/localhost/cert.pem", pebbleDir),
|
||||
PrivateKey: fmt.Sprintf("%s/test/certs/localhost/key.pem", pebbleDir),
|
||||
OCSPResponderURL: "",
|
||||
ExternalAccountBindingRequired: false,
|
||||
ExternalAccountMACKeys: make(map[string]string),
|
||||
DomainBlocklist: []string{"blocked-domain.example"},
|
||||
Profiles: map[string]struct {
|
||||
Description string
|
||||
ValidityPeriod uint64
|
||||
}{
|
||||
"default": {
|
||||
Description: "default profile",
|
||||
ValidityPeriod: 3600,
|
||||
},
|
||||
},
|
||||
RetryAfter: struct {
|
||||
Authz int
|
||||
Order int
|
||||
}{
|
||||
3,
|
||||
5,
|
||||
},
|
||||
},
|
||||
dnsPort: takeNextPort(),
|
||||
}
|
||||
}
|
||||
|
||||
// pebbleConfig matches the JSON structure of the Pebble configuration file.
|
||||
type pebbleConfig struct {
|
||||
ListenAddress string
|
||||
ManagementListenAddress string
|
||||
HTTPPort uint32
|
||||
TLSPort uint32
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
OCSPResponderURL string
|
||||
ExternalAccountBindingRequired bool
|
||||
ExternalAccountMACKeys map[string]string
|
||||
DomainBlocklist []string
|
||||
Profiles map[string]struct {
|
||||
Description string
|
||||
ValidityPeriod uint64
|
||||
}
|
||||
RetryAfter struct {
|
||||
Authz int
|
||||
Order int
|
||||
}
|
||||
}
|
||||
|
||||
func takeNextPort() uint32 {
|
||||
return nextPort.Add(1) - 1
|
||||
}
|
||||
|
||||
// startPebbleEnvironment is a test helper that spawns Pebble and Pebble
|
||||
// challenge test server processes based on the provided environmentConfig. The
|
||||
// processes will be torn down when the test ends.
|
||||
func startPebbleEnvironment(t *testing.T, config *environmentConfig) environment {
|
||||
t.Helper()
|
||||
|
||||
var pebbleDir string
|
||||
if *pebbleLocalDir != "" {
|
||||
pebbleDir = *pebbleLocalDir
|
||||
} else {
|
||||
pebbleDir = fetchModule(t, "github.com/letsencrypt/pebble/v2", pebbleModVersion)
|
||||
}
|
||||
|
||||
binDir := prepareBinaries(t, pebbleDir)
|
||||
|
||||
if config == nil {
|
||||
cfg := defaultConfig(pebbleDir)
|
||||
config = &cfg
|
||||
}
|
||||
|
||||
marshalConfig := struct {
|
||||
Pebble pebbleConfig
|
||||
}{
|
||||
Pebble: config.pebbleConfig,
|
||||
}
|
||||
|
||||
configData, err := json.Marshal(marshalConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal config: %v", err)
|
||||
}
|
||||
|
||||
configFile, err := os.CreateTemp("", "pebble-config-*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp config file: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { os.Remove(configFile.Name()) })
|
||||
|
||||
if _, err := configFile.Write(configData); err != nil {
|
||||
t.Fatalf("failed to write config file: %v", err)
|
||||
}
|
||||
configFile.Close()
|
||||
|
||||
log.Printf("pebble dir: %s", pebbleDir)
|
||||
log.Printf("config file: %s", configFile.Name())
|
||||
|
||||
// Spawn the Pebble CA server. It answers ACME requests and performs
|
||||
// outbound validations. We configure it to use a mock DNS server that
|
||||
// always answers 127.0.0.1 for all A queries so that validation
|
||||
// requests for any domain name will resolve to our local challenge
|
||||
// server instances.
|
||||
spawnServerProcess(t, binDir, "pebble", "-config", configFile.Name(),
|
||||
"-dnsserver", fmt.Sprintf("127.0.0.1:%d", config.dnsPort),
|
||||
"-strict")
|
||||
|
||||
// Spawn the Pebble challenge test server. We'll use it to mock DNS
|
||||
// responses but disable all the other interfaces. We want to stand
|
||||
// up our own challenge response servers for TLS-ALPN-01,
|
||||
// etc.
|
||||
// Note: we specify -defaultIPv6 "" so that no AAAA records are served.
|
||||
// The LUCI CI runners have issues with IPv6 connectivity on localhost.
|
||||
spawnServerProcess(t, binDir, "pebble-challtestsrv",
|
||||
"-dns01", fmt.Sprintf(":%d", config.dnsPort),
|
||||
"-defaultIPv6", "",
|
||||
"-management", fmt.Sprintf(":%d", takeNextPort()),
|
||||
"-doh", "",
|
||||
"-http01", "",
|
||||
"-tlsalpn01", "",
|
||||
"-https01", "")
|
||||
|
||||
waitForServer(t, config.pebbleConfig.ListenAddress)
|
||||
waitForServer(t, fmt.Sprintf("127.0.0.1:%d", config.dnsPort))
|
||||
|
||||
log.Printf("pebble environment ready")
|
||||
|
||||
// Construct a cert pool that contains the CA certificate used by the ACME
|
||||
// interface's certificate chain. This is separate from the issuing
|
||||
// hierarchy and is used for the ACME client to interact with the ACME
|
||||
// interface without cert verification error.
|
||||
caCertPath := filepath.Join(pebbleDir, "test/certs/pebble.minica.pem")
|
||||
caCert, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read CA certificate %s: %v", caCertPath, err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||
t.Fatalf("failed to parse CA certificate %s", caCertPath)
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create an ACME account keypair/client and verify it can discover
|
||||
// the Pebble server's ACME directory without error.
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate account key: %v", err)
|
||||
}
|
||||
client := &acme.Client{
|
||||
Key: key,
|
||||
HTTPClient: httpClient,
|
||||
DirectoryURL: fmt.Sprintf("https://%s/dir", config.ListenAddress),
|
||||
}
|
||||
_, err = client.Discover(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to discover ACME directory: %v", err)
|
||||
}
|
||||
|
||||
return environment{
|
||||
config: config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func waitForServer(t *testing.T, addr string) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
if conn, err := net.Dial("tcp", addr); err == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(i*100) * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("failed to connect to %q after 20 tries", addr)
|
||||
}
|
||||
|
||||
// fetchModule fetches the module at the given version and returns the directory
|
||||
// containing its source tree. It skips the test if fetching modules is not
|
||||
// possible in this environment.
|
||||
//
|
||||
// Copied from the stdlib cryptotest.FetchModule and adapted to not rely on the
|
||||
// stdlib internal testenv package.
|
||||
func fetchModule(t *testing.T, module, version string) string {
|
||||
// If the default GOMODCACHE doesn't exist, use a temporary directory
|
||||
// instead. (For example, run.bash sets GOPATH=/nonexist-gopath.)
|
||||
out, err := exec.Command("go", "env", "GOMODCACHE").Output()
|
||||
if err != nil {
|
||||
t.Errorf("go env GOMODCACHE: %v\n%s", err, out)
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
t.Logf("%s", ee.Stderr)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
modcacheOk := false
|
||||
if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" {
|
||||
if _, err := os.Stat(gomodcache); err == nil {
|
||||
modcacheOk = true
|
||||
}
|
||||
}
|
||||
if !modcacheOk {
|
||||
t.Setenv("GOMODCACHE", t.TempDir())
|
||||
// Allow t.TempDir() to clean up subdirectories.
|
||||
t.Setenv("GOFLAGS", os.Getenv("GOFLAGS")+" -modcacherw")
|
||||
}
|
||||
|
||||
t.Logf("fetching %s@%s\n", module, version)
|
||||
|
||||
output, err := exec.Command("go", "mod", "download", "-json", module+"@"+version).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to download %s@%s: %s\n%s\n", module, version, err, output)
|
||||
}
|
||||
var j struct {
|
||||
Dir string
|
||||
}
|
||||
if err := json.Unmarshal(output, &j); err != nil {
|
||||
t.Fatalf("failed to parse 'go mod download': %s\n%s\n", err, output)
|
||||
}
|
||||
|
||||
return j.Dir
|
||||
}
|
||||
|
||||
func prepareBinaries(t *testing.T, pebbleDir string) string {
|
||||
t.Helper()
|
||||
|
||||
// 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
|
||||
// is used.
|
||||
binDir := t.TempDir()
|
||||
|
||||
build := func(cmd string) {
|
||||
log.Printf("building %s", cmd)
|
||||
buildCmd := exec.Command(
|
||||
"go",
|
||||
"build", "-o", filepath.Join(binDir, cmd), "-mod", "mod", "./cmd/"+cmd)
|
||||
buildCmd.Dir = pebbleDir
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build %s: %s\n%s\n", cmd, err, output)
|
||||
}
|
||||
}
|
||||
|
||||
build("pebble")
|
||||
build("pebble-challtestsrv")
|
||||
|
||||
return binDir
|
||||
}
|
||||
|
||||
func spawnServerProcess(t *testing.T, dir string, cmd string, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
cmdInstance := exec.Command("./"+cmd, args...)
|
||||
cmdInstance.Dir = dir
|
||||
cmdInstance.Stdout = &stdout
|
||||
cmdInstance.Stderr = &stderr
|
||||
|
||||
if err := cmdInstance.Start(); err != nil {
|
||||
t.Fatalf("failed to start %s: %v", cmd, err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
cmdInstance.Process.Kill()
|
||||
cmdInstance.Wait()
|
||||
|
||||
if t.Failed() || testing.Verbose() {
|
||||
t.Logf("=== %s output ===", cmd)
|
||||
if stdout.Len() > 0 {
|
||||
t.Logf("stdout:\n%s", strings.TrimSpace(stdout.String()))
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
t.Logf("stderr:\n%s", strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+6
-3
@@ -53,6 +53,9 @@ func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tos
|
||||
Contact: acct.Contact,
|
||||
}
|
||||
if c.dir.Terms != "" {
|
||||
if prompt == nil {
|
||||
return nil, errors.New("acme: missing Manager.Prompt to accept server's terms of service")
|
||||
}
|
||||
req.TermsAgreed = prompt(c.dir.Terms)
|
||||
}
|
||||
|
||||
@@ -232,7 +235,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
|
||||
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.
|
||||
//
|
||||
// If a caller needs to poll an order until its status is final,
|
||||
@@ -272,7 +275,7 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case o.Status == StatusInvalid:
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
case o.Status == StatusReady || o.Status == StatusValid:
|
||||
return o, nil
|
||||
}
|
||||
@@ -369,7 +372,7 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
|
||||
}
|
||||
// The only acceptable status post finalize and WaitOrder is "valid".
|
||||
if o.Status != StatusValid {
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
}
|
||||
crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
|
||||
return crt, o.CertURL, err
|
||||
|
||||
+16
-3
@@ -885,11 +885,17 @@ func TestRFC_WaitOrderError(t *testing.T) {
|
||||
s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", s.url("/orders/1"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
s := StatusPending
|
||||
if count > 0 {
|
||||
s = StatusInvalid
|
||||
// https://www.rfc-editor.org/rfc/rfc8555#section-7.3.3
|
||||
errorData := `{
|
||||
"type": "urn:ietf:params:acme:error:userActionRequired",
|
||||
"detail": "Terms of service have changed",
|
||||
"instance": "https://example.com/acme/agreement/?token=W8Ih3PswD-8"
|
||||
}`
|
||||
fmt.Fprintf(w, `{"status": %q, "error": %s}`, StatusInvalid, errorData)
|
||||
} else {
|
||||
fmt.Fprintf(w, `{"status": %q}`, StatusPending)
|
||||
}
|
||||
fmt.Fprintf(w, `{"status": %q}`, s)
|
||||
count++
|
||||
})
|
||||
s.start()
|
||||
@@ -910,6 +916,13 @@ func TestRFC_WaitOrderError(t *testing.T) {
|
||||
if e.Status != StatusInvalid {
|
||||
t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
|
||||
}
|
||||
if e.Problem == nil {
|
||||
t.Errorf("e.Problem = nil")
|
||||
}
|
||||
expectedProbType := "urn:ietf:params:acme:error:userActionRequired"
|
||||
if e.Problem.ProblemType != expectedProbType {
|
||||
t.Errorf("e.Problem.ProblemType = %q; want %q", e.Problem.ProblemType, expectedProbType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_CreateOrderCert(t *testing.T) {
|
||||
|
||||
+26
-4
@@ -7,6 +7,7 @@ package acme
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -55,6 +56,10 @@ var (
|
||||
|
||||
// ErrNoAccount indicates that the Client's key has not been registered with the CA.
|
||||
ErrNoAccount = errors.New("acme: account does not exist")
|
||||
|
||||
// errPreAuthorizationNotSupported indicates that the server does not
|
||||
// support pre-authorization of identifiers.
|
||||
errPreAuthorizationNotSupported = errors.New("acme: pre-authorization is not supported")
|
||||
)
|
||||
|
||||
// A Subproblem describes an ACME subproblem as reported in an Error.
|
||||
@@ -149,17 +154,24 @@ func (a *AuthorizationError) Error() string {
|
||||
|
||||
// OrderError is returned from Client's order related methods.
|
||||
// It indicates the order is unusable and the clients should start over with
|
||||
// AuthorizeOrder.
|
||||
// AuthorizeOrder. A Problem description may be provided with details on
|
||||
// what caused the order to become unusable.
|
||||
//
|
||||
// The clients can still fetch the order object from CA using GetOrder
|
||||
// to inspect its state.
|
||||
type OrderError struct {
|
||||
OrderURL string
|
||||
Status string
|
||||
// Problem is the error that occurred while processing the order.
|
||||
Problem *Error
|
||||
}
|
||||
|
||||
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
|
||||
@@ -288,7 +300,7 @@ type Directory struct {
|
||||
// KeyChangeURL allows to perform account key rollover flow.
|
||||
KeyChangeURL string
|
||||
|
||||
// Term is a URI identifying the current terms of service.
|
||||
// Terms is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
// Website is an HTTP or HTTPS URL locating a website
|
||||
@@ -527,6 +539,16 @@ type Challenge struct {
|
||||
// when this challenge was used.
|
||||
// The type of a non-nil value is *Error.
|
||||
Error error
|
||||
|
||||
// Payload is the JSON-formatted payload that the client sends
|
||||
// to the server to indicate it is ready to respond to the challenge.
|
||||
// When unset, it defaults to an empty JSON object: {}.
|
||||
// For most challenges, the client must not set Payload,
|
||||
// see https://tools.ietf.org/html/rfc8555#section-7.5.1.
|
||||
// Payload is used only for newer challenges (such as "device-attest-01")
|
||||
// where the client must send additional data for the server to validate
|
||||
// the challenge.
|
||||
Payload json.RawMessage
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
@@ -604,7 +626,7 @@ func (*certOptKey) privateCertOpt() {}
|
||||
//
|
||||
// In TLS ChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
// The DNSNames or IPAddresses fields of t are always overwritten for tls-alpn challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
@@ -217,3 +217,19 @@ func TestErrorStringerWithSubproblems(t *testing.T) {
|
||||
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderErrorProblem(t *testing.T) {
|
||||
oe := &OrderError{
|
||||
OrderURL: "url",
|
||||
Status: "invalid",
|
||||
Problem: &Error{
|
||||
StatusCode: 400,
|
||||
ProblemType: "type",
|
||||
Detail: "detail",
|
||||
},
|
||||
}
|
||||
want := "acme: order url status: invalid; problem: 400 type: detail"
|
||||
if got := oe.Error(); got != want {
|
||||
t.Errorf("oe.Error() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
+11
-7
@@ -6,7 +6,7 @@
|
||||
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
||||
// 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
|
||||
// the parameter recommendations for your scenario.
|
||||
@@ -17,7 +17,7 @@
|
||||
// It uses data-independent memory access, which is preferred for password
|
||||
// hashing and password-based key derivation. Argon2i requires more passes over
|
||||
// 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.
|
||||
//
|
||||
// # Argon2id
|
||||
@@ -27,11 +27,11 @@
|
||||
// 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-
|
||||
// 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.
|
||||
//
|
||||
// [1] 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
|
||||
// [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
|
||||
package argon2
|
||||
|
||||
import (
|
||||
@@ -59,7 +59,7 @@ const (
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// 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 {
|
||||
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)
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// 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 {
|
||||
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
|
||||
type InvalidCostError int
|
||||
|
||||
func (ic InvalidCostError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed inclusive range %d..%d", int(ic), MinCost, MaxCost)
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,13 +4,13 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -4,12 +4,12 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ hash.XOF = (*xof)(nil)
|
||||
|
||||
func fromHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
// XOF defines the interface to hash functions that
|
||||
// support arbitrary-length output.
|
||||
//
|
||||
// New callers should prefer the standard library [hash.XOF].
|
||||
type XOF interface {
|
||||
// Write absorbs more data into the hash's state. It panics if called
|
||||
// after Read.
|
||||
@@ -47,6 +49,8 @@ const maxOutputLength = (1 << 32) * 64
|
||||
//
|
||||
// A non-nil key turns the hash into a MAC. The key must between
|
||||
// zero and 32 bytes long.
|
||||
//
|
||||
// The result can be safely interface-upgraded to [hash.XOF].
|
||||
func NewXOF(size uint32, key []byte) (XOF, error) {
|
||||
if len(key) > Size {
|
||||
return nil, errKeySize
|
||||
@@ -93,6 +97,10 @@ func (x *xof) Clone() XOF {
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (x *xof) BlockSize() int {
|
||||
return x.d.BlockSize()
|
||||
}
|
||||
|
||||
func (x *xof) Reset() {
|
||||
x.cfg[0] = byte(Size)
|
||||
binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -29,7 +29,7 @@ loop:
|
||||
MOVD $NUM_ROUNDS, R21
|
||||
VLD1 (R11), [V30.S4, V31.S4]
|
||||
|
||||
// load contants
|
||||
// load constants
|
||||
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
|
||||
WORD $0x4D60E940
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!arm64 && !s390x && !ppc64le) || !gc || purego
|
||||
//go:build (!arm64 && !s390x && !ppc64 && !ppc64le) || !gc || purego
|
||||
|
||||
package chacha20
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
//go:build gc && !purego && (ppc64 || ppc64le)
|
||||
|
||||
package chacha20
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// The differences in this and the original implementation are
|
||||
// due to the calling conventions and initialization of constants.
|
||||
|
||||
//go:build gc && !purego
|
||||
//go:build gc && !purego && (ppc64 || ppc64le)
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -36,32 +36,68 @@
|
||||
// for VPERMXOR
|
||||
#define MASK R18
|
||||
|
||||
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
|
||||
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
|
||||
DATA consts<>+0x10(SB)/8, $0x0000000000000001
|
||||
DATA consts<>+0x18(SB)/8, $0x0000000000000000
|
||||
DATA consts<>+0x20(SB)/8, $0x0000000000000004
|
||||
DATA consts<>+0x28(SB)/8, $0x0000000000000000
|
||||
DATA consts<>+0x30(SB)/8, $0x0a0b08090e0f0c0d
|
||||
DATA consts<>+0x38(SB)/8, $0x0203000106070405
|
||||
DATA consts<>+0x40(SB)/8, $0x090a0b080d0e0f0c
|
||||
DATA consts<>+0x48(SB)/8, $0x0102030005060704
|
||||
DATA consts<>+0x50(SB)/8, $0x6170786561707865
|
||||
DATA consts<>+0x58(SB)/8, $0x6170786561707865
|
||||
DATA consts<>+0x60(SB)/8, $0x3320646e3320646e
|
||||
DATA consts<>+0x68(SB)/8, $0x3320646e3320646e
|
||||
DATA consts<>+0x70(SB)/8, $0x79622d3279622d32
|
||||
DATA consts<>+0x78(SB)/8, $0x79622d3279622d32
|
||||
DATA consts<>+0x80(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x90(SB)/8, $0x0000000100000000
|
||||
DATA consts<>+0x98(SB)/8, $0x0000000300000002
|
||||
DATA consts<>+0xa0(SB)/8, $0x5566774411223300
|
||||
DATA consts<>+0xa8(SB)/8, $0xddeeffcc99aabb88
|
||||
DATA consts<>+0xb0(SB)/8, $0x6677445522330011
|
||||
DATA consts<>+0xb8(SB)/8, $0xeeffccddaabb8899
|
||||
DATA consts<>+0x00(SB)/4, $0x61707865
|
||||
DATA consts<>+0x04(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x08(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x0c(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x10(SB)/4, $0x00000001
|
||||
DATA consts<>+0x14(SB)/4, $0x00000000
|
||||
DATA consts<>+0x18(SB)/4, $0x00000000
|
||||
DATA consts<>+0x1c(SB)/4, $0x00000000
|
||||
DATA consts<>+0x20(SB)/4, $0x00000004
|
||||
DATA consts<>+0x24(SB)/4, $0x00000000
|
||||
DATA consts<>+0x28(SB)/4, $0x00000000
|
||||
DATA consts<>+0x2c(SB)/4, $0x00000000
|
||||
DATA consts<>+0x30(SB)/4, $0x0e0f0c0d
|
||||
DATA consts<>+0x34(SB)/4, $0x0a0b0809
|
||||
DATA consts<>+0x38(SB)/4, $0x06070405
|
||||
DATA consts<>+0x3c(SB)/4, $0x02030001
|
||||
DATA consts<>+0x40(SB)/4, $0x0d0e0f0c
|
||||
DATA consts<>+0x44(SB)/4, $0x090a0b08
|
||||
DATA consts<>+0x48(SB)/4, $0x05060704
|
||||
DATA consts<>+0x4c(SB)/4, $0x01020300
|
||||
DATA consts<>+0x50(SB)/4, $0x61707865
|
||||
DATA consts<>+0x54(SB)/4, $0x61707865
|
||||
DATA consts<>+0x58(SB)/4, $0x61707865
|
||||
DATA consts<>+0x5c(SB)/4, $0x61707865
|
||||
DATA consts<>+0x60(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x64(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x68(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x6c(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x70(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x74(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x78(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x7c(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x80(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x84(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x88(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x8c(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x90(SB)/4, $0x00000000
|
||||
DATA consts<>+0x94(SB)/4, $0x00000001
|
||||
DATA consts<>+0x98(SB)/4, $0x00000002
|
||||
DATA consts<>+0x9c(SB)/4, $0x00000003
|
||||
DATA consts<>+0xa0(SB)/4, $0x11223300
|
||||
DATA consts<>+0xa4(SB)/4, $0x55667744
|
||||
DATA consts<>+0xa8(SB)/4, $0x99aabb88
|
||||
DATA consts<>+0xac(SB)/4, $0xddeeffcc
|
||||
DATA consts<>+0xb0(SB)/4, $0x22330011
|
||||
DATA consts<>+0xb4(SB)/4, $0x66774455
|
||||
DATA consts<>+0xb8(SB)/4, $0xaabb8899
|
||||
DATA consts<>+0xbc(SB)/4, $0xeeffccdd
|
||||
GLOBL consts<>(SB), RODATA, $0xc0
|
||||
|
||||
#ifdef GOARCH_ppc64
|
||||
#define BE_XXBRW_INIT() \
|
||||
LVSL (R0)(R0), V24 \
|
||||
VSPLTISB $3, V25 \
|
||||
VXOR V24, V25, V24 \
|
||||
|
||||
#define BE_XXBRW(vr) VPERM vr, vr, V24, vr
|
||||
#else
|
||||
#define BE_XXBRW_INIT()
|
||||
#define BE_XXBRW(vr)
|
||||
#endif
|
||||
|
||||
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
MOVD out+0(FP), OUT
|
||||
@@ -94,6 +130,8 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
// Clear V27
|
||||
VXOR V27, V27, V27
|
||||
|
||||
BE_XXBRW_INIT()
|
||||
|
||||
// V28
|
||||
LXVW4X (CONSTBASE)(R11), VS60
|
||||
|
||||
@@ -299,6 +337,11 @@ loop_vsx:
|
||||
VADDUWM V8, V18, V8
|
||||
VADDUWM V12, V19, V12
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(V12)
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
@@ -327,6 +370,11 @@ loop_vsx:
|
||||
VADDUWM V9, V18, V8
|
||||
VADDUWM V13, V19, V12
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(V12)
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
@@ -334,8 +382,8 @@ loop_vsx:
|
||||
LXVW4X (INP)(R8), VS60
|
||||
LXVW4X (INP)(R9), VS61
|
||||
LXVW4X (INP)(R10), VS62
|
||||
VXOR V27, V0, V27
|
||||
|
||||
VXOR V27, V0, V27
|
||||
VXOR V28, V4, V28
|
||||
VXOR V29, V8, V29
|
||||
VXOR V30, V12, V30
|
||||
@@ -354,6 +402,11 @@ loop_vsx:
|
||||
VADDUWM V10, V18, V8
|
||||
VADDUWM V14, V19, V12
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(V12)
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
@@ -381,6 +434,11 @@ loop_vsx:
|
||||
VADDUWM V11, V18, V8
|
||||
VADDUWM V15, V19, V12
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(V12)
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
@@ -408,9 +466,9 @@ loop_vsx:
|
||||
|
||||
done_vsx:
|
||||
// Increment counter by number of 64 byte blocks
|
||||
MOVD (CNT), R14
|
||||
MOVWZ (CNT), R14
|
||||
ADD BLOCKS, R14
|
||||
MOVD R14, (CNT)
|
||||
MOVWZ R14, (CNT)
|
||||
RET
|
||||
|
||||
tail_vsx:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,12 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -38,6 +38,9 @@ type chacha20poly1305 struct {
|
||||
|
||||
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||
func New(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool
|
||||
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
|
||||
|
||||
var (
|
||||
useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
||||
useAVX2 = cpu.X86.HasSSSE3 && cpu.X86.HasAVX2 && cpu.X86.HasBMI2
|
||||
)
|
||||
|
||||
// setupState writes a ChaCha20 input matrix to state. See
|
||||
@@ -47,7 +47,7 @@ func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if !cpu.X86.HasSSSE3 {
|
||||
if !useAVX2 {
|
||||
return c.sealGeneric(dst, nonce, plaintext, additionalData)
|
||||
}
|
||||
|
||||
@@ -56,14 +56,17 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||
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)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if !cpu.X86.HasSSSE3 {
|
||||
if !useAVX2 {
|
||||
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]
|
||||
ret, out := sliceForAppend(dst, len(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) {
|
||||
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)
|
||||
ciphertext, tag := out[:len(plaintext)], out[len(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
|
||||
@@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
|
||||
|
||||
ret, out := sliceForAppend(dst, len(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) {
|
||||
for i := range out {
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestRandom(t *testing.T) {
|
||||
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.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.SetBytes(int64(len(buf)))
|
||||
|
||||
@@ -204,17 +204,17 @@ func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
|
||||
func BenchmarkChacha20Poly1305(b *testing.B) {
|
||||
for _, length := range []int{64, 1350, 8 * 1024} {
|
||||
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) {
|
||||
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
|
||||
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
|
||||
})
|
||||
|
||||
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) {
|
||||
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
|
||||
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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
|
||||
|
||||
func fips140Enforced() bool { return false }
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.26
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
import "crypto/fips140"
|
||||
|
||||
func fips140Enforced() bool { return fips140.Enforced() }
|
||||
@@ -22,6 +22,9 @@ type xchacha20poly1305 struct {
|
||||
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
|
||||
// nonces are randomly generated.
|
||||
func NewX(key []byte) (cipher.AEAD, error) {
|
||||
if fips140Enforced() {
|
||||
return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
|
||||
+1
-1
@@ -234,7 +234,7 @@ func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
|
||||
// Identifiers with the low five bits set indicate high-tag-number format
|
||||
// (two or more octets), which we don't support.
|
||||
if tag&0x1f == 0x1f {
|
||||
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
|
||||
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octets not supported: 0x%x", tag)
|
||||
return
|
||||
}
|
||||
b.AddUint8(uint8(tag))
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package curve25519 provides an implementation of the X25519 function, which
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||
// See RFC 7748.
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519
|
||||
// according to [RFC 7748].
|
||||
//
|
||||
// This package is a wrapper for the X25519 implementation
|
||||
// in the crypto/ecdh package.
|
||||
// The curve25519 package is a wrapper for the X25519 implementation in the
|
||||
// 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
|
||||
|
||||
import "crypto/ecdh"
|
||||
@@ -36,7 +39,7 @@ func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||
curve := ecdh.X25519()
|
||||
priv, err := curve.NewPrivateKey(scalar[:])
|
||||
if err != nil {
|
||||
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
|
||||
panic("curve25519: " + err.Error())
|
||||
}
|
||||
copy(dst[:], priv.PublicKey().Bytes())
|
||||
}
|
||||
|
||||
+7
-4
@@ -2,16 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// https://ed25519.cr.yp.to/.
|
||||
// Package ed25519 implements the Ed25519 signature algorithm.
|
||||
//
|
||||
// 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
|
||||
// operations with the same key more efficient. This package refers to the RFC
|
||||
// 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
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module golang.org/x/crypto
|
||||
|
||||
go 1.20
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.21.0 // tagx:ignore
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/term v0.25.0
|
||||
golang.org/x/net v0.55.0 // tagx:ignore
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/term v0.44.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.19.0 // indirect
|
||||
require golang.org/x/text v0.38.0 // indirect
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
|
||||
+13
-8
@@ -11,6 +11,7 @@
|
||||
package hkdf
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -24,15 +25,19 @@ import (
|
||||
// Expand invocations and different context values. Most common scenarios,
|
||||
// including the generation of multiple keys, should use New instead.
|
||||
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
||||
if salt == nil {
|
||||
salt = make([]byte, hash().Size())
|
||||
// Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
|
||||
// key (which in HKDF is the salt). The only possible error is FIPS 140
|
||||
// enforcement of the hash, which had to panic under this API anyway. We
|
||||
// don't use the stdlib Expand, because it switched to returning a []byte
|
||||
// instead of an io.Reader, and Expand uses the HMAC key as a key.
|
||||
out, err := hkdf.Extract(hash, secret, salt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
extractor := hmac.New(hash, salt)
|
||||
extractor.Write(secret)
|
||||
return extractor.Sum(nil)
|
||||
return out
|
||||
}
|
||||
|
||||
type hkdf struct {
|
||||
type hkdfReader struct {
|
||||
expander hash.Hash
|
||||
size int
|
||||
|
||||
@@ -43,7 +48,7 @@ type hkdf struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (f *hkdf) Read(p []byte) (int, error) {
|
||||
func (f *hkdfReader) Read(p []byte) (int, error) {
|
||||
// Check whether enough data can be generated
|
||||
need := len(p)
|
||||
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
||||
@@ -84,7 +89,7 @@ func (f *hkdf) Read(p []byte) (int, error) {
|
||||
// 3.3. Most common scenarios will want to use New instead.
|
||||
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
||||
expander := hmac.New(hash, pseudorandomKey)
|
||||
return &hkdf{expander, expander.Size(), info, 1, nil, nil}
|
||||
return &hkdfReader{expander, expander.Size(), info, 1, nil, nil}
|
||||
}
|
||||
|
||||
// New returns a Reader, from which keys can be read, using the given hash,
|
||||
|
||||
@@ -4,12 +4,12 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!amd64 && !ppc64le && !s390x) || !gc || purego
|
||||
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !s390x) || !gc || purego
|
||||
|
||||
package poly1305
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le)
|
||||
|
||||
package poly1305
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// 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 gc && !purego
|
||||
|
||||
// func update(state *macState, msg []byte)
|
||||
TEXT ·update(SB), $0-32
|
||||
MOVV state+0(FP), R4
|
||||
MOVV msg_base+8(FP), R5
|
||||
MOVV msg_len+16(FP), R6
|
||||
|
||||
MOVV $0x10, R7
|
||||
|
||||
MOVV (R4), R8 // h0
|
||||
MOVV 8(R4), R9 // h1
|
||||
MOVV 16(R4), R10 // h2
|
||||
MOVV 24(R4), R11 // r0
|
||||
MOVV 32(R4), R12 // r1
|
||||
|
||||
BLT R6, R7, bytes_between_0_and_15
|
||||
|
||||
loop:
|
||||
MOVV (R5), R14 // msg[0:8]
|
||||
MOVV 8(R5), R16 // msg[8:16]
|
||||
ADDV R14, R8, R8 // h0 (x1 + y1 = z1', if z1' < x1 then z1' overflow)
|
||||
ADDV R16, R9, R27
|
||||
SGTU R14, R8, R24 // h0.carry
|
||||
SGTU R9, R27, R28
|
||||
ADDV R27, R24, R9 // h1
|
||||
SGTU R27, R9, R24
|
||||
OR R24, R28, R24 // h1.carry
|
||||
ADDV $0x01, R24, R24
|
||||
ADDV R10, R24, R10 // h2
|
||||
|
||||
ADDV $16, R5, R5 // msg = msg[16:]
|
||||
|
||||
multiply:
|
||||
MULV R8, R11, R14 // h0r0.lo
|
||||
MULHVU R8, R11, R15 // h0r0.hi
|
||||
MULV R9, R11, R13 // h1r0.lo
|
||||
MULHVU R9, R11, R16 // h1r0.hi
|
||||
ADDV R13, R15, R15
|
||||
SGTU R13, R15, R24
|
||||
ADDV R24, R16, R16
|
||||
MULV R10, R11, R25
|
||||
ADDV R16, R25, R25
|
||||
MULV R8, R12, R13 // h0r1.lo
|
||||
MULHVU R8, R12, R16 // h0r1.hi
|
||||
ADDV R13, R15, R15
|
||||
SGTU R13, R15, R24
|
||||
ADDV R24, R16, R16
|
||||
MOVV R16, R8
|
||||
MULV R10, R12, R26 // h2r1
|
||||
MULV R9, R12, R13 // h1r1.lo
|
||||
MULHVU R9, R12, R16 // h1r1.hi
|
||||
ADDV R13, R25, R25
|
||||
ADDV R16, R26, R27
|
||||
SGTU R13, R25, R24
|
||||
ADDV R27, R24, R26
|
||||
ADDV R8, R25, R25
|
||||
SGTU R8, R25, R24
|
||||
ADDV R24, R26, R26
|
||||
AND $3, R25, R10
|
||||
AND $-4, R25, R17
|
||||
ADDV R17, R14, R8
|
||||
ADDV R26, R15, R27
|
||||
SGTU R17, R8, R24
|
||||
SGTU R26, R27, R28
|
||||
ADDV R27, R24, R9
|
||||
SGTU R27, R9, R24
|
||||
OR R24, R28, R24
|
||||
ADDV R24, R10, R10
|
||||
SLLV $62, R26, R27
|
||||
SRLV $2, R25, R28
|
||||
SRLV $2, R26, R26
|
||||
OR R27, R28, R25
|
||||
ADDV R25, R8, R8
|
||||
ADDV R26, R9, R27
|
||||
SGTU R25, R8, R24
|
||||
SGTU R26, R27, R28
|
||||
ADDV R27, R24, R9
|
||||
SGTU R27, R9, R24
|
||||
OR R24, R28, R24
|
||||
ADDV R24, R10, R10
|
||||
|
||||
SUBV $16, R6, R6
|
||||
BGE R6, R7, loop
|
||||
|
||||
bytes_between_0_and_15:
|
||||
BEQ R6, R0, done
|
||||
MOVV $1, R14
|
||||
XOR R15, R15
|
||||
ADDV R6, R5, R5
|
||||
|
||||
flush_buffer:
|
||||
MOVBU -1(R5), R25
|
||||
SRLV $56, R14, R24
|
||||
SLLV $8, R15, R28
|
||||
SLLV $8, R14, R14
|
||||
OR R24, R28, R15
|
||||
XOR R25, R14, R14
|
||||
SUBV $1, R6, R6
|
||||
SUBV $1, R5, R5
|
||||
BNE R6, R0, flush_buffer
|
||||
|
||||
ADDV R14, R8, R8
|
||||
SGTU R14, R8, R24
|
||||
ADDV R15, R9, R27
|
||||
SGTU R15, R27, R28
|
||||
ADDV R27, R24, R9
|
||||
SGTU R27, R9, R24
|
||||
OR R24, R28, R24
|
||||
ADDV R10, R24, R10
|
||||
|
||||
MOVV $16, R6
|
||||
JMP multiply
|
||||
|
||||
done:
|
||||
MOVV R8, (R4)
|
||||
MOVV R9, 8(R4)
|
||||
MOVV R10, 16(R4)
|
||||
RET
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
|
||||
package poly1305
|
||||
|
||||
//go:noescape
|
||||
func update(state *macState, msg []byte)
|
||||
|
||||
// mac is a wrapper for macGeneric that redirects calls that would have gone to
|
||||
// updateGeneric to update.
|
||||
//
|
||||
// Its Write and Sum methods are otherwise identical to the macGeneric ones, but
|
||||
// using function pointers would carry a major performance cost.
|
||||
type mac struct{ macGeneric }
|
||||
|
||||
func (h *mac) Write(p []byte) (int, error) {
|
||||
nn := len(p)
|
||||
if h.offset > 0 {
|
||||
n := copy(h.buffer[h.offset:], p)
|
||||
if h.offset+n < TagSize {
|
||||
h.offset += n
|
||||
return nn, nil
|
||||
}
|
||||
p = p[n:]
|
||||
h.offset = 0
|
||||
update(&h.macState, h.buffer[:])
|
||||
}
|
||||
if n := len(p) - (len(p) % TagSize); n > 0 {
|
||||
update(&h.macState, p[:n])
|
||||
p = p[n:]
|
||||
}
|
||||
if len(p) > 0 {
|
||||
h.offset += copy(h.buffer[h.offset:], p)
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (h *mac) Sum(out *[16]byte) {
|
||||
state := h.macState
|
||||
if h.offset > 0 {
|
||||
update(&state, h.buffer[:h.offset])
|
||||
}
|
||||
finalize(out, &state.h, &state.s)
|
||||
}
|
||||
@@ -2,15 +2,25 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
//go:build gc && !purego && (ppc64 || ppc64le)
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// This was ported from the amd64 implementation.
|
||||
|
||||
#ifdef GOARCH_ppc64le
|
||||
#define LE_MOVD MOVD
|
||||
#define LE_MOVWZ MOVWZ
|
||||
#define LE_MOVHZ MOVHZ
|
||||
#else
|
||||
#define LE_MOVD MOVDBR
|
||||
#define LE_MOVWZ MOVWBR
|
||||
#define LE_MOVHZ MOVHBR
|
||||
#endif
|
||||
|
||||
#define POLY1305_ADD(msg, h0, h1, h2, t0, t1, t2) \
|
||||
MOVD (msg), t0; \
|
||||
MOVD 8(msg), t1; \
|
||||
LE_MOVD (msg)( R0), t0; \
|
||||
LE_MOVD (msg)(R24), t1; \
|
||||
MOVD $1, t2; \
|
||||
ADDC t0, h0, h0; \
|
||||
ADDE t1, h1, h1; \
|
||||
@@ -50,10 +60,6 @@
|
||||
ADDE t3, h1, h1; \
|
||||
ADDZE h2
|
||||
|
||||
DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
|
||||
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
|
||||
GLOBL ·poly1305Mask<>(SB), RODATA, $16
|
||||
|
||||
// func update(state *[7]uint64, msg []byte)
|
||||
TEXT ·update(SB), $0-32
|
||||
MOVD state+0(FP), R3
|
||||
@@ -66,6 +72,8 @@ TEXT ·update(SB), $0-32
|
||||
MOVD 24(R3), R11 // r0
|
||||
MOVD 32(R3), R12 // r1
|
||||
|
||||
MOVD $8, R24
|
||||
|
||||
CMP R5, $16
|
||||
BLT bytes_between_0_and_15
|
||||
|
||||
@@ -94,7 +102,7 @@ flush_buffer:
|
||||
|
||||
// Greater than 8 -- load the rightmost remaining bytes in msg
|
||||
// and put into R17 (h1)
|
||||
MOVD (R4)(R21), R17
|
||||
LE_MOVD (R4)(R21), R17
|
||||
MOVD $16, R22
|
||||
|
||||
// Find the offset to those bytes
|
||||
@@ -118,7 +126,7 @@ just1:
|
||||
BLT less8
|
||||
|
||||
// Exactly 8
|
||||
MOVD (R4), R16
|
||||
LE_MOVD (R4), R16
|
||||
|
||||
CMP R17, $0
|
||||
|
||||
@@ -133,7 +141,7 @@ less8:
|
||||
MOVD $0, R22 // shift count
|
||||
CMP R5, $4
|
||||
BLT less4
|
||||
MOVWZ (R4), R16
|
||||
LE_MOVWZ (R4), R16
|
||||
ADD $4, R4
|
||||
ADD $-4, R5
|
||||
MOVD $32, R22
|
||||
@@ -141,7 +149,7 @@ less8:
|
||||
less4:
|
||||
CMP R5, $2
|
||||
BLT less2
|
||||
MOVHZ (R4), R21
|
||||
LE_MOVHZ (R4), R21
|
||||
SLD R22, R21, R21
|
||||
OR R16, R21, R16
|
||||
ADD $16, R22
|
||||
@@ -141,7 +141,7 @@ func TestRsaPss(t *testing.T) {
|
||||
}
|
||||
// Run all the tests twice: the first 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 _, sig := range tg.Tests {
|
||||
h.Reset()
|
||||
|
||||
+10
-19
@@ -2,25 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package auth authenticates a message using a secret key.
|
||||
|
||||
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
|
||||
that an attacker cannot find authenticators for any messages not authenticated
|
||||
by the sender, even if the attacker has adaptively influenced the messages
|
||||
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
|
||||
message authentication code," Journal of Computer and System Sciences 61 (2000),
|
||||
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 authenticates a message using a secret key.
|
||||
//
|
||||
// This package is interoperable with [NaCl].
|
||||
//
|
||||
// The auth package is essentially a wrapper for HMAC-SHA-512 (implemented by
|
||||
// crypto/hmac and crypto/sha512), truncated to 32 bytes. It is [frozen] and is
|
||||
// not accepting new features.
|
||||
//
|
||||
// [NaCl]: https://nacl.cr.yp.to/auth.html
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package auth
|
||||
|
||||
import (
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestSealOpenAnonymous(t *testing.T) {
|
||||
t.Fatal("expected out to be unchanged")
|
||||
}
|
||||
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)
|
||||
if !ok {
|
||||
|
||||
+8
-13
@@ -4,20 +4,15 @@
|
||||
|
||||
// Package sign signs small messages using public-key cryptography.
|
||||
//
|
||||
// Sign uses Ed25519 to sign messages. The length of messages is not hidden.
|
||||
// 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 interoperable with [libsodium], as well as [TweetNaCl].
|
||||
//
|
||||
// This package is not interoperable with the current release of NaCl
|
||||
// (https://nacl.cr.yp.to/sign.html), which does not support Ed25519 yet. However,
|
||||
// it is compatible with the NaCl fork libsodium (https://www.libsodium.org), as well
|
||||
// as TweetNaCl (https://tweetnacl.cr.yp.to/).
|
||||
// The sign package is essentially a wrapper for the Ed25519 signature
|
||||
// algorithm (implemented by crypto/ed25519). It is [frozen] and is not accepting
|
||||
// new features.
|
||||
//
|
||||
// [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
|
||||
|
||||
import (
|
||||
|
||||
@@ -69,6 +69,6 @@ func TestGenerateSignOpen(t *testing.T) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,24 +123,12 @@ func TestSigning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// We use this to make test keys, so that they aren't all the same.
|
||||
type quickRand byte
|
||||
|
||||
func (qr *quickRand) Read(p []byte) (int, error) {
|
||||
for i := range p {
|
||||
p[i] = byte(*qr)
|
||||
}
|
||||
*qr++
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestMultiSign(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping long test in -short mode")
|
||||
}
|
||||
|
||||
zero := quickRand(0)
|
||||
config := packet.Config{Rand: &zero}
|
||||
var config packet.Config
|
||||
|
||||
for nKeys := 0; nKeys < 4; nKeys++ {
|
||||
nextTest:
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestOpaqueParseReason(t *testing.T) {
|
||||
const expectedBad = 3
|
||||
// Test post-conditions, make sure we actually parsed packets as expected.
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
// The version of OTR implemented by this package has been deprecated
|
||||
// (https://bugs.otr.im/lib/libotr/issues/140). An implementation of OTRv3 is
|
||||
// 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
|
||||
|
||||
import (
|
||||
|
||||
+3
-3
@@ -75,8 +75,8 @@ const libOTRPrivateKey = `(privkeys
|
||||
(account
|
||||
(name "foo@example.com")
|
||||
(protocol prpl-jabber)
|
||||
(private-key
|
||||
(dsa
|
||||
(private-key
|
||||
(dsa
|
||||
(p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
|
||||
(q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
|
||||
(g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
|
||||
@@ -402,7 +402,7 @@ func TestAgainstLibOTR(t *testing.T) {
|
||||
// This test requires otr.c.test to be built as /tmp/a.out.
|
||||
// If enabled, this tests runs forever performing OTR handshakes in a
|
||||
// loop.
|
||||
return
|
||||
t.Skip("This test requires otr.c.test to be built as /tmp/a.out")
|
||||
|
||||
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
|
||||
var alice Conversation
|
||||
|
||||
+13
-60
@@ -2,24 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
// Package pbkdf2 implements the key derivation function PBKDF2 as defined in
|
||||
// RFC 8018 (PKCS #5 v2.1).
|
||||
//
|
||||
// This package is a wrapper for the PBKDF2 implementation in the
|
||||
// [crypto/pbkdf2] package. It is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package pbkdf2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/pbkdf2"
|
||||
"hash"
|
||||
)
|
||||
|
||||
@@ -27,51 +20,11 @@ import (
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
out, err := pbkdf2.Key(h, string(password), salt, iter, keyLen)
|
||||
if err != nil {
|
||||
// FIPS 140 enforcement, or an invalid key length.
|
||||
panic(err)
|
||||
}
|
||||
return dk[:keyLen]
|
||||
return out
|
||||
}
|
||||
|
||||
+5
-1
@@ -26,7 +26,7 @@ type pbeCipher interface {
|
||||
create(key []byte) (cipher.Block, error)
|
||||
// deriveKey returns a key derived from the given password and salt.
|
||||
deriveKey(salt, password []byte, iterations int) []byte
|
||||
// deriveKey returns an IV derived from the given password and salt.
|
||||
// deriveIV returns an IV derived from the given password and salt.
|
||||
deriveIV(salt, password []byte, iterations int) []byte
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if params.Iterations < 0 || params.Iterations > maxIterations {
|
||||
return nil, 0, NotImplementedError("iteration count is invalid or too high")
|
||||
}
|
||||
|
||||
key := cipherType.deriveKey(params.Salt, password, params.Iterations)
|
||||
iv := cipherType.deriveIV(params.Salt, password, params.Iterations)
|
||||
|
||||
|
||||
@@ -13,6 +13,46 @@ import (
|
||||
|
||||
var sha1WithTripleDES = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
|
||||
|
||||
func TestPbDecrypterForIterationLimit(t *testing.T) {
|
||||
pass, _ := bmpString("Sesame open")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
iterations int
|
||||
wantErr bool
|
||||
}{
|
||||
{"at limit", maxIterations, false},
|
||||
{"over limit", maxIterations + 1, true},
|
||||
{"negative", -1, true},
|
||||
{"max int", 1<<31 - 1, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
params, _ := asn1.Marshal(pbeParams{
|
||||
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
Iterations: tt.iterations,
|
||||
})
|
||||
alg := pkix.AlgorithmIdentifier{
|
||||
Algorithm: sha1WithTripleDES,
|
||||
Parameters: asn1.RawValue{
|
||||
FullBytes: params,
|
||||
},
|
||||
}
|
||||
_, _, err := pbDecrypterFor(alg, pass)
|
||||
if tt.wantErr {
|
||||
if _, ok := err.(NotImplementedError); !ok {
|
||||
t.Errorf("iterations=%d: got %v, want NotImplementedError", tt.iterations, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("iterations=%d: unexpected error: %v", tt.iterations, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPbDecrypterFor(t *testing.T) {
|
||||
params, _ := asn1.Marshal(pbeParams{
|
||||
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
|
||||
@@ -27,11 +27,19 @@ var (
|
||||
oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
|
||||
)
|
||||
|
||||
// maxIterations is a safety limit to prevent CPU exhaustion from
|
||||
// crafted PKCS#12 files with unreasonable iteration counts.
|
||||
const maxIterations = 1 << 20 // ~1 million
|
||||
|
||||
func verifyMac(macData *macData, message, password []byte) error {
|
||||
if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) {
|
||||
return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
|
||||
}
|
||||
|
||||
if macData.Iterations < 0 || macData.Iterations > maxIterations {
|
||||
return NotImplementedError("iteration count is invalid or too high")
|
||||
}
|
||||
|
||||
key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20)
|
||||
|
||||
mac := hmac.New(sha1.New, key)
|
||||
|
||||
@@ -5,10 +5,52 @@
|
||||
package pkcs12
|
||||
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVerifyMacIterationLimit(t *testing.T) {
|
||||
password, _ := bmpString("Sesame open")
|
||||
message := []byte{11, 12, 13, 14, 15}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
iterations int
|
||||
wantErr bool
|
||||
}{
|
||||
{"at limit", maxIterations, false},
|
||||
{"over limit", maxIterations + 1, true},
|
||||
{"negative", -1, true},
|
||||
{"max int", 1<<31 - 1, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
td := macData{
|
||||
Mac: digestInfo{
|
||||
Algorithm: pkix.AlgorithmIdentifier{
|
||||
Algorithm: oidSHA1,
|
||||
},
|
||||
Digest: nil, // will fail MAC check, but iteration check comes first
|
||||
},
|
||||
MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
Iterations: tt.iterations,
|
||||
}
|
||||
err := verifyMac(&td, message, password)
|
||||
if tt.wantErr {
|
||||
if _, ok := err.(NotImplementedError); !ok {
|
||||
t.Errorf("iterations=%d: got %v, want NotImplementedError", tt.iterations, err)
|
||||
}
|
||||
} else {
|
||||
if _, ok := err.(NotImplementedError); ok {
|
||||
t.Errorf("iterations=%d: got unexpected NotImplementedError", tt.iterations)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyMac(t *testing.T) {
|
||||
td := macData{
|
||||
Mac: digestInfo{
|
||||
|
||||
+9
-5
@@ -4,12 +4,16 @@
|
||||
|
||||
// Package pkcs12 implements some of PKCS#12.
|
||||
//
|
||||
// This implementation is distilled from https://tools.ietf.org/html/rfc7292
|
||||
// and referenced documents. It is intended for decoding P12/PFX-stored
|
||||
// certificates and keys for use with the crypto/tls package.
|
||||
// This implementation is distilled from [RFC 7292] and referenced documents.
|
||||
// It is intended for decoding P12/PFX-stored certificates and keys for use
|
||||
// with the crypto/tls package.
|
||||
//
|
||||
// This package is frozen. If it's missing functionality you need, consider
|
||||
// an alternative like software.sslmate.com/src/go-pkcs12.
|
||||
// The pkcs12 package is [frozen] and is not accepting new features.
|
||||
// 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
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// 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
|
||||
|
||||
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 {
|
||||
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
|
||||
}
|
||||
if r <= 0 || p <= 0 {
|
||||
return nil, errors.New("scrypt: parameters must be > 0")
|
||||
}
|
||||
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
|
||||
return nil, errors.New("scrypt: parameters are too large")
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ var bad = []testVector{
|
||||
{"p", "s", 1, 1, 1, nil}, // N == 1
|
||||
{"p", "s", 7, 8, 1, nil}, // N is not power of 2
|
||||
{"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large
|
||||
{"p", "s", 2, 0, 1, nil}, // r too small
|
||||
{"p", "s", 2, 1, 0, nil}, // p too small
|
||||
{"p", "s", 2, -1, 1, nil}, // r is negative
|
||||
{"p", "s", 2, 1, -1, nil}, // p is negative
|
||||
}
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
module sha3/_asm
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
-62
@@ -1,62 +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.
|
||||
//
|
||||
// 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
-64
@@ -2,108 +2,94 @@
|
||||
// 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 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
|
||||
|
||||
// This file provides functions for creating instances of the SHA-3
|
||||
// and SHAKE hash functions, as well as utility functions for hashing
|
||||
// bytes.
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha3"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// New224 creates a new SHA3-224 hash.
|
||||
// Its generic security strength is 224 bits against preimage 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 {
|
||||
return new224()
|
||||
return sha3.New224()
|
||||
}
|
||||
|
||||
// New256 creates a new SHA3-256 hash.
|
||||
// Its generic security strength is 256 bits against preimage 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 {
|
||||
return new256()
|
||||
return sha3.New256()
|
||||
}
|
||||
|
||||
// New384 creates a new SHA3-384 hash.
|
||||
// Its generic security strength is 384 bits against preimage 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 {
|
||||
return new384()
|
||||
return sha3.New384()
|
||||
}
|
||||
|
||||
// New512 creates a new SHA3-512 hash.
|
||||
// Its generic security strength is 512 bits against preimage 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
func new224Generic() *state {
|
||||
return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
|
||||
}
|
||||
|
||||
func new256Generic() *state {
|
||||
return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
|
||||
}
|
||||
|
||||
func new384Generic() *state {
|
||||
return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
|
||||
}
|
||||
|
||||
func new512Generic() *state {
|
||||
return &state{rate: 72, outputLen: 64, dsbyte: 0x06}
|
||||
}
|
||||
|
||||
// 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: 136, outputLen: 32, dsbyte: 0x01} }
|
||||
|
||||
// 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: 72, outputLen: 64, dsbyte: 0x01} }
|
||||
|
||||
// Sum224 returns the SHA3-224 digest of the data.
|
||||
func Sum224(data []byte) (digest [28]byte) {
|
||||
h := New224()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum224] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum224(data []byte) [28]byte {
|
||||
return sha3.Sum224(data)
|
||||
}
|
||||
|
||||
// Sum256 returns the SHA3-256 digest of the data.
|
||||
func Sum256(data []byte) (digest [32]byte) {
|
||||
h := New256()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum256] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum256(data []byte) [32]byte {
|
||||
return sha3.Sum256(data)
|
||||
}
|
||||
|
||||
// Sum384 returns the SHA3-384 digest of the data.
|
||||
func Sum384(data []byte) (digest [48]byte) {
|
||||
h := New384()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum384] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
func Sum384(data []byte) [48]byte {
|
||||
return sha3.Sum384(data)
|
||||
}
|
||||
|
||||
// Sum512 returns the SHA3-512 digest of the data.
|
||||
func Sum512(data []byte) (digest [64]byte) {
|
||||
h := New512()
|
||||
h.Write(data)
|
||||
h.Sum(digest[:0])
|
||||
return
|
||||
//
|
||||
// It is a wrapper for the [sha3.Sum512] function in the standard library.
|
||||
//
|
||||
//go:fix inline
|
||||
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,6 +4,46 @@
|
||||
|
||||
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 (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"unsafe"
|
||||
|
||||
"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.
|
||||
type spongeDirection int
|
||||
|
||||
@@ -14,16 +54,13 @@ const (
|
||||
spongeSqueezing
|
||||
)
|
||||
|
||||
const (
|
||||
// maxRate is the maximum size of the internal buffer. SHAKE-256
|
||||
// currently needs the largest buffer.
|
||||
maxRate = 168
|
||||
)
|
||||
|
||||
type state struct {
|
||||
// Generic sponge components.
|
||||
a [25]uint64 // main state of the hash
|
||||
rate int // the number of bytes of state to use
|
||||
a [1600 / 8]byte // main state of the hash
|
||||
|
||||
// a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR
|
||||
// into before running the permutation. If squeezing, it's the remaining
|
||||
// output to produce before running the permutation.
|
||||
n, rate int
|
||||
|
||||
// dsbyte contains the "domain separation" bits and the first bit of
|
||||
// the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
|
||||
@@ -39,10 +76,6 @@ type state struct {
|
||||
// Extendable-Output Functions (May 2014)"
|
||||
dsbyte byte
|
||||
|
||||
i, n int // storage[i:n] is the buffer, i is only used while squeezing
|
||||
storage [maxRate]byte
|
||||
|
||||
// Specific to SHA-3 and SHAKE.
|
||||
outputLen int // the default output size in bytes
|
||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||
}
|
||||
@@ -61,7 +94,7 @@ func (d *state) Reset() {
|
||||
d.a[i] = 0
|
||||
}
|
||||
d.state = spongeAbsorbing
|
||||
d.i, d.n = 0, 0
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
func (d *state) clone() *state {
|
||||
@@ -69,22 +102,25 @@ func (d *state) clone() *state {
|
||||
return &ret
|
||||
}
|
||||
|
||||
// permute applies the KeccakF-1600 permutation. It handles
|
||||
// any input-output buffering.
|
||||
// permute applies the KeccakF-1600 permutation.
|
||||
func (d *state) permute() {
|
||||
switch d.state {
|
||||
case spongeAbsorbing:
|
||||
// If we're absorbing, we need to xor the input into the state
|
||||
// before applying the permutation.
|
||||
xorIn(d, d.storage[:d.rate])
|
||||
d.n = 0
|
||||
keccakF1600(&d.a)
|
||||
case spongeSqueezing:
|
||||
// If we're squeezing, we need to apply the permutation before
|
||||
// copying more output.
|
||||
keccakF1600(&d.a)
|
||||
d.i = 0
|
||||
copyOut(d, d.storage[:d.rate])
|
||||
var a *[25]uint64
|
||||
if cpu.IsBigEndian {
|
||||
a = new([25]uint64)
|
||||
for i := range a {
|
||||
a[i] = binary.LittleEndian.Uint64(d.a[i*8:])
|
||||
}
|
||||
} else {
|
||||
a = (*[25]uint64)(unsafe.Pointer(&d.a))
|
||||
}
|
||||
|
||||
keccakF1600(a)
|
||||
d.n = 0
|
||||
|
||||
if cpu.IsBigEndian {
|
||||
for i := range a {
|
||||
binary.LittleEndian.PutUint64(d.a[i*8:], a[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,53 +128,36 @@ func (d *state) permute() {
|
||||
// the multi-bitrate 10..1 padding rule, and permutes the state.
|
||||
func (d *state) padAndPermute() {
|
||||
// Pad with this instance's domain-separator bits. We know that there's
|
||||
// at least one byte of space in d.buf because, if it were full,
|
||||
// at least one byte of space in the sponge because, if it were full,
|
||||
// permute would have been called to empty it. dsbyte also contains the
|
||||
// first one bit for the padding. See the comment in the state struct.
|
||||
d.storage[d.n] = d.dsbyte
|
||||
d.n++
|
||||
for d.n < d.rate {
|
||||
d.storage[d.n] = 0
|
||||
d.n++
|
||||
}
|
||||
d.a[d.n] ^= d.dsbyte
|
||||
// This adds the final one bit for the padding. Because of the way that
|
||||
// bits are numbered from the LSB upwards, the final bit is the MSB of
|
||||
// the last byte.
|
||||
d.storage[d.rate-1] ^= 0x80
|
||||
d.a[d.rate-1] ^= 0x80
|
||||
// Apply the permutation
|
||||
d.permute()
|
||||
d.state = spongeSqueezing
|
||||
d.n = d.rate
|
||||
copyOut(d, d.storage[:d.rate])
|
||||
}
|
||||
|
||||
// Write absorbs more data into the hash's state. It panics if any
|
||||
// output has already been read.
|
||||
func (d *state) Write(p []byte) (written int, err error) {
|
||||
func (d *state) Write(p []byte) (n int, err error) {
|
||||
if d.state != spongeAbsorbing {
|
||||
panic("sha3: Write after Read")
|
||||
}
|
||||
written = len(p)
|
||||
|
||||
n = len(p)
|
||||
|
||||
for len(p) > 0 {
|
||||
if d.n == 0 && len(p) >= d.rate {
|
||||
// The fast path; absorb a full "rate" bytes of input and apply the permutation.
|
||||
xorIn(d, p[:d.rate])
|
||||
p = p[d.rate:]
|
||||
keccakF1600(&d.a)
|
||||
} else {
|
||||
// The slow path; buffer the input until we can fill the sponge, and then xor it in.
|
||||
todo := d.rate - d.n
|
||||
if todo > len(p) {
|
||||
todo = len(p)
|
||||
}
|
||||
d.n += copy(d.storage[d.n:], p[:todo])
|
||||
p = p[todo:]
|
||||
x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p)
|
||||
d.n += x
|
||||
p = p[x:]
|
||||
|
||||
// If the sponge is full, apply the permutation.
|
||||
if d.n == d.rate {
|
||||
d.permute()
|
||||
}
|
||||
// If the sponge is full, apply the permutation.
|
||||
if d.n == d.rate {
|
||||
d.permute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,14 +175,14 @@ func (d *state) Read(out []byte) (n int, err error) {
|
||||
|
||||
// Now, do the squeezing.
|
||||
for len(out) > 0 {
|
||||
n := copy(out, d.storage[d.i:d.n])
|
||||
d.i += n
|
||||
out = out[n:]
|
||||
|
||||
// Apply the permutation if we've squeezed the sponge dry.
|
||||
if d.i == d.rate {
|
||||
if d.n == d.rate {
|
||||
d.permute()
|
||||
}
|
||||
|
||||
x := copy(out, d.a[d.n:d.rate])
|
||||
d.n += x
|
||||
out = out[x:]
|
||||
}
|
||||
|
||||
return
|
||||
@@ -183,3 +202,62 @@ func (d *state) Sum(in []byte) []byte {
|
||||
dup.Read(hash)
|
||||
return append(in, hash...)
|
||||
}
|
||||
|
||||
const (
|
||||
magicKeccak = "sha\x0b"
|
||||
// magic || rate || main state || n || sponge direction
|
||||
marshaledSize = len(magicKeccak) + 1 + 200 + 1 + 1
|
||||
)
|
||||
|
||||
func (d *state) MarshalBinary() ([]byte, error) {
|
||||
return d.AppendBinary(make([]byte, 0, marshaledSize))
|
||||
}
|
||||
|
||||
func (d *state) AppendBinary(b []byte) ([]byte, error) {
|
||||
switch d.dsbyte {
|
||||
case dsbyteKeccak:
|
||||
b = append(b, magicKeccak...)
|
||||
default:
|
||||
panic("unknown dsbyte")
|
||||
}
|
||||
// rate is at most 168, and n is at most rate.
|
||||
b = append(b, byte(d.rate))
|
||||
b = append(b, d.a[:]...)
|
||||
b = append(b, byte(d.n), byte(d.state))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (d *state) UnmarshalBinary(b []byte) error {
|
||||
if len(b) != marshaledSize {
|
||||
return errors.New("sha3: invalid hash state")
|
||||
}
|
||||
|
||||
magic := string(b[:len(magicKeccak)])
|
||||
b = b[len(magicKeccak):]
|
||||
switch {
|
||||
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
|
||||
default:
|
||||
return errors.New("sha3: invalid hash state identifier")
|
||||
}
|
||||
|
||||
rate := int(b[0])
|
||||
b = b[1:]
|
||||
if rate != d.rate {
|
||||
return errors.New("sha3: invalid hash state function")
|
||||
}
|
||||
|
||||
copy(d.a[:], b)
|
||||
b = b[len(d.a):]
|
||||
|
||||
n, state := int(b[0]), spongeDirection(b[1])
|
||||
if n > d.rate {
|
||||
return errors.New("sha3: invalid hash state")
|
||||
}
|
||||
d.n = n
|
||||
if state != spongeAbsorbing && state != spongeSqueezing {
|
||||
return errors.New("sha3: invalid hash state")
|
||||
}
|
||||
d.state = state
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
// 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 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"
|
||||
|
||||
// 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
|
||||
+133
-106
@@ -13,10 +13,11 @@ package sha3
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"encoding"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -375,123 +376,149 @@ func TestClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
func TestCSHAKEAccumulated(t *testing.T) {
|
||||
// Generated with pycryptodome@3.20.0
|
||||
//
|
||||
// from Crypto.Hash import cSHAKE128
|
||||
// rng = cSHAKE128.new()
|
||||
// acc = cSHAKE128.new()
|
||||
// for n in range(200):
|
||||
// N = rng.read(n)
|
||||
// for s in range(200):
|
||||
// S = rng.read(s)
|
||||
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
|
||||
// c.update(rng.read(100))
|
||||
// acc.update(c.read(200))
|
||||
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
|
||||
// c.update(rng.read(168))
|
||||
// acc.update(c.read(200))
|
||||
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
|
||||
// c.update(rng.read(200))
|
||||
// acc.update(c.read(200))
|
||||
// print(acc.read(32).hex())
|
||||
//
|
||||
// and with @noble/hashes@v1.5.0
|
||||
//
|
||||
// import { bytesToHex } from "@noble/hashes/utils";
|
||||
// import { cshake128 } from "@noble/hashes/sha3-addons";
|
||||
// const rng = cshake128.create();
|
||||
// const acc = cshake128.create();
|
||||
// for (let n = 0; n < 200; n++) {
|
||||
// const N = rng.xof(n);
|
||||
// for (let s = 0; s < 200; s++) {
|
||||
// const S = rng.xof(s);
|
||||
// let c = cshake128.create({ NISTfn: N, personalization: S });
|
||||
// c.update(rng.xof(100));
|
||||
// acc.update(c.xof(200));
|
||||
// c = cshake128.create({ NISTfn: N, personalization: S });
|
||||
// c.update(rng.xof(168));
|
||||
// acc.update(c.xof(200));
|
||||
// c = cshake128.create({ NISTfn: N, personalization: S });
|
||||
// c.update(rng.xof(200));
|
||||
// acc.update(c.xof(200));
|
||||
// }
|
||||
// }
|
||||
// console.log(bytesToHex(acc.xof(32)));
|
||||
//
|
||||
t.Run("cSHAKE128", func(t *testing.T) {
|
||||
testCSHAKEAccumulated(t, NewCShake128, rateK256,
|
||||
"bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252")
|
||||
})
|
||||
t.Run("cSHAKE256", func(t *testing.T) {
|
||||
testCSHAKEAccumulated(t, NewCShake256, rateK512,
|
||||
"0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef")
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
func testCSHAKEAccumulated(t *testing.T, newCShake func(N, S []byte) ShakeHash, rate int64, exp string) {
|
||||
rnd := newCShake(nil, nil)
|
||||
acc := newCShake(nil, nil)
|
||||
for n := 0; n < 200; n++ {
|
||||
N := make([]byte, n)
|
||||
rnd.Read(N)
|
||||
for s := 0; s < 200; s++ {
|
||||
S := make([]byte, s)
|
||||
rnd.Read(S)
|
||||
|
||||
var state []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < num; j++ {
|
||||
h.Write(data)
|
||||
c := newCShake(N, S)
|
||||
io.CopyN(c, rnd, 100 /* < rate */)
|
||||
io.CopyN(acc, c, 200)
|
||||
|
||||
c.Reset()
|
||||
io.CopyN(c, rnd, rate)
|
||||
io.CopyN(acc, c, 200)
|
||||
|
||||
c.Reset()
|
||||
io.CopyN(c, rnd, 200 /* > rate */)
|
||||
io.CopyN(acc, c, 200)
|
||||
}
|
||||
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)
|
||||
if got := hex.EncodeToString(acc.Sum(nil)[:32]); got != exp {
|
||||
t.Errorf("got %s, want %s", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
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 TestCSHAKELargeS(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
|
||||
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) }
|
||||
// See https://go.dev/issue/66232.
|
||||
const s = (1<<32)/8 + 1000 // s * 8 > 2^32
|
||||
S := make([]byte, s)
|
||||
rnd := NewShake128()
|
||||
rnd.Read(S)
|
||||
c := NewCShake128(nil, S)
|
||||
io.CopyN(c, rnd, 1000)
|
||||
|
||||
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
|
||||
// Generated with pycryptodome@3.20.0
|
||||
//
|
||||
// from Crypto.Hash import cSHAKE128
|
||||
// rng = cSHAKE128.new()
|
||||
// S = rng.read(536871912)
|
||||
// c = cSHAKE128.new(custom=S)
|
||||
// c.update(rng.read(1000))
|
||||
// print(c.read(32).hex())
|
||||
//
|
||||
exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0"
|
||||
if got := hex.EncodeToString(c.Sum(nil)); got != exp {
|
||||
t.Errorf("got %s, want %s", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
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 TestMarshalUnmarshal(t *testing.T) {
|
||||
t.Run("SHA3-224", func(t *testing.T) { testMarshalUnmarshal(t, New224()) })
|
||||
t.Run("SHA3-256", func(t *testing.T) { testMarshalUnmarshal(t, New256()) })
|
||||
t.Run("SHA3-384", func(t *testing.T) { testMarshalUnmarshal(t, New384()) })
|
||||
t.Run("SHA3-512", func(t *testing.T) { testMarshalUnmarshal(t, New512()) })
|
||||
t.Run("SHAKE128", func(t *testing.T) { testMarshalUnmarshal(t, NewShake128()) })
|
||||
t.Run("SHAKE256", func(t *testing.T) { testMarshalUnmarshal(t, NewShake256()) })
|
||||
t.Run("cSHAKE128", func(t *testing.T) { testMarshalUnmarshal(t, NewCShake128([]byte("N"), []byte("S"))) })
|
||||
t.Run("cSHAKE256", func(t *testing.T) { testMarshalUnmarshal(t, NewCShake256([]byte("N"), []byte("S"))) })
|
||||
t.Run("Keccak-256", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak256()) })
|
||||
t.Run("Keccak-512", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak512()) })
|
||||
}
|
||||
|
||||
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
|
||||
// TODO(filippo): move this to crypto/internal/cryptotest.
|
||||
func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
|
||||
buf := make([]byte, 200)
|
||||
rand.Read(buf)
|
||||
n := rand.Intn(200)
|
||||
h.Write(buf)
|
||||
want := h.Sum(nil)
|
||||
h.Reset()
|
||||
h.Write(buf[:n])
|
||||
b, err := h.(encoding.BinaryMarshaler).MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("MarshalBinary: %v", err)
|
||||
}
|
||||
h.Write(bytes.Repeat([]byte{0}, 200))
|
||||
if err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b); err != nil {
|
||||
t.Errorf("UnmarshalBinary: %v", err)
|
||||
}
|
||||
h.Write(buf[n:])
|
||||
got := h.Sum(nil)
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("got %x, want %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
+49
-104
@@ -4,19 +4,8 @@
|
||||
|
||||
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 (
|
||||
"encoding/binary"
|
||||
"crypto/sha3"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
@@ -29,7 +18,7 @@ type ShakeHash interface {
|
||||
hash.Hash
|
||||
|
||||
// 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
|
||||
// will panic.
|
||||
io.Reader
|
||||
@@ -38,99 +27,18 @@ type ShakeHash interface {
|
||||
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
|
||||
}
|
||||
|
||||
// Consts for configuring initial SHA-3 state
|
||||
const (
|
||||
dsbyteShake = 0x1f
|
||||
dsbyteCShake = 0x04
|
||||
rate128 = 168
|
||||
rate256 = 136
|
||||
)
|
||||
|
||||
func bytepad(input []byte, w int) []byte {
|
||||
// leftEncode always returns max 9 bytes
|
||||
buf := make([]byte, 0, 9+len(input)+w)
|
||||
buf = append(buf, leftEncode(uint64(w))...)
|
||||
buf = append(buf, input...)
|
||||
padlen := w - (len(buf) % w)
|
||||
return append(buf, make([]byte, padlen)...)
|
||||
}
|
||||
|
||||
func leftEncode(value uint64) []byte {
|
||||
var b [9]byte
|
||||
binary.BigEndian.PutUint64(b[1:], value)
|
||||
// Trim all but last leading zero bytes
|
||||
i := byte(1)
|
||||
for i < 8 && b[i] == 0 {
|
||||
i++
|
||||
}
|
||||
// Prepend number of encoded bytes
|
||||
b[i-1] = 9 - i
|
||||
return b[i-1:]
|
||||
}
|
||||
|
||||
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
|
||||
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
|
||||
|
||||
// leftEncode returns max 9 bytes
|
||||
c.initBlock = make([]byte, 0, 9*2+len(N)+len(S))
|
||||
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()
|
||||
}
|
||||
|
||||
// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
|
||||
// Its generic security strength is 128 bits against all attacks if at
|
||||
// least 32 bytes of its output are used.
|
||||
func NewShake128() ShakeHash {
|
||||
return newShake128()
|
||||
return &shakeWrapper{sha3.NewSHAKE128(), 32, false, sha3.NewSHAKE128}
|
||||
}
|
||||
|
||||
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
|
||||
// Its generic security strength is 256 bits against all attacks if
|
||||
// at least 64 bytes of its output are used.
|
||||
func NewShake256() ShakeHash {
|
||||
return newShake256()
|
||||
}
|
||||
|
||||
func newShake128Generic() *state {
|
||||
return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
|
||||
}
|
||||
|
||||
func newShake256Generic() *state {
|
||||
return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
|
||||
return &shakeWrapper{sha3.NewSHAKE256(), 64, false, sha3.NewSHAKE256}
|
||||
}
|
||||
|
||||
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
|
||||
@@ -140,10 +48,9 @@ func newShake256Generic() *state {
|
||||
// computations on same input with different S yield unrelated outputs.
|
||||
// When N and S are both empty, this is equivalent to NewShake128.
|
||||
func NewCShake128(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake128()
|
||||
}
|
||||
return newCShake(N, S, rate128, 32, dsbyteCShake)
|
||||
return &shakeWrapper{sha3.NewCSHAKE128(N, S), 32, false, func() *sha3.SHAKE {
|
||||
return sha3.NewCSHAKE128(N, S)
|
||||
}}
|
||||
}
|
||||
|
||||
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
|
||||
@@ -153,10 +60,9 @@ func NewCShake128(N, S []byte) ShakeHash {
|
||||
// computations on same input with different S yield unrelated outputs.
|
||||
// When N and S are both empty, this is equivalent to NewShake256.
|
||||
func NewCShake256(N, S []byte) ShakeHash {
|
||||
if len(N) == 0 && len(S) == 0 {
|
||||
return NewShake256()
|
||||
}
|
||||
return newCShake(N, S, rate256, 64, dsbyteCShake)
|
||||
return &shakeWrapper{sha3.NewCSHAKE256(N, S), 64, false, func() *sha3.SHAKE {
|
||||
return sha3.NewCSHAKE256(N, S)
|
||||
}}
|
||||
}
|
||||
|
||||
// ShakeSum128 writes an arbitrary-length digest of data into hash.
|
||||
@@ -172,3 +78,42 @@ func ShakeSum256(hash, data []byte) {
|
||||
h.Write(data)
|
||||
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()
|
||||
}
|
||||
-40
@@ -1,40 +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.
|
||||
|
||||
package sha3
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// xorIn xors the bytes in buf into the state.
|
||||
func xorIn(d *state, buf []byte) {
|
||||
if cpu.IsBigEndian {
|
||||
for i := 0; len(buf) >= 8; i++ {
|
||||
a := binary.LittleEndian.Uint64(buf)
|
||||
d.a[i] ^= a
|
||||
buf = buf[8:]
|
||||
}
|
||||
} else {
|
||||
ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
|
||||
subtle.XORBytes(ab[:], ab[:], buf)
|
||||
}
|
||||
}
|
||||
|
||||
// copyOut copies uint64s to a byte buffer.
|
||||
func copyOut(d *state, b []byte) {
|
||||
if cpu.IsBigEndian {
|
||||
for i := 0; len(b) >= 8; i++ {
|
||||
binary.LittleEndian.PutUint64(b, d.a[i])
|
||||
b = b[8:]
|
||||
}
|
||||
} else {
|
||||
ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
|
||||
copy(b, ab[:])
|
||||
}
|
||||
}
|
||||
+248
-16
@@ -26,6 +26,7 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -307,17 +308,50 @@ func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
||||
}, record.Rest, nil
|
||||
}
|
||||
|
||||
// pipelineMaxInFlight is the maximum number of outstanding requests the
|
||||
// client will pipeline to the agent before applying backpressure.
|
||||
const pipelineMaxInFlight = 32
|
||||
|
||||
// client is a client for an ssh-agent process.
|
||||
//
|
||||
// Exactly one of pipeline / (mu, conn) is set, chosen by NewClient
|
||||
// based on whether the underlying transport implements io.Closer.
|
||||
type client struct {
|
||||
// conn is typically a *net.UnixConn
|
||||
// pipeline, if non-nil, dispatches requests over a pipelined
|
||||
// connection: requests are written as soon as the wire is
|
||||
// available and responses are routed back to per-call reply
|
||||
// channels in FIFO order by a background reader goroutine.
|
||||
pipeline *pipeline
|
||||
|
||||
// mu and conn are used in fully-serialized mode, when the
|
||||
// transport does not implement io.Closer. Each call takes mu,
|
||||
// writes its request, reads the matching response, and releases
|
||||
// mu before returning. There is no background goroutine.
|
||||
mu sync.Mutex
|
||||
conn io.ReadWriter
|
||||
// mu is used to prevent concurrent access to the agent
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||
// the given connection.
|
||||
//
|
||||
// If rw also implements io.Closer (like *net.UnixConn and ssh.Channel
|
||||
// do), the returned client pipelines concurrent requests over the
|
||||
// connection: callers can issue Sign and other operations from
|
||||
// multiple goroutines and they will be written to the agent as soon
|
||||
// as the wire is available, rather than waiting for the previous
|
||||
// responses. The ssh-agent protocol still requires responses to be
|
||||
// returned in request order, so a slow request delays subsequent
|
||||
// responses on the same connection (head-of-line blocking).
|
||||
//
|
||||
// Pipelining requires io.Closer because, on a Write error, the
|
||||
// background reader goroutine must be unblocked by closing the
|
||||
// underlying connection. When rw does not implement io.Closer
|
||||
// this is not possible, so NewClient falls back to fully
|
||||
// serializing each request: a single in-flight call at a time.
|
||||
func NewClient(rw io.ReadWriter) ExtendedAgent {
|
||||
if rwc, ok := rw.(io.ReadWriteCloser); ok {
|
||||
return &client{pipeline: newPipeline(rwc)}
|
||||
}
|
||||
return &client{conn: rw}
|
||||
}
|
||||
|
||||
@@ -340,6 +374,16 @@ func (c *client) call(req []byte) (reply interface{}, err error) {
|
||||
// bytes of the response are returned; no unmarshalling is
|
||||
// performed on the response.
|
||||
func (c *client) callRaw(req []byte) (reply []byte, err error) {
|
||||
if c.pipeline != nil {
|
||||
return c.pipeline.call(req)
|
||||
}
|
||||
return c.serialCall(req)
|
||||
}
|
||||
|
||||
// serialCall implements the fully-serialized request/response path
|
||||
// used when the transport is not an io.Closer. It writes req under mu
|
||||
// and reads the matching response before returning.
|
||||
func (c *client) serialCall(req []byte) (reply []byte, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -430,8 +474,9 @@ func (c *client) List() ([]*Key, error) {
|
||||
return keys, nil
|
||||
case *failureAgentMsg:
|
||||
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
|
||||
@@ -462,8 +507,9 @@ func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFl
|
||||
return &sig, nil
|
||||
case *failureAgentMsg:
|
||||
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
|
||||
@@ -555,7 +601,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaKeyMsg{
|
||||
Type: ssh.KeyAlgoDSA,
|
||||
Type: ssh.InsecureKeyAlgoDSA,
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
@@ -575,6 +621,9 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
||||
Constraints: constraints,
|
||||
})
|
||||
case ed25519.PrivateKey:
|
||||
if len(k) != ed25519.PrivateKeySize {
|
||||
return fmt.Errorf("agent: bad ED25519 key size: %d", len(k))
|
||||
}
|
||||
req = ssh.Marshal(ed25519KeyMsg{
|
||||
Type: ssh.KeyAlgoED25519,
|
||||
Pub: []byte(k)[32:],
|
||||
@@ -586,6 +635,9 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
||||
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
||||
// We still support the pointer variant for backwards compatibility.
|
||||
case *ed25519.PrivateKey:
|
||||
if len(*k) != ed25519.PrivateKeySize {
|
||||
return fmt.Errorf("agent: bad ED25519 key size: %d", len(*k))
|
||||
}
|
||||
req = ssh.Marshal(ed25519KeyMsg{
|
||||
Type: ssh.KeyAlgoED25519,
|
||||
Pub: []byte(*k)[32:],
|
||||
@@ -661,6 +713,13 @@ func (c *client) Add(key AddedKey) error {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
for _, ext := range key.ConstraintExtensions {
|
||||
constraints = append(constraints, ssh.Marshal(constrainExtensionAgentMsg{
|
||||
ExtensionName: ext.ExtensionName,
|
||||
ExtensionDetails: ext.ExtensionDetails,
|
||||
})...)
|
||||
}
|
||||
|
||||
cert := key.Certificate
|
||||
if cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
@@ -703,6 +762,9 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
||||
Constraints: constraints,
|
||||
})
|
||||
case ed25519.PrivateKey:
|
||||
if len(k) != ed25519.PrivateKeySize {
|
||||
return fmt.Errorf("agent: bad ED25519 key size: %d", len(k))
|
||||
}
|
||||
req = ssh.Marshal(ed25519CertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
@@ -715,6 +777,9 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
||||
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
||||
// We still support the pointer variant for backwards compatibility.
|
||||
case *ed25519.PrivateKey:
|
||||
if len(*k) != ed25519.PrivateKeySize {
|
||||
return fmt.Errorf("agent: bad ED25519 key size: %d", len(*k))
|
||||
}
|
||||
req = ssh.Marshal(ed25519CertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
@@ -803,16 +868,16 @@ var _ ssh.AlgorithmSigner = &agentKeyringSigner{}
|
||||
//
|
||||
// This map must be kept in sync with the one in certs.go.
|
||||
var certKeyAlgoNames = map[string]string{
|
||||
ssh.CertAlgoRSAv01: ssh.KeyAlgoRSA,
|
||||
ssh.CertAlgoRSASHA256v01: ssh.KeyAlgoRSASHA256,
|
||||
ssh.CertAlgoRSASHA512v01: ssh.KeyAlgoRSASHA512,
|
||||
ssh.CertAlgoDSAv01: ssh.KeyAlgoDSA,
|
||||
ssh.CertAlgoECDSA256v01: ssh.KeyAlgoECDSA256,
|
||||
ssh.CertAlgoECDSA384v01: ssh.KeyAlgoECDSA384,
|
||||
ssh.CertAlgoECDSA521v01: ssh.KeyAlgoECDSA521,
|
||||
ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256,
|
||||
ssh.CertAlgoED25519v01: ssh.KeyAlgoED25519,
|
||||
ssh.CertAlgoSKED25519v01: ssh.KeyAlgoSKED25519,
|
||||
ssh.CertAlgoRSAv01: ssh.KeyAlgoRSA,
|
||||
ssh.CertAlgoRSASHA256v01: ssh.KeyAlgoRSASHA256,
|
||||
ssh.CertAlgoRSASHA512v01: ssh.KeyAlgoRSASHA512,
|
||||
ssh.InsecureCertAlgoDSAv01: ssh.InsecureKeyAlgoDSA,
|
||||
ssh.CertAlgoECDSA256v01: ssh.KeyAlgoECDSA256,
|
||||
ssh.CertAlgoECDSA384v01: ssh.KeyAlgoECDSA384,
|
||||
ssh.CertAlgoECDSA521v01: ssh.KeyAlgoECDSA521,
|
||||
ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256,
|
||||
ssh.CertAlgoED25519v01: ssh.KeyAlgoED25519,
|
||||
ssh.CertAlgoSKED25519v01: ssh.KeyAlgoSKED25519,
|
||||
}
|
||||
|
||||
// underlyingAlgo returns the signature algorithm associated with algo (which is
|
||||
@@ -852,3 +917,170 @@ func (c *client) Extension(extensionType string, contents []byte) ([]byte, error
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// pipelineResult carries either a raw agent reply or an error back to a
|
||||
// caller waiting on the response channel.
|
||||
type pipelineResult struct {
|
||||
reply []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// pipeline implements request pipelining over a single agent connection.
|
||||
//
|
||||
// Writers serialize on writeMu to both register a reply channel in the
|
||||
// pending FIFO queue and write the request bytes on the wire; the two
|
||||
// must be atomic so the queue order matches the wire order. A single
|
||||
// reader goroutine decodes responses from the connection and dispatches
|
||||
// each one to the channel at the head of the queue.
|
||||
//
|
||||
// pending is a chan-of-chan acting as a FIFO queue with a fixed
|
||||
// capacity of pipelineMaxInFlight. The outer channel provides ordering
|
||||
// (reads happen in send order) and natural backpressure (a full queue
|
||||
// blocks new writers). Each inner channel is buffered with capacity
|
||||
// one and is sent to exactly once: either by the reader goroutine
|
||||
// with the agent reply, or by shutdown with the terminal error during
|
||||
// drain. The cap-one buffer makes the producer's send non-blocking,
|
||||
// so the reader and shutdown never have to wait for the caller to be
|
||||
// scheduled on the receive.
|
||||
//
|
||||
// When the reader goroutine exits (on read error or protocol
|
||||
// violation), it closes exitCh to wake any writer blocked on the
|
||||
// pending queue, then serializes with any in-flight writer to close
|
||||
// the pending channel, and finally drains the remaining entries
|
||||
// delivering the terminal error to each waiting caller. The
|
||||
// pipeline relies on conn implementing io.Closer so a writer that
|
||||
// hits a Write error can close the connection to unblock the reader
|
||||
// goroutine; NewClient is responsible for only constructing a
|
||||
// pipeline when this guarantee holds.
|
||||
type pipeline struct {
|
||||
conn io.ReadWriteCloser
|
||||
|
||||
writeMu sync.Mutex
|
||||
// pending is the FIFO queue of reply channels with capacity
|
||||
// pipelineMaxInFlight. See type-level documentation.
|
||||
pending chan chan pipelineResult
|
||||
exitCh chan struct{}
|
||||
|
||||
// err carries the terminal error to callers blocked on a closed
|
||||
// pipeline. It is stored exactly once by the reader goroutine
|
||||
// before exitCh is closed; every read happens after observing
|
||||
// exitCh closed, so the load synchronises through the close and
|
||||
// is guaranteed to return the stored value (never nil).
|
||||
err atomic.Pointer[error]
|
||||
}
|
||||
|
||||
func newPipeline(conn io.ReadWriteCloser) *pipeline {
|
||||
p := &pipeline{
|
||||
conn: conn,
|
||||
pending: make(chan chan pipelineResult, pipelineMaxInFlight),
|
||||
exitCh: make(chan struct{}),
|
||||
}
|
||||
go p.readLoop()
|
||||
return p
|
||||
}
|
||||
|
||||
// readLoop decodes responses from conn and dispatches them in FIFO order
|
||||
// to reply channels in pending. On any failure it invokes shutdown.
|
||||
func (p *pipeline) readLoop() {
|
||||
var finalErr error
|
||||
for {
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(p.conn, sizeBuf[:]); err != nil {
|
||||
finalErr = err
|
||||
break
|
||||
}
|
||||
respSize := binary.BigEndian.Uint32(sizeBuf[:])
|
||||
if respSize > maxAgentResponseBytes {
|
||||
finalErr = errors.New("response too large")
|
||||
break
|
||||
}
|
||||
buf := make([]byte, respSize)
|
||||
if _, err := io.ReadFull(p.conn, buf); err != nil {
|
||||
finalErr = err
|
||||
break
|
||||
}
|
||||
// Successful writes always enqueue before sending bytes, so
|
||||
// pending has a waiting channel for this response.
|
||||
ch := <-p.pending
|
||||
// The reply channel is buffered with capacity 1 and is only
|
||||
// ever written to once, so this send cannot block.
|
||||
ch <- pipelineResult{reply: buf}
|
||||
}
|
||||
p.shutdown(clientErr(finalErr))
|
||||
}
|
||||
|
||||
// shutdown is called exactly once, from readLoop, when the reader is
|
||||
// terminating. It unblocks pending writers and fails all in-flight
|
||||
// requests with finalErr.
|
||||
func (p *pipeline) shutdown(finalErr error) {
|
||||
// Publish the terminal error before closing exitCh so any
|
||||
// writer that subsequently observes exitCh closed sees err.
|
||||
p.err.Store(&finalErr)
|
||||
|
||||
// Wake any writer blocked waiting for a slot in the pending queue.
|
||||
close(p.exitCh)
|
||||
|
||||
// Wait for any writer currently inside its critical section to
|
||||
// complete. After this lock, no new writer can reach the send on
|
||||
// pending: they will observe exitCh closed in the select and bail
|
||||
// out before attempting the send.
|
||||
p.writeMu.Lock()
|
||||
close(p.pending)
|
||||
p.writeMu.Unlock()
|
||||
|
||||
// Drain entries that were enqueued but never answered, delivering
|
||||
// the terminal error to their waiting callers. The reply channels
|
||||
// are buffered (cap 1) and written to exactly once, so these sends
|
||||
// cannot block.
|
||||
for ch := range p.pending {
|
||||
ch <- pipelineResult{err: finalErr}
|
||||
}
|
||||
}
|
||||
|
||||
// call sends req to the agent and returns the matching raw response.
|
||||
func (p *pipeline) call(req []byte) ([]byte, error) {
|
||||
replyCh := make(chan pipelineResult, 1)
|
||||
|
||||
p.writeMu.Lock()
|
||||
|
||||
// Priority check: if the reader has already finished shutdown,
|
||||
// pending is closed and sending to it would panic. Bail out now.
|
||||
// Once we pass this check while holding writeMu, shutdown cannot
|
||||
// complete close(pending) until we release writeMu, so the send
|
||||
// below is safe against concurrent closure.
|
||||
select {
|
||||
case <-p.exitCh:
|
||||
p.writeMu.Unlock()
|
||||
return nil, *p.err.Load()
|
||||
default:
|
||||
}
|
||||
|
||||
// Enqueue the reply channel before writing the request, so FIFO
|
||||
// order on the wire matches FIFO order in the pending queue. The
|
||||
// exitCh arm handles the case where the reader errors while we
|
||||
// block on a full queue.
|
||||
select {
|
||||
case p.pending <- replyCh:
|
||||
case <-p.exitCh:
|
||||
p.writeMu.Unlock()
|
||||
return nil, *p.err.Load()
|
||||
}
|
||||
|
||||
msg := make([]byte, 4+len(req))
|
||||
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||
copy(msg[4:], req)
|
||||
_, werr := p.conn.Write(msg)
|
||||
p.writeMu.Unlock()
|
||||
|
||||
if werr != nil {
|
||||
// The connection is in an undefined state. Close it so the
|
||||
// reader unblocks promptly and triggers shutdown for every
|
||||
// other in-flight caller. NewClient guarantees conn is a
|
||||
// real io.Closer when the pipeline is in use.
|
||||
p.conn.Close()
|
||||
return nil, clientErr(werr)
|
||||
}
|
||||
|
||||
res := <-replyCh
|
||||
return res.reply, res.err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,649 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPipelineParallelSigns(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
keyring := NewKeyring()
|
||||
if err := keyring.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"]}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go ServeAgent(keyring, c2)
|
||||
|
||||
client := NewClient(c1)
|
||||
pubKey := testPublicKeys["rsa"]
|
||||
|
||||
const N = 64
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, N)
|
||||
for i := range N {
|
||||
wg.Go(func() {
|
||||
data := []byte{byte(i), byte(i >> 8)}
|
||||
sig, err := client.Sign(pubKey, data)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
if err := pubKey.Verify(data, sig); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipelineBackpressure(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
const total = pipelineMaxInFlight + 8
|
||||
|
||||
// Fake agent: the reader drains requests from the wire independently
|
||||
// of response production, so the write path is never self-blocked.
|
||||
// The responder only writes a canned success reply once unblocked
|
||||
// via gate.
|
||||
var reqsRead int64
|
||||
gate := make(chan struct{}, total)
|
||||
pending := make(chan struct{}, total+pipelineMaxInFlight)
|
||||
readerDone := make(chan struct{})
|
||||
responderDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(readerDone)
|
||||
defer close(pending)
|
||||
for {
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(c2, sizeBuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
size := binary.BigEndian.Uint32(sizeBuf[:])
|
||||
req := make([]byte, size)
|
||||
if _, err := io.ReadFull(c2, req); err != nil {
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&reqsRead, 1)
|
||||
pending <- struct{}{}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer close(responderDone)
|
||||
for range pending {
|
||||
<-gate
|
||||
var out [5]byte
|
||||
binary.BigEndian.PutUint32(out[:4], 1)
|
||||
out[4] = agentSuccess
|
||||
if _, err := c2.Write(out[:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
<-readerDone
|
||||
<-responderDone
|
||||
}()
|
||||
|
||||
client := NewClient(c1)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan error, total)
|
||||
for range total {
|
||||
wg.Go(func() {
|
||||
// RemoveAll maps to a simpleCall expecting agentSuccess.
|
||||
if err := client.RemoveAll(); err != nil {
|
||||
errs <- err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for the client to fill the pipeline.
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if atomic.LoadInt64(&reqsRead) >= pipelineMaxInFlight {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if got := atomic.LoadInt64(&reqsRead); got != pipelineMaxInFlight {
|
||||
t.Errorf("requests in flight before any response: got %d, want %d", got, pipelineMaxInFlight)
|
||||
}
|
||||
|
||||
// Release all responses; every caller should eventually complete.
|
||||
for range total {
|
||||
gate <- struct{}{}
|
||||
}
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := atomic.LoadInt64(&reqsRead); got != total {
|
||||
t.Errorf("total requests received: got %d, want %d", got, total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipelineConnCloseFailsInFlight(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
// Server that reads requests but never replies, so all calls are
|
||||
// parked waiting for responses.
|
||||
serverExit := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverExit)
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
if _, err := c2.Read(buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
client := NewClient(c1)
|
||||
|
||||
const N = 8
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan error, N)
|
||||
for range N {
|
||||
wg.Go(func() {
|
||||
errs <- client.RemoveAll()
|
||||
})
|
||||
}
|
||||
|
||||
// Give callers time to enter the pipeline, then tear down the conn.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
|
||||
// All callers must return promptly with an error.
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(doneCh)
|
||||
}()
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("pipelined callers did not complete after conn close")
|
||||
}
|
||||
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
if err == nil {
|
||||
t.Error("expected error after conn close, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Subsequent calls also fail fast rather than hanging.
|
||||
errCh := make(chan error, 1)
|
||||
go func() { errCh <- client.RemoveAll() }()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err == nil {
|
||||
t.Error("expected error on post-close call, got nil")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("post-close call hung")
|
||||
}
|
||||
|
||||
<-serverExit
|
||||
}
|
||||
|
||||
// readWriter exposes only io.Reader and io.Writer, hiding any Close
|
||||
// method on the wrapped value so NewClient cannot take the pipelined
|
||||
// path.
|
||||
type readWriter struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func TestNewClientSerialFallback(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
keyring := NewKeyring()
|
||||
if err := keyring.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"]}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go ServeAgent(keyring, c2)
|
||||
|
||||
rw := readWriter{Reader: c1, Writer: c1}
|
||||
ag := NewClient(rw)
|
||||
|
||||
ci, ok := ag.(*client)
|
||||
if !ok {
|
||||
t.Fatalf("NewClient: got %T, want *client", ag)
|
||||
}
|
||||
if ci.pipeline != nil {
|
||||
t.Error("NewClient with non-Closer rw: pipeline must be nil")
|
||||
}
|
||||
if ci.conn == nil {
|
||||
t.Error("NewClient with non-Closer rw: conn must be set")
|
||||
}
|
||||
|
||||
// The serialized path correctly handles a request/response cycle.
|
||||
keys, err := ag.List()
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(keys) != 1 {
|
||||
t.Errorf("List: got %d keys, want 1", len(keys))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialFallbackParallelSigns(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
keyring := NewKeyring()
|
||||
if err := keyring.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"]}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go ServeAgent(keyring, c2)
|
||||
|
||||
rw := readWriter{Reader: c1, Writer: c1}
|
||||
ag := NewClient(rw)
|
||||
|
||||
if ci, ok := ag.(*client); !ok {
|
||||
t.Fatalf("NewClient: got %T, want *client", ag)
|
||||
} else if ci.pipeline != nil {
|
||||
t.Fatal("pipeline must be nil for non-Closer rw")
|
||||
}
|
||||
|
||||
pubKey := testPublicKeys["rsa"]
|
||||
|
||||
const N = 64
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, N)
|
||||
for i := range N {
|
||||
wg.Go(func() {
|
||||
data := []byte{byte(i), byte(i >> 8)}
|
||||
sig, err := ag.Sign(pubKey, data)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
if err := pubKey.Verify(data, sig); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// writeFailConn wraps a net.Conn and forces Write to return an error when
|
||||
// the fail flag is set. Close delegates to the underlying conn so the test
|
||||
// can observe whether the pipeline closed it.
|
||||
type writeFailConn struct {
|
||||
net.Conn
|
||||
fail atomic.Bool
|
||||
}
|
||||
|
||||
func (c *writeFailConn) Write(p []byte) (int, error) {
|
||||
if c.fail.Load() {
|
||||
return 0, errors.New("synthetic write failure")
|
||||
}
|
||||
return c.Conn.Write(p)
|
||||
}
|
||||
|
||||
func TestPipelineWriteErrorClosesConn(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c2.Close()
|
||||
|
||||
// Peer that reads and discards, exiting when the conn closes.
|
||||
serverExit := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverExit)
|
||||
io.Copy(io.Discard, c2)
|
||||
}()
|
||||
|
||||
wrapper := &writeFailConn{Conn: c1}
|
||||
wrapper.fail.Store(true)
|
||||
|
||||
client := NewClient(wrapper)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() { errCh <- client.RemoveAll() }()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err == nil {
|
||||
t.Fatal("expected error on forced write failure, got nil")
|
||||
}
|
||||
// The caller whose Write failed must receive the actual
|
||||
// write error, not the reader's "closed connection" error
|
||||
// from the post-shutdown drain.
|
||||
if !strings.Contains(err.Error(), "synthetic write failure") {
|
||||
t.Errorf("got %q, want error containing %q", err, "synthetic write failure")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("RemoveAll hung after write failure")
|
||||
}
|
||||
|
||||
// The pipeline must have closed the underlying conn, so the peer
|
||||
// goroutine returns from its Read.
|
||||
select {
|
||||
case <-serverExit:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("connection was not closed after write failure")
|
||||
}
|
||||
|
||||
// After shutdown, subsequent calls must also fail promptly.
|
||||
errCh2 := make(chan error, 1)
|
||||
go func() { errCh2 <- client.RemoveAll() }()
|
||||
select {
|
||||
case err := <-errCh2:
|
||||
if err == nil {
|
||||
t.Error("expected error on post-shutdown call, got nil")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("post-shutdown call hung")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipelineResponseTooLarge(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
// Server: read the request, then reply with a size header that
|
||||
// exceeds maxAgentResponseBytes. The size check fires before the
|
||||
// body is read, so no body needs to follow.
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(c2, sizeBuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
size := binary.BigEndian.Uint32(sizeBuf[:])
|
||||
if _, err := io.CopyN(io.Discard, c2, int64(size)); err != nil {
|
||||
return
|
||||
}
|
||||
var out [4]byte
|
||||
binary.BigEndian.PutUint32(out[:], maxAgentResponseBytes+1)
|
||||
c2.Write(out[:])
|
||||
}()
|
||||
|
||||
client := NewClient(c1)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() { errCh <- client.RemoveAll() }()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "response too large") {
|
||||
t.Errorf("got %q, want error containing %q", err, "response too large")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("call hung")
|
||||
}
|
||||
|
||||
// Subsequent calls must also fail: the pipeline is shut down.
|
||||
errCh2 := make(chan error, 1)
|
||||
go func() { errCh2 <- client.RemoveAll() }()
|
||||
select {
|
||||
case err := <-errCh2:
|
||||
if err == nil {
|
||||
t.Error("expected error on post-shutdown call, got nil")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("post-shutdown call hung")
|
||||
}
|
||||
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestPipelineShutdownWithBlockedWriter(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
|
||||
// Server drains requests off the wire so writes never block on
|
||||
// backpressure from the transport, but never replies. The reader
|
||||
// goroutine therefore stays parked on Read, never dequeues from
|
||||
// pending, and the queue fills up.
|
||||
var reqsRead int64
|
||||
serverExit := make(chan struct{})
|
||||
go func() {
|
||||
defer close(serverExit)
|
||||
for {
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(c2, sizeBuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
size := binary.BigEndian.Uint32(sizeBuf[:])
|
||||
if _, err := io.CopyN(io.Discard, c2, int64(size)); err != nil {
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&reqsRead, 1)
|
||||
}
|
||||
}()
|
||||
|
||||
client := NewClient(c1)
|
||||
|
||||
const total = pipelineMaxInFlight + 4
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan error, total)
|
||||
for range total {
|
||||
wg.Go(func() {
|
||||
errs <- client.RemoveAll()
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for the queue to fill: once pipelineMaxInFlight requests
|
||||
// are on the wire, pending is at capacity and the +1 writer is
|
||||
// blocked in the second-select arm; the rest are queued behind
|
||||
// writeMu.
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if atomic.LoadInt64(&reqsRead) >= pipelineMaxInFlight {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if got := atomic.LoadInt64(&reqsRead); got < pipelineMaxInFlight {
|
||||
t.Fatalf("requests on wire: got %d, want >= %d", got, pipelineMaxInFlight)
|
||||
}
|
||||
// Give the +1 writer a moment to actually park in the second
|
||||
// select before we trigger shutdown.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Trigger shutdown: closing the server side makes the client's
|
||||
// reader hit EOF, which calls shutdown(...).
|
||||
c2.Close()
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(doneCh)
|
||||
}()
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("blocked callers did not complete after shutdown")
|
||||
}
|
||||
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
if err == nil {
|
||||
t.Error("expected error after shutdown, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
<-serverExit
|
||||
}
|
||||
|
||||
// multiDeviceAgent simulates an ssh-agent backed by numDevices signing
|
||||
// devices, each of which takes latency to process a request. Incoming
|
||||
// requests are round-robin dispatched to devices, and responses are
|
||||
// serialized back to the wire in request order as the protocol requires.
|
||||
//
|
||||
// The simulation does not emit a semantically meaningful reply; it just
|
||||
// writes a 1-byte SSH_AGENT_SUCCESS after the simulated latency has
|
||||
// elapsed. This is enough to exercise callRaw/simpleCall (RemoveAll).
|
||||
func multiDeviceAgent(t testing.TB, conn io.ReadWriteCloser, numDevices int, latency time.Duration) (done <-chan struct{}) {
|
||||
t.Helper()
|
||||
|
||||
type job struct {
|
||||
ready chan struct{}
|
||||
}
|
||||
|
||||
devices := make([]chan job, numDevices)
|
||||
var devicesWG sync.WaitGroup
|
||||
for i := range numDevices {
|
||||
devices[i] = make(chan job, 1024)
|
||||
ch := devices[i]
|
||||
devicesWG.Go(func() {
|
||||
for j := range ch {
|
||||
time.Sleep(latency)
|
||||
close(j.ready)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Order-preserving responder: responses are written in the same
|
||||
// order the requests were read, regardless of which device finished
|
||||
// first.
|
||||
orderCh := make(chan chan struct{}, 1024)
|
||||
responderDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(responderDone)
|
||||
for ready := range orderCh {
|
||||
<-ready
|
||||
var out [5]byte
|
||||
binary.BigEndian.PutUint32(out[:4], 1)
|
||||
out[4] = agentSuccess
|
||||
if _, err := conn.Write(out[:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
finished := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finished)
|
||||
defer func() {
|
||||
close(orderCh)
|
||||
<-responderDone
|
||||
for _, d := range devices {
|
||||
close(d)
|
||||
}
|
||||
devicesWG.Wait()
|
||||
}()
|
||||
next := 0
|
||||
for {
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(conn, sizeBuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
size := binary.BigEndian.Uint32(sizeBuf[:])
|
||||
if _, err := io.CopyN(io.Discard, conn, int64(size)); err != nil {
|
||||
return
|
||||
}
|
||||
ready := make(chan struct{})
|
||||
devices[next%numDevices] <- job{ready: ready}
|
||||
next++
|
||||
orderCh <- ready
|
||||
}
|
||||
}()
|
||||
|
||||
return finished
|
||||
}
|
||||
|
||||
// BenchmarkPipelineMultiDevice measures throughput of the pipelined
|
||||
// agent client against a simulated multi-device agent, varying the
|
||||
// number of backend devices.
|
||||
//
|
||||
// Setup: each simulated signing device takes 2ms per request. The
|
||||
// protocol serializes responses in request order, but pipelining lets
|
||||
// the client keep multiple devices busy concurrently; throughput
|
||||
// should scale roughly linearly with numDevices up to the pipeline
|
||||
// depth (pipelineMaxInFlight).
|
||||
//
|
||||
// Run with: go test -benchmem -bench BenchmarkPipelineMultiDevice -benchtime=2s ./ssh/agent/
|
||||
func BenchmarkPipelineMultiDevice(b *testing.B) {
|
||||
const (
|
||||
deviceLatency = 2 * time.Millisecond
|
||||
parallelism = 32
|
||||
)
|
||||
|
||||
for _, numDevices := range []int{1, 2, 4, 8, 16} {
|
||||
b.Run(fmt.Sprintf("Devices=%d", numDevices), func(b *testing.B) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
serverDone := multiDeviceAgent(b, c2, numDevices, deviceLatency)
|
||||
|
||||
client := NewClient(c1)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetParallelism(parallelism)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.RemoveAll(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.StopTimer()
|
||||
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
<-serverDone
|
||||
})
|
||||
}
|
||||
}
|
||||
+147
-1
@@ -6,7 +6,9 @@ package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
@@ -39,7 +41,11 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-s")
|
||||
cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior.
|
||||
cmd.Env = []string{
|
||||
// ssh-agent creates ~/.ssh and ~/.ssh/agent;
|
||||
// ensure a writeable home directory.
|
||||
"HOME=" + t.TempDir(),
|
||||
} // Do not let the user's environment influence ssh-agent behavior.
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
@@ -347,6 +353,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) {
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
@@ -542,3 +595,96 @@ func TestAgentExtensions(t *testing.T) {
|
||||
t.Fatal("should have gotten agent extension failure")
|
||||
}
|
||||
}
|
||||
|
||||
// capturingAgent records the AddedKey observed on the server side of the
|
||||
// agent protocol. It forwards to a keyring with constraint fields stripped,
|
||||
// so the keyring's own rejection of unsupported constraints does not
|
||||
// interfere with what this test is measuring: the client-side wire
|
||||
// serialization of ConstraintExtensions.
|
||||
type capturingAgent struct {
|
||||
Agent
|
||||
lastAdd AddedKey
|
||||
}
|
||||
|
||||
func newCapturingAgent() *capturingAgent {
|
||||
return &capturingAgent{Agent: NewKeyring()}
|
||||
}
|
||||
|
||||
func (a *capturingAgent) Add(key AddedKey) error {
|
||||
a.lastAdd = key
|
||||
stripped := key
|
||||
stripped.ConstraintExtensions = nil
|
||||
stripped.ConfirmBeforeUse = false
|
||||
return a.Agent.Add(stripped)
|
||||
}
|
||||
|
||||
// TestAddConstraintExtensionsWireFormat verifies that client.Add serializes
|
||||
// ConstraintExtensions into the SSH_AGENTC_ADD_IDENTITY payload and the
|
||||
// server deserializes them back into the AddedKey delivered to the backend.
|
||||
// Regressions in the client marshal loop (missing, swapped fields, wrong
|
||||
// framing) would be invisible to a keyring-based rejection test, which
|
||||
// signals only "extensions were present", not "the right ones arrived".
|
||||
func TestAddConstraintExtensionsWireFormat(t *testing.T) {
|
||||
capturing := newCapturingAgent()
|
||||
client, cleanup := startAgent(t, capturing)
|
||||
defer cleanup()
|
||||
|
||||
constraints := []ConstraintExtension{
|
||||
{ExtensionName: "ext-one@example.com", ExtensionDetails: []byte("details-one")},
|
||||
{ExtensionName: "ext-two@example.com", ExtensionDetails: []byte("\x00\x01\x02\xff")},
|
||||
}
|
||||
|
||||
if err := client.Add(AddedKey{
|
||||
PrivateKey: testPrivateKeys["rsa"],
|
||||
Comment: "wire-format-test",
|
||||
ConstraintExtensions: constraints,
|
||||
}); err != nil {
|
||||
t.Fatalf("client.Add: %v", err)
|
||||
}
|
||||
|
||||
got := capturing.lastAdd.ConstraintExtensions
|
||||
if len(got) != len(constraints) {
|
||||
t.Fatalf("server received %d extensions, want %d", len(got), len(constraints))
|
||||
}
|
||||
for i, want := range constraints {
|
||||
if got[i].ExtensionName != want.ExtensionName {
|
||||
t.Errorf("extension[%d] name: got %q, want %q", i, got[i].ExtensionName, want.ExtensionName)
|
||||
}
|
||||
if !bytes.Equal(got[i].ExtensionDetails, want.ExtensionDetails) {
|
||||
t.Errorf("extension[%d] details: got %x, want %x", i, got[i].ExtensionDetails, want.ExtensionDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRejectsShortEd25519Key(t *testing.T) {
|
||||
short := ed25519.PrivateKey(make([]byte, 16))
|
||||
// insertCert reaches its ed25519 branch only when AddedKey.Certificate
|
||||
// is non-nil. The length check returns before cert.Type/Marshal run, so
|
||||
// an empty certificate is sufficient to drive the path.
|
||||
emptyCert := &ssh.Certificate{}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
add AddedKey
|
||||
}{
|
||||
{"insertKey value", AddedKey{PrivateKey: short}},
|
||||
{"insertKey pointer", AddedKey{PrivateKey: &short}},
|
||||
{"insertCert value", AddedKey{PrivateKey: short, Certificate: emptyCert}},
|
||||
{"insertCert pointer", AddedKey{PrivateKey: &short, Certificate: emptyCert}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
err := client.Add(tc.add)
|
||||
if err == nil {
|
||||
t.Fatal("Add accepted ed25519 key shorter than 64 bytes")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad ED25519 key size") {
|
||||
t.Errorf("got error %q, want substring %q", err.Error(), "bad ED25519 key size")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+19
-6
@@ -32,8 +32,10 @@ type keyring struct {
|
||||
|
||||
var errLocked = errors.New("agent: locked")
|
||||
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe for
|
||||
// concurrent use by multiple goroutines.
|
||||
//
|
||||
// The returned Agent only supports the "lifetime" constraint.
|
||||
func NewKeyring() Agent {
|
||||
return &keyring{}
|
||||
}
|
||||
@@ -112,7 +114,7 @@ func (r *keyring) Unlock(passphrase []byte) error {
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (r *keyring) expireKeysLocked() {
|
||||
for _, k := range r.keys {
|
||||
@@ -143,15 +145,26 @@ func (r *keyring) List() ([]*Key, error) {
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the keyring. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
// Add adds a private key to the keyring. If a certificate is given, that
|
||||
// certificate is added as public key.
|
||||
//
|
||||
// Add returns an error if key contains ConstraintExtensions or
|
||||
// ConfirmBeforeUse.
|
||||
func (r *keyring) Add(key AddedKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
return errors.New("agent: confirm before use constraint is not supported")
|
||||
}
|
||||
|
||||
if len(key.ConstraintExtensions) > 0 {
|
||||
return errors.New("agent: constraint extensions are present but not supported")
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
actualKeys := make(map[string]bool)
|
||||
@@ -120,3 +120,42 @@ func TestAddDuplicateKey(t *testing.T) {
|
||||
t.Fatal("key with the updated comment not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddKeyWithConstraints(t *testing.T) {
|
||||
// Verifies the keyring refuses keys carrying constraint extensions it
|
||||
// cannot enforce.
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
constraints := []ConstraintExtension{
|
||||
{
|
||||
ExtensionName: "extension1",
|
||||
ExtensionDetails: []byte("details1"),
|
||||
},
|
||||
}
|
||||
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
err := agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
ConstraintExtensions: constraints,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("adding a key with unsupported constraints succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddKeyWithConfirmBeforeUse(t *testing.T) {
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
err := agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
ConfirmBeforeUse: true,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("adding a key with confirm before use constraint succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
+38
-7
@@ -36,7 +36,7 @@ func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||
return []byte{agentFailure}
|
||||
}
|
||||
|
||||
if err == nil && rep == nil {
|
||||
if rep == nil {
|
||||
return []byte{agentSuccess}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse
|
||||
for len(constraints) != 0 {
|
||||
switch constraints[0] {
|
||||
case agentConstrainLifetime:
|
||||
if len(constraints) < 5 {
|
||||
return 0, false, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
||||
constraints = constraints[5:]
|
||||
case agentConstrainConfirm:
|
||||
@@ -237,13 +240,35 @@ func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRSAKeyParams enforces the same bounds as parseRSA in the ssh
|
||||
// package, and additionally caps the prime factors. Without this,
|
||||
// the rsa.PrivateKey built from an Add request would call Precompute()
|
||||
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
|
||||
// |p| and can consume excessive CPU on oversized keys.
|
||||
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
|
||||
if N.BitLen() > 8192 {
|
||||
return errors.New("agent: RSA modulus too large")
|
||||
}
|
||||
if P.BitLen() > 4096 || Q.BitLen() > 4096 {
|
||||
return errors.New("agent: RSA prime too large")
|
||||
}
|
||||
if E.BitLen() > 24 {
|
||||
return errors.New("agent: RSA public exponent too large")
|
||||
}
|
||||
e := E.Int64()
|
||||
if e < 3 || e&1 == 0 {
|
||||
return errors.New("agent: incorrect RSA public exponent")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRSAKey(req []byte) (*AddedKey, error) {
|
||||
var k rsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.E.BitLen() > 30 {
|
||||
return nil, errors.New("agent: RSA public exponent too large")
|
||||
if err := checkRSAKeyParams(k.N, k.E, k.P, k.Q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
@@ -267,6 +292,9 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
|
||||
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||
@@ -333,6 +361,9 @@ func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
@@ -390,8 +421,8 @@ func parseRSACert(req []byte) (*AddedKey, error) {
|
||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||
}
|
||||
|
||||
if rsaPub.E.BitLen() > 30 {
|
||||
return nil, errors.New("agent: RSA public exponent too large")
|
||||
if err := checkRSAKeyParams(rsaPub.N, rsaPub.E, k.P, k.Q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priv := rsa.PrivateKey{
|
||||
@@ -506,7 +537,7 @@ func (s *server) insertIdentity(req []byte) error {
|
||||
switch record.Type {
|
||||
case ssh.KeyAlgoRSA:
|
||||
addedKey, err = parseRSAKey(req)
|
||||
case ssh.KeyAlgoDSA:
|
||||
case ssh.InsecureKeyAlgoDSA:
|
||||
addedKey, err = parseDSAKey(req)
|
||||
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
||||
addedKey, err = parseECDSAKey(req)
|
||||
@@ -514,7 +545,7 @@ func (s *server) insertIdentity(req []byte) error {
|
||||
addedKey, err = parseEd25519Key(req)
|
||||
case ssh.CertAlgoRSAv01:
|
||||
addedKey, err = parseRSACert(req)
|
||||
case ssh.CertAlgoDSAv01:
|
||||
case ssh.InsecureCertAlgoDSAv01:
|
||||
addedKey, err = parseDSACert(req)
|
||||
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
||||
addedKey, err = parseECDSACert(req)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user