Compare commits
227
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbb6ec16ec | ||
|
|
403f69900e | ||
|
|
055043dfed | ||
|
|
08396bb92b | ||
|
|
9d2ee975ef | ||
|
|
4e5a26183e | ||
|
|
152cdb1503 | ||
|
|
fdfe1f8531 | ||
|
|
b8ffc16e10 | ||
|
|
7e6fbd82c8 | ||
|
|
bda2f3f5cf | ||
|
|
325b735346 | ||
|
|
1eadac50a5 | ||
|
|
b2d7c26edb | ||
|
|
1c17e20020 | ||
|
|
270bf2552c | ||
|
|
1cf1811d71 | ||
|
|
a2edfb5072 | ||
|
|
ff15cd57d1 | ||
|
|
eb61739cd9 | ||
|
|
42c83fffff | ||
|
|
e668aa9b45 | ||
|
|
df0bc9e1bb | ||
|
|
74c2ba9521 | ||
|
|
cf8dcb0f7d | ||
|
|
4f30245552 | ||
|
|
1d57292618 | ||
|
|
8779cbd1c9 | ||
|
|
2aeefc3f8a | ||
|
|
e3cc52e598 | ||
|
|
833695f0a5 | ||
|
|
ec07f4e35b | ||
|
|
b665ba6f6f | ||
|
|
a1aeb9b34e | ||
|
|
28c53ff63c | ||
|
|
3f0842a464 | ||
|
|
e90f1e17ee | ||
|
|
d359caa4a3 | ||
|
|
c5370d2cc6 | ||
|
|
0d375be9b6 | ||
|
|
b4ddeeda5b | ||
|
|
edc325d13a | ||
|
|
eab9315960 | ||
|
|
ddfa82138e | ||
|
|
d08e19beac | ||
|
|
12e1fcd8c0 | ||
|
|
3f8f0645c2 | ||
|
|
5df3b59941 | ||
|
|
2e82bdd171 | ||
|
|
64e0e99383 | ||
|
|
23b1b90df2 | ||
|
|
e98487292d | ||
|
|
183630ada7 | ||
|
|
a9e447dde7 | ||
|
|
64c3993f5c | ||
|
|
5fe8145aca | ||
|
|
043e94c17a | ||
|
|
0d502d7cd6 | ||
|
|
0ff60057bb | ||
|
|
8e447d8cc5 | ||
|
|
22c9997952 | ||
|
|
d0b316056b | ||
|
|
16222386f4 | ||
|
|
a4e984136a | ||
|
|
3ef8056490 | ||
|
|
a8cc953517 | ||
|
|
7d6d3f5d4a | ||
|
|
1faeef9713 | ||
|
|
00fd4ff485 | ||
|
|
018c28f8a1 | ||
|
|
9cd0187c09 | ||
|
|
c6a20f9984 | ||
|
|
776e461a4e | ||
|
|
ebe92624d1 | ||
|
|
a9f661cb6e | ||
|
|
310bfa40f1 | ||
|
|
59ff47295c | ||
|
|
3d872d0428 | ||
|
|
bc7d1d1eb5 | ||
|
|
7e3ac2043e | ||
|
|
23edec0b38 | ||
|
|
f495dc37d5 | ||
|
|
eb2c406296 | ||
|
|
2c476679df | ||
|
|
0ec7e8322c | ||
|
|
6fad3dfc18 | ||
|
|
21d60a1521 | ||
|
|
183a9b70cc | ||
|
|
334521b960 | ||
|
|
a3485e1740 | ||
|
|
642fcc37f5 | ||
|
|
56aed06173 | ||
|
|
9be5aaad42 | ||
|
|
d6f0a8c073 | ||
|
|
4161e89ecf | ||
|
|
eccd6366d1 | ||
|
|
4ba4fb4dd9 | ||
|
|
35f4265a4b | ||
|
|
c86fa9a7ed | ||
|
|
bd7e27e617 | ||
|
|
5757bc0c55 | ||
|
|
bc19a97f63 | ||
|
|
04dced17f7 | ||
|
|
630584e8d5 | ||
|
|
05595931fe | ||
|
|
793ad666bf | ||
|
|
6f7dac9698 | ||
|
|
85d78b3ac1 | ||
|
|
403b01795a | ||
|
|
46612604a0 | ||
|
|
c6db032c6c | ||
|
|
2cf3adece1 | ||
|
|
eb4f295cb3 | ||
|
|
7b82a4e95d | ||
|
|
5352b09029 | ||
|
|
ae2d96664a | ||
|
|
2c7772ba30 | ||
|
|
3147a52a75 | ||
|
|
5d542ad81a | ||
|
|
a5774263c1 | ||
|
|
1baeb1ce4c | ||
|
|
fcc990c556 | ||
|
|
4d399097f0 | ||
|
|
e4b3678e5f | ||
|
|
9b076918e3 | ||
|
|
b769efc7c0 | ||
|
|
6068a2e6cf | ||
|
|
efcb8507fb | ||
|
|
86341886e2 | ||
|
|
1e6e3497d5 | ||
|
|
f4118a5b28 | ||
|
|
db638375bc | ||
|
|
dad33157f4 | ||
|
|
2f5cac09c9 | ||
|
|
65fa2f7aa8 | ||
|
|
bba287dce9 | ||
|
|
20e1d8d225 | ||
|
|
30dcbda588 | ||
|
|
198e4374d7 | ||
|
|
aa10faf2a1 | ||
|
|
e04a8579fe | ||
|
|
5e0467b6c7 | ||
|
|
e495a2d5b3 | ||
|
|
4570a0811e | ||
|
|
5770296d90 | ||
|
|
ae814b36b8 | ||
|
|
b4de73f9ec | ||
|
|
ceb1ce70b4 | ||
|
|
089bfa5675 | ||
|
|
84f357641f | ||
|
|
c084706c22 | ||
|
|
32db794688 | ||
|
|
0a44fdfbc1 | ||
|
|
0ba0e8f031 | ||
|
|
a769d52b0f | ||
|
|
5ff15b2933 | ||
|
|
c07d793c2f | ||
|
|
cd7d49e622 | ||
|
|
38f3c27a63 | ||
|
|
3497b51f5e | ||
|
|
e9a32991a8 | ||
|
|
83a5a9bb28 | ||
|
|
5bf0f12bab | ||
|
|
4f45737414 | ||
|
|
0c34fe9e7d | ||
|
|
513c2a44f6 | ||
|
|
e6e6c4f2bb | ||
|
|
5ea612d1eb | ||
|
|
b8e89b74b9 | ||
|
|
1f2b32a52c | ||
|
|
042588c79f | ||
|
|
86c0c3e7f9 | ||
|
|
eec23a3978 | ||
|
|
9d13527586 | ||
|
|
8b5274cf68 | ||
|
|
5f87f3452a | ||
|
|
be400aefbc | ||
|
|
c8d3bf9c53 | ||
|
|
c1f2f97bff | ||
|
|
4be66e5b65 | ||
|
|
0c6587e931 | ||
|
|
9e8e0b3908 | ||
|
|
84dcc777aa | ||
|
|
7f63de1d35 | ||
|
|
c90954cbb9 | ||
|
|
eb9a90e9f9 | ||
|
|
afb6bcd081 | ||
|
|
5c72a88397 | ||
|
|
123391ffb6 | ||
|
|
948cd5f358 | ||
|
|
ab33eee955 | ||
|
|
75b288015a | ||
|
|
70a84ac30b | ||
|
|
279210d13f | ||
|
|
06a226fb4e | ||
|
|
4b2356b1ed | ||
|
|
729f1e841b | ||
|
|
4bdfaf469e | ||
|
|
18b771bd64 | ||
|
|
44a6062868 | ||
|
|
3c4aac8981 | ||
|
|
a76a400e30 | ||
|
|
1c2c788b11 | ||
|
|
884d27f428 | ||
|
|
0848c95719 | ||
|
|
4f8f47aa79 | ||
|
|
056763e48d | ||
|
|
baeed622b8 | ||
|
|
0ec3e9974c | ||
|
|
891825fb96 | ||
|
|
97fc981609 | ||
|
|
1b76d66859 | ||
|
|
f7b00557c8 | ||
|
|
78000ba7a0 | ||
|
|
32487eceac | ||
|
|
2aa609cf4a | ||
|
|
eddbd5da47 | ||
|
|
0f24fbd83d | ||
|
|
bac4c82f69 | ||
|
|
1ad67e1f0e | ||
|
|
1d94cc7ab1 | ||
|
|
86ce3cb696 | ||
|
|
6ca56c2f2e | ||
|
|
ecb85df213 | ||
|
|
a95e85b341 | ||
|
|
a0c6ece9d3 | ||
|
|
c9f3fb736b |
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Add no patterns to .hgignore except for files generated by the build.
|
||||
# Add no patterns to .gitignore except for files generated by the build.
|
||||
last-change
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/AUTHORS.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/CONTRIBUTORS.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Go Cryptography
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/crypto)
|
||||
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
## Download/Install
|
||||
|
||||
+98
-377
@@ -3,17 +3,20 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package acme provides an implementation of the
|
||||
// Automatic Certificate Management Environment (ACME) spec.
|
||||
// The intial implementation was based on ACME draft-02 and
|
||||
// is now being extended to comply with RFC 8555.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02
|
||||
// and https://tools.ietf.org/html/rfc8555 for details.
|
||||
// Automatic Certificate Management Environment (ACME) spec,
|
||||
// most famously used by Let's Encrypt.
|
||||
//
|
||||
// The initial implementation of this package was based on an early version
|
||||
// of the spec. The current implementation supports only the modern
|
||||
// RFC 8555 but some of the old API surface remains for compatibility.
|
||||
// While code using the old API will still compile, it will return an error.
|
||||
// Note the deprecation comments to update your code.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc8555 for the spec.
|
||||
//
|
||||
// Most common scenarios will want to use autocert subdirectory instead,
|
||||
// which provides automatic access to certificates from Let's Encrypt
|
||||
// and any other ACME-based CA.
|
||||
//
|
||||
// This package is a work in progress and makes no API stability promises.
|
||||
package acme
|
||||
|
||||
import (
|
||||
@@ -33,8 +36,6 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -55,8 +56,9 @@ const (
|
||||
ALPNProto = "acme-tls/1"
|
||||
)
|
||||
|
||||
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
// idPeACMEIdentifier is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
||||
var idPeACMEIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
|
||||
const (
|
||||
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||
@@ -71,22 +73,22 @@ const (
|
||||
)
|
||||
|
||||
// Client is an ACME client.
|
||||
//
|
||||
// The only required field is Key. An example of creating a client with a new key
|
||||
// is as follows:
|
||||
//
|
||||
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// client := &Client{Key: key}
|
||||
//
|
||||
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// client := &Client{Key: key}
|
||||
type Client struct {
|
||||
// Key is the account key used to register with a CA and sign requests.
|
||||
// Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
|
||||
//
|
||||
// The following algorithms are supported:
|
||||
// RS256, ES256, ES384 and ES512.
|
||||
// See RFC7518 for more details about the algorithms.
|
||||
// See RFC 7518 for more details about the algorithms.
|
||||
Key crypto.Signer
|
||||
|
||||
// HTTPClient optionally specifies an HTTP client to use
|
||||
@@ -124,7 +126,9 @@ type Client struct {
|
||||
|
||||
cacheMu sync.Mutex
|
||||
dir *Directory // cached result of Client's Discover method
|
||||
kid keyID // cached Account.URI obtained from registerRFC or getAccountRFC
|
||||
// KID is the key identifier provided by the CA. If not provided it will be
|
||||
// retrieved from the CA by making a call to the registration endpoint.
|
||||
KID KeyID
|
||||
|
||||
noncesMu sync.Mutex
|
||||
nonces map[string]struct{} // nonces collected from previous responses
|
||||
@@ -139,23 +143,22 @@ type Client struct {
|
||||
//
|
||||
// When in pre-RFC mode or when c.getRegRFC responds with an error, accountKID
|
||||
// returns noKeyID.
|
||||
func (c *Client) accountKID(ctx context.Context) keyID {
|
||||
func (c *Client) accountKID(ctx context.Context) KeyID {
|
||||
c.cacheMu.Lock()
|
||||
defer c.cacheMu.Unlock()
|
||||
if !c.dir.rfcCompliant() {
|
||||
return noKeyID
|
||||
}
|
||||
if c.kid != noKeyID {
|
||||
return c.kid
|
||||
if c.KID != noKeyID {
|
||||
return c.KID
|
||||
}
|
||||
a, err := c.getRegRFC(ctx)
|
||||
if err != nil {
|
||||
return noKeyID
|
||||
}
|
||||
c.kid = keyID(a.URI)
|
||||
return c.kid
|
||||
c.KID = KeyID(a.URI)
|
||||
return c.KID
|
||||
}
|
||||
|
||||
var errPreRFC = errors.New("acme: server does not support the RFC 8555 version of ACME")
|
||||
|
||||
// Discover performs ACME server discovery using c.DirectoryURL.
|
||||
//
|
||||
// It caches successful result. So, subsequent calls will not result in
|
||||
@@ -176,53 +179,36 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
c.addNonce(res.Header)
|
||||
|
||||
var v struct {
|
||||
Reg string `json:"new-reg"`
|
||||
RegRFC string `json:"newAccount"`
|
||||
Authz string `json:"new-authz"`
|
||||
AuthzRFC string `json:"newAuthz"`
|
||||
OrderRFC string `json:"newOrder"`
|
||||
Cert string `json:"new-cert"`
|
||||
Revoke string `json:"revoke-cert"`
|
||||
RevokeRFC string `json:"revokeCert"`
|
||||
NonceRFC string `json:"newNonce"`
|
||||
KeyChangeRFC string `json:"keyChange"`
|
||||
Meta struct {
|
||||
Terms string `json:"terms-of-service"`
|
||||
TermsRFC string `json:"termsOfService"`
|
||||
WebsiteRFC string `json:"website"`
|
||||
CAA []string `json:"caa-identities"`
|
||||
CAARFC []string `json:"caaIdentities"`
|
||||
ExternalAcctRFC bool `json:"externalAccountRequired"`
|
||||
Reg string `json:"newAccount"`
|
||||
Authz string `json:"newAuthz"`
|
||||
Order string `json:"newOrder"`
|
||||
Revoke string `json:"revokeCert"`
|
||||
Nonce string `json:"newNonce"`
|
||||
KeyChange string `json:"keyChange"`
|
||||
Meta struct {
|
||||
Terms string `json:"termsOfService"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caaIdentities"`
|
||||
ExternalAcct bool `json:"externalAccountRequired"`
|
||||
}
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
if v.OrderRFC == "" {
|
||||
// Non-RFC compliant ACME CA.
|
||||
c.dir = &Directory{
|
||||
RegURL: v.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
CertURL: v.Cert,
|
||||
RevokeURL: v.Revoke,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.WebsiteRFC,
|
||||
CAA: v.Meta.CAA,
|
||||
}
|
||||
return *c.dir, nil
|
||||
if v.Order == "" {
|
||||
return Directory{}, errPreRFC
|
||||
}
|
||||
// RFC compliant ACME CA.
|
||||
c.dir = &Directory{
|
||||
RegURL: v.RegRFC,
|
||||
AuthzURL: v.AuthzRFC,
|
||||
OrderURL: v.OrderRFC,
|
||||
RevokeURL: v.RevokeRFC,
|
||||
NonceURL: v.NonceRFC,
|
||||
KeyChangeURL: v.KeyChangeRFC,
|
||||
Terms: v.Meta.TermsRFC,
|
||||
Website: v.Meta.WebsiteRFC,
|
||||
CAA: v.Meta.CAARFC,
|
||||
ExternalAccountRequired: v.Meta.ExternalAcctRFC,
|
||||
RegURL: v.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
OrderURL: v.Order,
|
||||
RevokeURL: v.Revoke,
|
||||
NonceURL: v.Nonce,
|
||||
KeyChangeURL: v.KeyChange,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
ExternalAccountRequired: v.Meta.ExternalAcct,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
@@ -234,55 +220,11 @@ func (c *Client) directoryURL() string {
|
||||
return LetsEncryptURL
|
||||
}
|
||||
|
||||
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
||||
// It is incompatible with RFC 8555. Callers should use CreateOrderCert when interfacing
|
||||
// with an RFC-compliant CA.
|
||||
// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
|
||||
//
|
||||
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
||||
// with a different duration.
|
||||
// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
|
||||
//
|
||||
// In the case where CA server does not provide the issued certificate in the response,
|
||||
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||
// In such a scenario, the caller can cancel the polling with ctx.
|
||||
//
|
||||
// CreateCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
|
||||
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}{
|
||||
Resource: "new-cert",
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
now := timeNow()
|
||||
req.NotBefore = now.Format(time.RFC3339)
|
||||
if exp > 0 {
|
||||
req.NotAfter = now.Add(exp).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, nil, c.dir.CertURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
curl := res.Header.Get("Location") // cert permanent URL
|
||||
if res.ContentLength == 0 {
|
||||
// no cert in the body; poll until we get it
|
||||
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
// slurp issued cert and CA chain, if requested
|
||||
cert, err := c.responseCert(ctx, res, bundle)
|
||||
return cert, curl, err
|
||||
return nil, "", errPreRFC
|
||||
}
|
||||
|
||||
// FetchCert retrieves already issued certificate from the given url, in DER format.
|
||||
@@ -296,20 +238,10 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid
|
||||
// and has expected features.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dir.rfcCompliant() {
|
||||
return c.fetchCertRFC(ctx, url, bundle)
|
||||
}
|
||||
|
||||
// Legacy non-authenticated GET request.
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.responseCert(ctx, res, bundle)
|
||||
return c.fetchCertRFC(ctx, url, bundle)
|
||||
}
|
||||
|
||||
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
|
||||
@@ -319,30 +251,10 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by
|
||||
// For instance, the key pair of the certificate may be authorized.
|
||||
// If the key is nil, c.Key is used instead.
|
||||
func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if dir.rfcCompliant() {
|
||||
return c.revokeCertRFC(ctx, key, cert, reason)
|
||||
}
|
||||
|
||||
// Legacy CA.
|
||||
body := &struct {
|
||||
Resource string `json:"resource"`
|
||||
Cert string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Resource: "revoke-cert",
|
||||
Cert: base64.RawURLEncoding.EncodeToString(cert),
|
||||
Reason: int(reason),
|
||||
}
|
||||
res, err := c.post(ctx, key, dir.RevokeURL, body, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
return c.revokeCertRFC(ctx, key, cert, reason)
|
||||
}
|
||||
|
||||
// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
|
||||
@@ -362,74 +274,50 @@ func AcceptTOS(tosURL string) bool { return true }
|
||||
// Also see Error's Instance field for when a CA requires already registered accounts to agree
|
||||
// to an updated Terms of Service.
|
||||
func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if c.Key == nil {
|
||||
return nil, errors.New("acme: client.Key must be set to Register")
|
||||
}
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dir.rfcCompliant() {
|
||||
return c.registerRFC(ctx, acct, prompt)
|
||||
}
|
||||
|
||||
// Legacy ACME draft registration flow.
|
||||
a, err := c.doReg(ctx, dir.RegURL, "new-reg", acct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accept bool
|
||||
if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms {
|
||||
accept = prompt(a.CurrentTerms)
|
||||
}
|
||||
if accept {
|
||||
a.AgreedTerms = a.CurrentTerms
|
||||
a, err = c.UpdateReg(ctx, a)
|
||||
}
|
||||
return a, err
|
||||
return c.registerRFC(ctx, acct, prompt)
|
||||
}
|
||||
|
||||
// GetReg retrieves an existing account associated with c.Key.
|
||||
//
|
||||
// The url argument is an Account URI used with pre-RFC 8555 CAs.
|
||||
// It is ignored when interfacing with an RFC-compliant CA.
|
||||
// The url argument is a legacy artifact of the pre-RFC 8555 API
|
||||
// and is ignored.
|
||||
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dir.rfcCompliant() {
|
||||
return c.getRegRFC(ctx)
|
||||
}
|
||||
|
||||
// Legacy CA.
|
||||
a, err := c.doReg(ctx, url, "reg", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = url
|
||||
return a, nil
|
||||
return c.getRegRFC(ctx)
|
||||
}
|
||||
|
||||
// UpdateReg updates an existing registration.
|
||||
// It returns an updated account copy. The provided account is not modified.
|
||||
//
|
||||
// When interfacing with RFC-compliant CAs, a.URI is ignored and the account URL
|
||||
// associated with c.Key is used instead.
|
||||
// The account's URI is ignored and the account URL associated with
|
||||
// c.Key is used instead.
|
||||
func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dir.rfcCompliant() {
|
||||
return c.updateRegRFC(ctx, acct)
|
||||
}
|
||||
return c.updateRegRFC(ctx, acct)
|
||||
}
|
||||
|
||||
// Legacy CA.
|
||||
uri := acct.URI
|
||||
a, err := c.doReg(ctx, uri, "reg", acct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = uri
|
||||
return a, nil
|
||||
// AccountKeyRollover attempts to transition a client's account key to a new key.
|
||||
// On success client's Key is updated which is not concurrency safe.
|
||||
// On failure an error will be returned.
|
||||
// The new key is already registered with the ACME provider if the following is true:
|
||||
// - error is of type acme.Error
|
||||
// - StatusCode should be 409 (Conflict)
|
||||
// - Location header will have the KID of the associated account
|
||||
//
|
||||
// More about account key rollover can be found at
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.3.5.
|
||||
func (c *Client) AccountKeyRollover(ctx context.Context, newKey crypto.Signer) error {
|
||||
return c.accountKeyRollover(ctx, newKey)
|
||||
}
|
||||
|
||||
// Authorize performs the initial step in the pre-authorization flow,
|
||||
@@ -498,17 +386,11 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
|
||||
// If a caller needs to poll an authorization until its status is final,
|
||||
// see the WaitAuthorization method.
|
||||
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res *http.Response
|
||||
if dir.rfcCompliant() {
|
||||
res, err = c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
} else {
|
||||
res, err = c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
}
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -530,7 +412,6 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati
|
||||
//
|
||||
// It does not revoke existing certificates.
|
||||
func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
// Required for c.accountKID() when in RFC mode.
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -560,18 +441,11 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
// In all other cases WaitAuthorization returns an error.
|
||||
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
|
||||
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
// Required for c.accountKID() when in RFC mode.
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getfn := c.postAsGet
|
||||
if !dir.rfcCompliant() {
|
||||
getfn = c.get
|
||||
}
|
||||
|
||||
for {
|
||||
res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -614,17 +488,11 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
|
||||
//
|
||||
// A client typically polls a challenge status using this method.
|
||||
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
|
||||
// Required for c.accountKID() when in RFC mode.
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getfn := c.postAsGet
|
||||
if !dir.rfcCompliant() {
|
||||
getfn = c.get
|
||||
}
|
||||
res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -642,29 +510,11 @@ func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, erro
|
||||
//
|
||||
// The server will then perform the validation asynchronously.
|
||||
func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
|
||||
// Required for c.accountKID() when in RFC mode.
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var req interface{} = json.RawMessage("{}") // RFC-compliant CA
|
||||
if !dir.rfcCompliant() {
|
||||
auth, err := keyAuth(c.Key.Public(), chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = struct {
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"type"`
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}{
|
||||
Resource: "challenge",
|
||||
Type: chal.Type,
|
||||
Auth: auth,
|
||||
}
|
||||
}
|
||||
res, err := c.post(ctx, nil, chal.URI, req, wantStatus(
|
||||
res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus(
|
||||
http.StatusOK, // according to the spec
|
||||
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||
))
|
||||
@@ -715,7 +565,7 @@ func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
|
||||
// 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 {
|
||||
@@ -733,7 +583,7 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
|
||||
// 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[:])
|
||||
@@ -778,7 +628,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: idPeACMEIdentifierV1,
|
||||
Id: idPeACMEIdentifier,
|
||||
Critical: true,
|
||||
Value: extValue,
|
||||
}
|
||||
@@ -800,63 +650,6 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests the old way (pre-RFC world).
|
||||
// The type of request is identified by typ argument, which is a "resource"
|
||||
// in the ACME spec terms.
|
||||
//
|
||||
// A non-nil acct argument indicates whether the intention is to mutate data
|
||||
// of the Account. Only Contact and Agreement of its fields are used
|
||||
// in such cases.
|
||||
func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Agreement string `json:"agreement,omitempty"`
|
||||
}{
|
||||
Resource: typ,
|
||||
}
|
||||
if acct != nil {
|
||||
req.Contact = acct.Contact
|
||||
req.Agreement = acct.AgreedTerms
|
||||
}
|
||||
res, err := c.post(ctx, nil, url, req, wantStatus(
|
||||
http.StatusOK, // updates and deletes
|
||||
http.StatusCreated, // new account creation
|
||||
http.StatusAccepted, // Let's Encrypt divergent implementation
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v struct {
|
||||
Contact []string
|
||||
Agreement string
|
||||
Authorizations string
|
||||
Certificates string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
var tos string
|
||||
if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 {
|
||||
tos = v[0]
|
||||
}
|
||||
var authz string
|
||||
if v := linkHeader(res.Header, "next"); len(v) > 0 {
|
||||
authz = v[0]
|
||||
}
|
||||
return &Account{
|
||||
URI: res.Header.Get("Location"),
|
||||
Contact: v.Contact,
|
||||
AgreedTerms: v.Agreement,
|
||||
CurrentTerms: tos,
|
||||
Authz: authz,
|
||||
Authorizations: v.Authorizations,
|
||||
Certificates: v.Certificates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
// or fetches a fresh one from c.dir.NonceURL.
|
||||
// If NonceURL is empty, it first tries c.directoryURL() and, failing that,
|
||||
@@ -931,78 +724,6 @@ func nonceFromHeader(h http.Header) string {
|
||||
return h.Get("Replay-Nonce")
|
||||
}
|
||||
|
||||
func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) {
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: response stream: %v", err)
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
cert := [][]byte{b}
|
||||
if !bundle {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Append CA chain cert(s).
|
||||
// At least one is required according to the spec:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1
|
||||
up := linkHeader(res.Header, "up")
|
||||
if len(up) == 0 {
|
||||
return nil, errors.New("acme: rel=up link not found")
|
||||
}
|
||||
if len(up) > maxChainLen {
|
||||
return nil, errors.New("acme: rel=up link is too large")
|
||||
}
|
||||
for _, url := range up {
|
||||
cc, err := c.chainCert(ctx, url, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert = append(cert, cc...)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// chainCert fetches CA certificate chain recursively by following "up" links.
|
||||
// Each recursive call increments the depth by 1, resulting in an error
|
||||
// if the recursion level reaches maxChainLen.
|
||||
//
|
||||
// First chainCert call starts with depth of 0.
|
||||
func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) {
|
||||
if depth >= maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too deep")
|
||||
}
|
||||
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
chain := [][]byte{b}
|
||||
|
||||
uplink := linkHeader(res.Header, "up")
|
||||
if len(uplink) > maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too large")
|
||||
}
|
||||
for _, up := range uplink {
|
||||
cc, err := c.chainCert(ctx, up, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chain = append(chain, cc...)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// linkHeader returns URI-Reference values of all Link headers
|
||||
// with relation-type rel.
|
||||
// See https://tools.ietf.org/html/rfc5988#section-5 for details.
|
||||
@@ -1093,5 +814,5 @@ func encodePEM(typ string, b []byte) []byte {
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is useful for testing for fixed current time.
|
||||
// timeNow is time.Now, except in tests which can mess with it.
|
||||
var timeNow = time.Now
|
||||
|
||||
+63
-640
@@ -79,240 +79,28 @@ func decodeJWSHead(r io.Reader) (*jwsHead, error) {
|
||||
return &head, nil
|
||||
}
|
||||
|
||||
func TestDiscover(t *testing.T) {
|
||||
const (
|
||||
reg = "https://example.com/acme/new-reg"
|
||||
authz = "https://example.com/acme/new-authz"
|
||||
cert = "https://example.com/acme/new-cert"
|
||||
revoke = "https://example.com/acme/revoke-cert"
|
||||
)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Replay-Nonce", "testnonce")
|
||||
fmt.Fprintf(w, `{
|
||||
"new-reg": %q,
|
||||
"new-authz": %q,
|
||||
"new-cert": %q,
|
||||
"revoke-cert": %q
|
||||
}`, reg, authz, cert, revoke)
|
||||
}))
|
||||
defer ts.Close()
|
||||
c := Client{DirectoryURL: ts.URL}
|
||||
dir, err := c.Discover(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dir.RegURL != reg {
|
||||
t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
|
||||
}
|
||||
if dir.AuthzURL != authz {
|
||||
t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
|
||||
}
|
||||
if dir.CertURL != cert {
|
||||
t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
|
||||
}
|
||||
if dir.RevokeURL != revoke {
|
||||
t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
|
||||
}
|
||||
if _, exist := c.nonces["testnonce"]; !exist {
|
||||
t.Errorf("c.nonces = %q; want 'testnonce' in the map", c.nonces)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
func TestRegisterWithoutKey(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-reg" {
|
||||
t.Errorf("j.Resource = %q; want new-reg", j.Resource)
|
||||
}
|
||||
if !reflect.DeepEqual(j.Contact, contacts) {
|
||||
t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "https://ca.tld/acme/reg/1")
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{"contact": %s}`, b)
|
||||
fmt.Fprint(w, `{}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
prompt := func(url string) bool {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
if url != terms {
|
||||
t.Errorf("prompt url = %q; want %q", url, terms)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// First verify that using a complete client results in success.
|
||||
c := Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: ts.URL,
|
||||
dir: &Directory{RegURL: ts.URL},
|
||||
}
|
||||
a := &Account{Contact: contacts}
|
||||
var err error
|
||||
if a, err = c.Register(context.Background(), a, prompt); err != nil {
|
||||
t.Fatal(err)
|
||||
if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err != nil {
|
||||
t.Fatalf("c.Register() = %v; want success with a complete test client", err)
|
||||
}
|
||||
if a.URI != "https://ca.tld/acme/reg/1" {
|
||||
t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.CurrentTerms != "https://ca.tld/acme/terms" {
|
||||
t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Contact, contacts) {
|
||||
t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReg(t *testing.T) {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "reg" {
|
||||
t.Errorf("j.Resource = %q; want reg", j.Resource)
|
||||
}
|
||||
if j.Agreement != terms {
|
||||
t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
|
||||
}
|
||||
if !reflect.DeepEqual(j.Contact, contacts) {
|
||||
t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
|
||||
}
|
||||
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c := Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: ts.URL, // don't dial outside of localhost
|
||||
dir: &Directory{}, // don't do discovery
|
||||
}
|
||||
a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
|
||||
var err error
|
||||
if a, err = c.UpdateReg(context.Background(), a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.AgreedTerms != terms {
|
||||
t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
|
||||
}
|
||||
if a.CurrentTerms != terms {
|
||||
t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
|
||||
}
|
||||
if a.URI != ts.URL {
|
||||
t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReg(t *testing.T) {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
const newTerms = "https://ca.tld/acme/new-terms"
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "reg" {
|
||||
t.Errorf("j.Resource = %q; want reg", j.Resource)
|
||||
}
|
||||
if len(j.Contact) != 0 {
|
||||
t.Errorf("j.Contact = %v", j.Contact)
|
||||
}
|
||||
if j.Agreement != "" {
|
||||
t.Errorf("j.Agreement = %q", j.Agreement)
|
||||
}
|
||||
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c := Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: ts.URL, // don't dial outside of localhost
|
||||
dir: &Directory{}, // don't do discovery
|
||||
}
|
||||
a, err := c.GetReg(context.Background(), ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.AgreedTerms != terms {
|
||||
t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
|
||||
}
|
||||
if a.CurrentTerms != newTerms {
|
||||
t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
|
||||
}
|
||||
if a.URI != ts.URL {
|
||||
t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
|
||||
c.Key = nil
|
||||
if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err == nil {
|
||||
t.Error("c.Register() from client without key succeeded, wanted error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,82 +254,6 @@ func TestAuthorizeValid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthorization(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("r.Method = %q; want GET", r.Method)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{
|
||||
"identifier": {"type":"dns","value":"example.com"},
|
||||
"status":"pending",
|
||||
"challenges":[
|
||||
{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1"
|
||||
},
|
||||
{
|
||||
"type":"tls-sni-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id2",
|
||||
"token":"token2"
|
||||
}
|
||||
],
|
||||
"combinations":[[0],[1]]}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
|
||||
auth, err := cl.GetAuthorization(context.Background(), ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if auth.Status != "pending" {
|
||||
t.Errorf("Status = %q; want pending", auth.Status)
|
||||
}
|
||||
if auth.Identifier.Type != "dns" {
|
||||
t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
|
||||
}
|
||||
if auth.Identifier.Value != "example.com" {
|
||||
t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
|
||||
}
|
||||
|
||||
if n := len(auth.Challenges); n != 2 {
|
||||
t.Fatalf("len(set.Challenges) = %d; want 2", n)
|
||||
}
|
||||
|
||||
c := auth.Challenges[0]
|
||||
if c.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Token)
|
||||
}
|
||||
|
||||
c = auth.Challenges[1]
|
||||
if c.Type != "tls-sni-01" {
|
||||
t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
|
||||
}
|
||||
if c.Token != "token2" {
|
||||
t.Errorf("c.Token = %q; want token2", c.Token)
|
||||
}
|
||||
|
||||
combs := [][]int{{0}, {1}}
|
||||
if !reflect.DeepEqual(auth.Combinations, combs) {
|
||||
t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitAuthorization(t *testing.T) {
|
||||
t.Run("wait loop", func(t *testing.T) {
|
||||
var count int
|
||||
@@ -569,6 +281,45 @@ func TestWaitAuthorization(t *testing.T) {
|
||||
t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
|
||||
}
|
||||
})
|
||||
t.Run("invalid status with error returns the authorization error", func(t *testing.T) {
|
||||
_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, `{
|
||||
"type": "dns-01",
|
||||
"status": "invalid",
|
||||
"error": {
|
||||
"type": "urn:ietf:params:acme:error:caa",
|
||||
"detail": "CAA record for <domain> prevents issuance",
|
||||
"status": 403
|
||||
},
|
||||
"url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx",
|
||||
"token": "xxx",
|
||||
"validationRecord": [
|
||||
{
|
||||
"hostname": "<domain>"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
})
|
||||
|
||||
want := &AuthorizationError{
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for <domain> prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
}
|
||||
|
||||
_, ok := err.(*AuthorizationError)
|
||||
if !ok {
|
||||
t.Errorf("err is %T; want non-nil *AuthorizationError", err)
|
||||
}
|
||||
|
||||
if err.Error() != want.Error() {
|
||||
t.Errorf("err is %v; want %v", err, want)
|
||||
}
|
||||
})
|
||||
t.Run("non-retriable error", func(t *testing.T) {
|
||||
const code = http.StatusBadRequest
|
||||
_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -603,38 +354,34 @@ func TestWaitAuthorization(t *testing.T) {
|
||||
})
|
||||
}
|
||||
t.Run("context cancel", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Retry-After", "60")
|
||||
fmt.Fprintf(w, `{"status":"pending"}`)
|
||||
time.AfterFunc(1*time.Millisecond, cancel)
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("err is nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) {
|
||||
t.Helper()
|
||||
ts := httptest.NewServer(h)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", fmt.Sprintf("bad-test-nonce-%v", time.Now().UnixNano()))
|
||||
h(w, r)
|
||||
}))
|
||||
defer ts.Close()
|
||||
type res struct {
|
||||
authz *Authorization
|
||||
err error
|
||||
|
||||
client := &Client{
|
||||
Key: testKey,
|
||||
DirectoryURL: ts.URL,
|
||||
dir: &Directory{},
|
||||
KID: "some-key-id", // set to avoid lookup attempt
|
||||
}
|
||||
ch := make(chan res, 1)
|
||||
go func() {
|
||||
var client = Client{DirectoryURL: ts.URL}
|
||||
a, err := client.WaitAuthorization(ctx, ts.URL)
|
||||
ch <- res{a, err}
|
||||
}()
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("WaitAuthorization took too long to return")
|
||||
case v := <-ch:
|
||||
return v.authz, v.err
|
||||
}
|
||||
panic("runWaitAuthorization: out of select")
|
||||
return client.WaitAuthorization(ctx, ts.URL)
|
||||
}
|
||||
|
||||
func TestRevokeAuthorization(t *testing.T) {
|
||||
@@ -679,238 +426,9 @@ func TestRevokeAuthorization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollChallenge(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("r.Method = %q; want GET", r.Method)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1"}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
|
||||
chall, err := cl.GetChallenge(context.Background(), ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if chall.Status != "pending" {
|
||||
t.Errorf("Status = %q; want pending", chall.Status)
|
||||
}
|
||||
if chall.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", chall.Type)
|
||||
}
|
||||
if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
|
||||
}
|
||||
if chall.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", chall.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptChallenge(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Type string
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "challenge" {
|
||||
t.Errorf(`resource = %q; want "challenge"`, j.Resource)
|
||||
}
|
||||
if j.Type != "http-01" {
|
||||
t.Errorf(`type = %q; want "http-01"`, j.Type)
|
||||
}
|
||||
keyAuth := "token1." + testKeyECThumbprint
|
||||
if j.Auth != keyAuth {
|
||||
t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
|
||||
}
|
||||
|
||||
// Respond to request
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1",
|
||||
"keyAuthorization":%q
|
||||
}`, keyAuth)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: ts.URL, // don't dial outside of localhost
|
||||
dir: &Directory{}, // don't do discovery
|
||||
}
|
||||
c, err := cl.Accept(context.Background(), &Challenge{
|
||||
URI: ts.URL,
|
||||
Token: "token1",
|
||||
Type: "http-01",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCert(t *testing.T) {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 2, 0)
|
||||
timeNow = func() time.Time { return notBefore }
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-cert" {
|
||||
t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
|
||||
}
|
||||
if j.NotBefore != notBefore.Format(time.RFC3339) {
|
||||
t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
|
||||
}
|
||||
if j.NotAfter != notAfter.Format(time.RFC3339) {
|
||||
t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// Respond to request
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(1)),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"goacme"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating certificate: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "https://ca.tld/acme/cert/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(sampleCert)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
csr := x509.CertificateRequest{
|
||||
Version: 0,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
Organization: []string{"goacme"},
|
||||
},
|
||||
}
|
||||
csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}}
|
||||
cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Errorf("cert is nil")
|
||||
}
|
||||
if certURL != "https://ca.tld/acme/cert/1" {
|
||||
t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCert(t *testing.T) {
|
||||
var count byte
|
||||
var ts *httptest.Server
|
||||
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
if count < 3 {
|
||||
up := fmt.Sprintf("<%s>;rel=up", ts.URL)
|
||||
w.Header().Set("Link", up)
|
||||
}
|
||||
w.Write([]byte{count})
|
||||
}))
|
||||
defer ts.Close()
|
||||
cl := newTestClient()
|
||||
res, err := cl.FetchCert(context.Background(), ts.URL, true)
|
||||
if err != nil {
|
||||
t.Fatalf("FetchCert: %v", err)
|
||||
}
|
||||
cert := [][]byte{{1}, {2}, {3}}
|
||||
if !reflect.DeepEqual(res, cert) {
|
||||
t.Errorf("res = %v; want %v", res, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCertRetry(t *testing.T) {
|
||||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if count < 1 {
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
count++
|
||||
return
|
||||
}
|
||||
w.Write([]byte{1})
|
||||
}))
|
||||
defer ts.Close()
|
||||
cl := newTestClient()
|
||||
res, err := cl.FetchCert(context.Background(), ts.URL, false)
|
||||
if err != nil {
|
||||
t.Fatalf("FetchCert: %v", err)
|
||||
}
|
||||
cert := [][]byte{{1}}
|
||||
if !reflect.DeepEqual(res, cert) {
|
||||
t.Errorf("res = %v; want %v", res, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCertCancel(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
<-r.Context().Done()
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}))
|
||||
@@ -979,42 +497,6 @@ func TestFetchCertSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeCert(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Resource string
|
||||
Certificate string
|
||||
Reason int
|
||||
}
|
||||
decodeJWSRequest(t, &req, r.Body)
|
||||
if req.Resource != "revoke-cert" {
|
||||
t.Errorf("req.Resource = %q; want revoke-cert", req.Resource)
|
||||
}
|
||||
if req.Reason != 1 {
|
||||
t.Errorf("req.Reason = %d; want 1", req.Reason)
|
||||
}
|
||||
// echo -n cert | base64 | tr -d '=' | tr '/+' '_-'
|
||||
cert := "Y2VydA"
|
||||
if req.Certificate != cert {
|
||||
t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
client := &Client{
|
||||
Key: testKeyEC,
|
||||
dir: &Directory{RevokeURL: ts.URL},
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonce_add(t *testing.T) {
|
||||
var c Client
|
||||
c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
|
||||
@@ -1135,65 +617,6 @@ func TestNonce_popWhenEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonce_postJWS(t *testing.T) {
|
||||
var count int
|
||||
seen := make(map[string]bool)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
|
||||
if r.Method == "HEAD" {
|
||||
// We expect the client do a HEAD request
|
||||
// but only to fetch the first nonce.
|
||||
return
|
||||
}
|
||||
// Make client.Authorize happy; we're not testing its result.
|
||||
defer func() {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"status":"valid"}`))
|
||||
}()
|
||||
|
||||
head, err := decodeJWSHead(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
return
|
||||
}
|
||||
if head.Nonce == "" {
|
||||
t.Error("head.Nonce is empty")
|
||||
return
|
||||
}
|
||||
if seen[head.Nonce] {
|
||||
t.Errorf("nonce is already used: %q", head.Nonce)
|
||||
}
|
||||
seen[head.Nonce] = true
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := Client{
|
||||
Key: testKey,
|
||||
DirectoryURL: ts.URL, // nonces are fetched from here first
|
||||
dir: &Directory{AuthzURL: ts.URL},
|
||||
}
|
||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||
t.Errorf("client.Authorize 1: %v", err)
|
||||
}
|
||||
// The second call should not generate another extra HEAD request.
|
||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||
t.Errorf("client.Authorize 2: %v", err)
|
||||
}
|
||||
|
||||
if count != 3 {
|
||||
t.Errorf("total requests count: %d; want 3", count)
|
||||
}
|
||||
if n := len(client.nonces); n != 1 {
|
||||
t.Errorf("len(client.nonces) = %d; want 1", n)
|
||||
}
|
||||
for k := range seen {
|
||||
if _, exist := client.nonces[k]; exist {
|
||||
t.Errorf("used nonce %q in client.nonces", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkHeader(t *testing.T) {
|
||||
h := http.Header{"Link": {
|
||||
`<https://example.com/acme/new-authz>;rel="next"`,
|
||||
@@ -1317,7 +740,7 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
}
|
||||
acmeExts := []pkix.Extension{}
|
||||
for _, ext := range cert.Extensions {
|
||||
if idPeACMEIdentifierV1.Equal(ext.Id) {
|
||||
if idPeACMEIdentifier.Equal(ext.Id) {
|
||||
acmeExts = append(acmeExts, ext)
|
||||
}
|
||||
}
|
||||
|
||||
+46
-97
@@ -47,6 +47,8 @@ var createCertRetryAfter = time.Minute
|
||||
// pseudoRand is safe for concurrent use.
|
||||
var pseudoRand *lockedMathRand
|
||||
|
||||
var errPreRFC = errors.New("autocert: ACME server doesn't support RFC 8555")
|
||||
|
||||
func init() {
|
||||
src := mathrand.NewSource(time.Now().UnixNano())
|
||||
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
||||
@@ -168,6 +170,11 @@ type Manager struct {
|
||||
// in the template's ExtraExtensions field as is.
|
||||
ExtraExtensions []pkix.Extension
|
||||
|
||||
// ExternalAccountBinding optionally represents an arbitrary binding to an
|
||||
// account of the CA to which the ACME server is tied.
|
||||
// See RFC 8555, Section 7.3.4 for more details.
|
||||
ExternalAccountBinding *acme.ExternalAccountBinding
|
||||
|
||||
clientMu sync.Mutex
|
||||
client *acme.Client // initialized by acmeClient method
|
||||
|
||||
@@ -456,7 +463,7 @@ func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error
|
||||
leaf: cert.Leaf,
|
||||
}
|
||||
m.state[ck] = s
|
||||
go m.renew(ck, s.key, s.leaf.NotAfter)
|
||||
m.startRenew(ck, s.key, s.leaf.NotAfter)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
@@ -582,8 +589,9 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
|
||||
if err != nil {
|
||||
// Remove the failed state after some time,
|
||||
// making the manager call createCert again on the following TLS hello.
|
||||
didRemove := testDidRemoveState // The lifetime of this timer is untracked, so copy mutable local state to avoid races.
|
||||
time.AfterFunc(createCertRetryAfter, func() {
|
||||
defer testDidRemoveState(ck)
|
||||
defer didRemove(ck)
|
||||
m.stateMu.Lock()
|
||||
defer m.stateMu.Unlock()
|
||||
// Verify the state hasn't changed and it's still invalid
|
||||
@@ -601,7 +609,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
|
||||
}
|
||||
state.cert = der
|
||||
state.leaf = leaf
|
||||
go m.renew(ck, state.key, state.leaf.NotAfter)
|
||||
m.startRenew(ck, state.key, state.leaf.NotAfter)
|
||||
return state.tlscert()
|
||||
}
|
||||
|
||||
@@ -658,31 +666,19 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck cert
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var chain [][]byte
|
||||
switch {
|
||||
// Pre-RFC legacy CA.
|
||||
case dir.OrderURL == "":
|
||||
if err := m.verify(ctx, client, ck.domain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
der, _, err := client.CreateCert(ctx, csr, 0, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
chain = der
|
||||
// RFC 8555 compliant CA.
|
||||
default:
|
||||
o, err := m.verifyRFC(ctx, client, ck.domain)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
der, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
chain = der
|
||||
if dir.OrderURL == "" {
|
||||
return nil, nil, errPreRFC
|
||||
}
|
||||
|
||||
o, err := m.verifyRFC(ctx, client, ck.domain)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
leaf, err = validCert(ck, chain, key, m.now())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -690,69 +686,6 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck cert
|
||||
return chain, leaf, nil
|
||||
}
|
||||
|
||||
// verify runs the identifier (domain) pre-authorization flow for legacy CAs
|
||||
// using each applicable ACME challenge type.
|
||||
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
|
||||
// Remove all hanging authorizations to reduce rate limit quotas
|
||||
// after we're done.
|
||||
var authzURLs []string
|
||||
defer func() {
|
||||
go m.deactivatePendingAuthz(authzURLs)
|
||||
}()
|
||||
|
||||
// errs accumulates challenge failure errors, printed if all fail
|
||||
errs := make(map[*acme.Challenge]error)
|
||||
challengeTypes := m.supportedChallengeTypes()
|
||||
var nextTyp int // challengeType index of the next challenge type to try
|
||||
for {
|
||||
// Start domain authorization and get the challenge.
|
||||
authz, err := client.Authorize(ctx, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authzURLs = append(authzURLs, authz.URI)
|
||||
// No point in accepting challenges if the authorization status
|
||||
// is in a final state.
|
||||
switch authz.Status {
|
||||
case acme.StatusValid:
|
||||
return nil // already authorized
|
||||
case acme.StatusInvalid:
|
||||
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
|
||||
}
|
||||
|
||||
// Pick the next preferred challenge.
|
||||
var chal *acme.Challenge
|
||||
for chal == nil && nextTyp < len(challengeTypes) {
|
||||
chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges)
|
||||
nextTyp++
|
||||
}
|
||||
if chal == nil {
|
||||
errorMsg := fmt.Sprintf("acme/autocert: unable to authorize %q", domain)
|
||||
for chal, err := range errs {
|
||||
errorMsg += fmt.Sprintf("; challenge %q failed with error: %v", chal.Type, err)
|
||||
}
|
||||
return errors.New(errorMsg)
|
||||
}
|
||||
cleanup, err := m.fulfill(ctx, client, chal, domain)
|
||||
if err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
defer cleanup()
|
||||
if _, err := client.Accept(ctx, chal); err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
|
||||
// A challenge is fulfilled and accepted: wait for the CA to validate.
|
||||
if _, err := client.WaitAuthorization(ctx, authz.URI); err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// verifyRFC runs the identifier (domain) order-based authorization flow for RFC compliant CAs
|
||||
// using each applicable ACME challenge type.
|
||||
func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) {
|
||||
@@ -966,7 +899,7 @@ func httpTokenCacheKey(tokenPath string) string {
|
||||
return path.Base(tokenPath) + "+http-01"
|
||||
}
|
||||
|
||||
// renew starts a cert renewal timer loop, one per domain.
|
||||
// startRenew starts a cert renewal timer loop, one per domain.
|
||||
//
|
||||
// The loop is scheduled in two cases:
|
||||
// - a cert was fetched from cache for the first time (wasn't in m.state)
|
||||
@@ -974,7 +907,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) renew(ck certKey, key crypto.Signer, exp time.Time) {
|
||||
func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
|
||||
m.renewalMu.Lock()
|
||||
defer m.renewalMu.Unlock()
|
||||
if m.renewal[ck] != nil {
|
||||
@@ -1068,7 +1001,7 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
||||
if m.Email != "" {
|
||||
contact = []string{"mailto:" + m.Email}
|
||||
}
|
||||
a := &acme.Account{Contact: contact}
|
||||
a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding}
|
||||
_, err := client.Register(ctx, a, m.Prompt)
|
||||
if err == nil || isAccountAlreadyExist(err) {
|
||||
m.client = client
|
||||
@@ -1133,11 +1066,11 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// certRequest generates a CSR for the given common name cn and optional SANs.
|
||||
func certRequest(key crypto.Signer, cn string, ext []pkix.Extension, san ...string) ([]byte, error) {
|
||||
// certRequest generates a CSR for the given common name.
|
||||
func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) {
|
||||
req := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: cn},
|
||||
DNSNames: san,
|
||||
Subject: pkix.Name{CommonName: name},
|
||||
DNSNames: []string{name},
|
||||
ExtraExtensions: ext,
|
||||
}
|
||||
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
||||
@@ -1200,6 +1133,10 @@ func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf
|
||||
if err := leaf.VerifyHostname(ck.domain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// renew certificates revoked by Let's Encrypt in January 2022
|
||||
if isRevokedLetsEncrypt(leaf) {
|
||||
return nil, errors.New("acme/autocert: certificate was probably revoked by Let's Encrypt")
|
||||
}
|
||||
// ensure the leaf corresponds to the private key and matches the certKey type
|
||||
switch pub := leaf.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
@@ -1230,6 +1167,18 @@ func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf
|
||||
return leaf, nil
|
||||
}
|
||||
|
||||
// https://community.letsencrypt.org/t/2022-01-25-issue-with-tls-alpn-01-validation-method/170450
|
||||
var letsEncryptFixDeployTime = time.Date(2022, time.January, 26, 00, 48, 0, 0, time.UTC)
|
||||
|
||||
// isRevokedLetsEncrypt returns whether the certificate is likely to be part of
|
||||
// a batch of certificates revoked by Let's Encrypt in January 2022. This check
|
||||
// can be safely removed from May 2022.
|
||||
func isRevokedLetsEncrypt(cert *x509.Certificate) bool {
|
||||
O := cert.Issuer.Organization
|
||||
return len(O) == 1 && O[0] == "Let's Encrypt" &&
|
||||
cert.NotBefore.Before(letsEncryptFixDeployTime)
|
||||
}
|
||||
|
||||
type lockedMathRand struct {
|
||||
sync.Mutex
|
||||
rnd *mathrand.Rand
|
||||
|
||||
+395
-632
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ package autocert
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -41,14 +40,14 @@ type DirCache string
|
||||
|
||||
// Get reads a certificate data from the specified file name.
|
||||
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
||||
name = filepath.Join(string(d), name)
|
||||
name = filepath.Join(string(d), filepath.Clean("/"+name))
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
data, err = ioutil.ReadFile(name)
|
||||
data, err = os.ReadFile(name)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
@@ -82,7 +81,7 @@ func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
case <-ctx.Done():
|
||||
// Don't overwrite the file if the context was canceled.
|
||||
default:
|
||||
newName := filepath.Join(string(d), name)
|
||||
newName := filepath.Join(string(d), filepath.Clean("/"+name))
|
||||
err = os.Rename(tmp, newName)
|
||||
}
|
||||
}()
|
||||
@@ -96,7 +95,7 @@ func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
|
||||
// Delete removes the specified file name.
|
||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||
name = filepath.Join(string(d), name)
|
||||
name = filepath.Join(string(d), filepath.Clean("/"+name))
|
||||
var (
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
@@ -119,7 +118,7 @@ func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
||||
func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
|
||||
// TempFile uses 0600 permissions
|
||||
f, err := ioutil.TempFile(string(d), prefix)
|
||||
f, err := os.CreateTemp(string(d), prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
var _ Cache = DirCache("/")
|
||||
|
||||
func TestDirCache(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "autocert")
|
||||
dir, err := os.MkdirTemp("", "autocert")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ func ExampleManager() {
|
||||
m := &autocert.Manager{
|
||||
Cache: autocert.DirCache("secret-dir"),
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Email: "example@example.org",
|
||||
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
|
||||
}
|
||||
s := &http.Server{
|
||||
|
||||
@@ -8,27 +8,30 @@
|
||||
package acmetest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
@@ -36,56 +39,65 @@ import (
|
||||
|
||||
// CAServer is a simple test server which implements ACME spec bits needed for testing.
|
||||
type CAServer struct {
|
||||
URL string // server URL after it has been started
|
||||
Roots *x509.CertPool // CA root certificates; initialized in NewCAServer
|
||||
|
||||
rootKey crypto.Signer
|
||||
rootCert []byte // DER encoding
|
||||
rootTemplate *x509.Certificate
|
||||
|
||||
server *httptest.Server
|
||||
challengeTypes []string // supported challenge types
|
||||
domainsWhitelist []string // only these domains are valid for issuing, unless empty
|
||||
t *testing.T
|
||||
server *httptest.Server
|
||||
issuer pkix.Name
|
||||
challengeTypes []string
|
||||
url string
|
||||
roots *x509.CertPool
|
||||
eabRequired bool
|
||||
|
||||
mu sync.Mutex
|
||||
certCount int // number of issued certs
|
||||
domainAddr map[string]string // domain name to addr:port resolution
|
||||
authorizations map[string]*authorization // keyed by domain name
|
||||
orders []*order // index is used as order ID
|
||||
errors []error // encountered client errors
|
||||
certCount int // number of issued certs
|
||||
acctRegistered bool // set once an account has been registered
|
||||
domainAddr map[string]string // domain name to addr:port resolution
|
||||
domainGetCert map[string]getCertificateFunc // domain name to GetCertificate function
|
||||
domainHandler map[string]http.Handler // domain name to Handle function
|
||||
validAuthz map[string]*authorization // valid authz, keyed by domain name
|
||||
authorizations []*authorization // all authz, index is used as ID
|
||||
orders []*order // index is used as order ID
|
||||
errors []error // encountered client errors
|
||||
}
|
||||
|
||||
// NewCAServer creates a new ACME test server and starts serving requests.
|
||||
// The returned CAServer issues certs signed with the CA roots
|
||||
// available in the Roots field.
|
||||
//
|
||||
// The challengeTypes argument defines the supported ACME challenge types
|
||||
// sent to a client in a response for a domain authorization.
|
||||
// If domainsWhitelist is non-empty, the certs will be issued only for the specified
|
||||
// list of domains. Otherwise, any domain name is allowed.
|
||||
func NewCAServer(challengeTypes []string, domainsWhitelist []string) *CAServer {
|
||||
var whitelist []string
|
||||
for _, name := range domainsWhitelist {
|
||||
whitelist = append(whitelist, name)
|
||||
}
|
||||
sort.Strings(whitelist)
|
||||
ca := &CAServer{
|
||||
challengeTypes: challengeTypes,
|
||||
domainsWhitelist: whitelist,
|
||||
domainAddr: make(map[string]string),
|
||||
authorizations: make(map[string]*authorization),
|
||||
type getCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
|
||||
// NewCAServer creates a new ACME test server. The returned CAServer issues
|
||||
// certs signed with the CA roots available in the Roots field.
|
||||
func NewCAServer(t *testing.T) *CAServer {
|
||||
ca := &CAServer{t: t,
|
||||
challengeTypes: []string{"fake-01", "tls-alpn-01", "http-01"},
|
||||
domainAddr: make(map[string]string),
|
||||
domainGetCert: make(map[string]getCertificateFunc),
|
||||
domainHandler: make(map[string]http.Handler),
|
||||
validAuthz: make(map[string]*authorization),
|
||||
}
|
||||
|
||||
ca.server = httptest.NewUnstartedServer(http.HandlerFunc(ca.handle))
|
||||
|
||||
r, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rand.Int: %v", err))
|
||||
}
|
||||
ca.issuer = pkix.Name{
|
||||
Organization: []string{"Test Acme Co"},
|
||||
CommonName: "Root CA " + r.String(),
|
||||
}
|
||||
|
||||
return ca
|
||||
}
|
||||
|
||||
func (ca *CAServer) generateRoot() {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err))
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Test Acme Co"},
|
||||
CommonName: "Root CA",
|
||||
},
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: ca.issuer,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
@@ -100,40 +112,96 @@ func NewCAServer(challengeTypes []string, domainsWhitelist []string) *CAServer {
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("x509.ParseCertificate: %v", err))
|
||||
}
|
||||
ca.Roots = x509.NewCertPool()
|
||||
ca.Roots.AddCert(cert)
|
||||
ca.roots = x509.NewCertPool()
|
||||
ca.roots.AddCert(cert)
|
||||
ca.rootKey = key
|
||||
ca.rootCert = der
|
||||
ca.rootTemplate = tmpl
|
||||
}
|
||||
|
||||
ca.server = httptest.NewServer(http.HandlerFunc(ca.handle))
|
||||
ca.URL = ca.server.URL
|
||||
// IssuerName sets the name of the issuing CA.
|
||||
func (ca *CAServer) IssuerName(name pkix.Name) *CAServer {
|
||||
if ca.url != "" {
|
||||
panic("IssuerName must be called before Start")
|
||||
}
|
||||
ca.issuer = name
|
||||
return ca
|
||||
}
|
||||
|
||||
// Close shuts down the server and blocks until all outstanding
|
||||
// requests on this server have completed.
|
||||
func (ca *CAServer) Close() {
|
||||
ca.server.Close()
|
||||
// ChallengeTypes sets the supported challenge types.
|
||||
func (ca *CAServer) ChallengeTypes(types ...string) *CAServer {
|
||||
if ca.url != "" {
|
||||
panic("ChallengeTypes must be called before Start")
|
||||
}
|
||||
ca.challengeTypes = types
|
||||
return ca
|
||||
}
|
||||
|
||||
// URL returns the server address, after Start has been called.
|
||||
func (ca *CAServer) URL() string {
|
||||
if ca.url == "" {
|
||||
panic("URL called before Start")
|
||||
}
|
||||
return ca.url
|
||||
}
|
||||
|
||||
// Roots returns a pool cointaining the CA root.
|
||||
func (ca *CAServer) Roots() *x509.CertPool {
|
||||
if ca.url == "" {
|
||||
panic("Roots called before Start")
|
||||
}
|
||||
return ca.roots
|
||||
}
|
||||
|
||||
// ExternalAccountRequired makes an EAB JWS required for account registration.
|
||||
func (ca *CAServer) ExternalAccountRequired() *CAServer {
|
||||
if ca.url != "" {
|
||||
panic("ExternalAccountRequired must be called before Start")
|
||||
}
|
||||
ca.eabRequired = true
|
||||
return ca
|
||||
}
|
||||
|
||||
// Start starts serving requests. The server address becomes available in the
|
||||
// URL field.
|
||||
func (ca *CAServer) Start() *CAServer {
|
||||
if ca.url == "" {
|
||||
ca.generateRoot()
|
||||
ca.server.Start()
|
||||
ca.t.Cleanup(ca.server.Close)
|
||||
ca.url = ca.server.URL
|
||||
}
|
||||
return ca
|
||||
}
|
||||
|
||||
func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
|
||||
return ca.server.URL + fmt.Sprintf(format, arg...)
|
||||
}
|
||||
|
||||
func (ca *CAServer) addr(domain string) (string, error) {
|
||||
func (ca *CAServer) addr(domain string) (string, bool) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
addr, ok := ca.domainAddr[domain]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
|
||||
}
|
||||
return addr, nil
|
||||
return addr, ok
|
||||
}
|
||||
|
||||
func (ca *CAServer) getCert(domain string) (getCertificateFunc, bool) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
f, ok := ca.domainGetCert[domain]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func (ca *CAServer) getHandler(domain string) (http.Handler, bool) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
h, ok := ca.domainHandler[domain]
|
||||
return h, ok
|
||||
}
|
||||
|
||||
func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
|
||||
s := fmt.Sprintf(format, a...)
|
||||
log.Println(s)
|
||||
ca.t.Errorf(format, a...)
|
||||
http.Error(w, s, code)
|
||||
}
|
||||
|
||||
@@ -145,11 +213,33 @@ func (ca *CAServer) Resolve(domain, addr string) {
|
||||
ca.domainAddr[domain] = addr
|
||||
}
|
||||
|
||||
// ResolveGetCertificate redirects TLS connections for domain to f when
|
||||
// validating challenges for the domain authorization.
|
||||
func (ca *CAServer) ResolveGetCertificate(domain string, f getCertificateFunc) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.domainGetCert[domain] = f
|
||||
}
|
||||
|
||||
// ResolveHandler redirects HTTP requests for domain to f when
|
||||
// validating challenges for the domain authorization.
|
||||
func (ca *CAServer) ResolveHandler(domain string, h http.Handler) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.domainHandler[domain] = h
|
||||
}
|
||||
|
||||
type discovery struct {
|
||||
NewNonce string `json:"newNonce"`
|
||||
NewReg string `json:"newAccount"`
|
||||
NewOrder string `json:"newOrder"`
|
||||
NewAuthz string `json:"newAuthz"`
|
||||
NewNonce string `json:"newNonce"`
|
||||
NewAccount string `json:"newAccount"`
|
||||
NewOrder string `json:"newOrder"`
|
||||
NewAuthz string `json:"newAuthz"`
|
||||
|
||||
Meta discoveryMeta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type discoveryMeta struct {
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
@@ -163,6 +253,7 @@ type authorization struct {
|
||||
Challenges []challenge `json:"challenges"`
|
||||
|
||||
domain string
|
||||
id int
|
||||
}
|
||||
|
||||
type order struct {
|
||||
@@ -175,7 +266,7 @@ type order struct {
|
||||
}
|
||||
|
||||
func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL)
|
||||
ca.t.Logf("%s %s", r.Method, r.URL)
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
// TODO: Verify nonce header for all POST requests.
|
||||
|
||||
@@ -186,10 +277,12 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
// Discovery request.
|
||||
case r.URL.Path == "/":
|
||||
resp := &discovery{
|
||||
NewNonce: ca.serverURL("/new-nonce"),
|
||||
NewReg: ca.serverURL("/new-reg"),
|
||||
NewOrder: ca.serverURL("/new-order"),
|
||||
NewAuthz: ca.serverURL("/new-authz"),
|
||||
NewNonce: ca.serverURL("/new-nonce"),
|
||||
NewAccount: ca.serverURL("/new-account"),
|
||||
NewOrder: ca.serverURL("/new-order"),
|
||||
Meta: discoveryMeta{
|
||||
ExternalAccountRequired: ca.eabRequired,
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
panic(fmt.Sprintf("discovery response: %v", err))
|
||||
@@ -201,7 +294,29 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
|
||||
// Client key registration request.
|
||||
case r.URL.Path == "/new-reg":
|
||||
case r.URL.Path == "/new-account":
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
if ca.acctRegistered {
|
||||
ca.httpErrorf(w, http.StatusServiceUnavailable, "multiple accounts are not implemented")
|
||||
return
|
||||
}
|
||||
ca.acctRegistered = true
|
||||
|
||||
var req struct {
|
||||
ExternalAccountBinding json.RawMessage
|
||||
}
|
||||
|
||||
if err := decodePayload(&req, r.Body); err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if ca.eabRequired && len(req.ExternalAccountBinding) == 0 {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "registration failed: no JWS for EAB")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Check the user account key against a ca.accountKeys?
|
||||
w.Header().Set("Location", ca.serverURL("/accounts/1"))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
@@ -221,7 +336,7 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
o := &order{Status: acme.StatusPending}
|
||||
for _, id := range req.Identifiers {
|
||||
z := ca.authz(id.Value)
|
||||
o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%s", z.domain))
|
||||
o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%d", z.id))
|
||||
}
|
||||
orderID := len(ca.orders)
|
||||
ca.orders = append(ca.orders, o)
|
||||
@@ -244,49 +359,50 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Identifier authorization request.
|
||||
case r.URL.Path == "/new-authz":
|
||||
var req struct {
|
||||
Identifier struct{ Value string }
|
||||
}
|
||||
if err := decodePayload(&req, r.Body); err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
// Accept challenge requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/challenge/"):
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
typ, id := parts[len(parts)-2], parts[len(parts)-1]
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
z := ca.authz(req.Identifier.Value)
|
||||
w.Header().Set("Location", ca.serverURL("/authz/%s", z.domain))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(z); err != nil {
|
||||
panic(fmt.Sprintf("new authz response: %v", err))
|
||||
supported := false
|
||||
for _, suppTyp := range ca.challengeTypes {
|
||||
if suppTyp == typ {
|
||||
supported = true
|
||||
}
|
||||
}
|
||||
|
||||
// Accept tls-alpn-01 challenge type requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/challenge/tls-alpn-01/"):
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/challenge/tls-alpn-01/")
|
||||
ca.mu.Lock()
|
||||
_, exist := ca.authorizations[domain]
|
||||
a, err := ca.storedAuthz(id)
|
||||
ca.mu.Unlock()
|
||||
if !exist {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: no authz for %q", domain)
|
||||
if !supported {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ)
|
||||
return
|
||||
}
|
||||
go ca.validateChallenge("tls-alpn-01", domain)
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err)
|
||||
return
|
||||
}
|
||||
ca.validateChallenge(a, typ)
|
||||
w.Write([]byte("{}"))
|
||||
|
||||
// Get authorization status requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/authz/"):
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/authz/")
|
||||
var req struct{ Status string }
|
||||
decodePayload(&req, r.Body)
|
||||
deactivate := req.Status == "deactivated"
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz, ok := ca.authorizations[domain]
|
||||
if !ok {
|
||||
ca.httpErrorf(w, http.StatusNotFound, "no authz for %q", domain)
|
||||
authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/"))
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusNotFound, "%v", err)
|
||||
return
|
||||
}
|
||||
if deactivate {
|
||||
// Note we don't invalidate authorized orders as we should.
|
||||
authz.Status = "deactivated"
|
||||
ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
|
||||
ca.updatePendingOrders()
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(authz); err != nil {
|
||||
panic(fmt.Sprintf("get authz for %q response: %v", domain, err))
|
||||
panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err))
|
||||
}
|
||||
|
||||
// Certificate issuance request.
|
||||
@@ -314,15 +430,6 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
names := unique(append(csr.DNSNames, csr.Subject.CommonName))
|
||||
if err := ca.matchWhitelist(names); err != nil {
|
||||
ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
if err := ca.authorized(names); err != nil {
|
||||
ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
// Issue the certificate.
|
||||
der, err := ca.leafCert(csr)
|
||||
if err != nil {
|
||||
@@ -355,25 +462,6 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// matchWhitelist reports whether all dnsNames are whitelisted.
|
||||
// The whitelist is provided in NewCAServer.
|
||||
func (ca *CAServer) matchWhitelist(dnsNames []string) error {
|
||||
if len(ca.domainsWhitelist) == 0 {
|
||||
return nil
|
||||
}
|
||||
var nomatch []string
|
||||
for _, name := range dnsNames {
|
||||
i := sort.SearchStrings(ca.domainsWhitelist, name)
|
||||
if i == len(ca.domainsWhitelist) || ca.domainsWhitelist[i] != name {
|
||||
nomatch = append(nomatch, name)
|
||||
}
|
||||
}
|
||||
if len(nomatch) > 0 {
|
||||
return fmt.Errorf("matchWhitelist: some domains don't match: %q", nomatch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storedOrder retrieves a previously created order at index i.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) storedOrder(i string) (*order, error) {
|
||||
@@ -387,46 +475,50 @@ func (ca *CAServer) storedOrder(i string) (*order, error) {
|
||||
if idx > len(ca.orders)-1 {
|
||||
return nil, fmt.Errorf("storedOrder: no such order %d", idx)
|
||||
}
|
||||
|
||||
ca.updatePendingOrders()
|
||||
return ca.orders[idx], nil
|
||||
}
|
||||
|
||||
// authz returns an existing authorization for the identifier or creates a new one.
|
||||
// storedAuthz retrieves a previously created authz at index i.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) storedAuthz(i string) (*authorization, error) {
|
||||
idx, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storedAuthz: %v", err)
|
||||
}
|
||||
if idx < 0 {
|
||||
return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx)
|
||||
}
|
||||
if idx > len(ca.authorizations)-1 {
|
||||
return nil, fmt.Errorf("storedAuthz: no such authz %d", idx)
|
||||
}
|
||||
return ca.authorizations[idx], nil
|
||||
}
|
||||
|
||||
// authz returns an existing valid authorization for the identifier or creates a
|
||||
// new one. It requires ca.mu to be locked.
|
||||
func (ca *CAServer) authz(identifier string) *authorization {
|
||||
authz, ok := ca.authorizations[identifier]
|
||||
authz, ok := ca.validAuthz[identifier]
|
||||
if !ok {
|
||||
authzId := len(ca.authorizations)
|
||||
authz = &authorization{
|
||||
id: authzId,
|
||||
domain: identifier,
|
||||
Status: acme.StatusPending,
|
||||
}
|
||||
for _, typ := range ca.challengeTypes {
|
||||
authz.Challenges = append(authz.Challenges, challenge{
|
||||
Type: typ,
|
||||
URI: ca.serverURL("/challenge/%s/%s", typ, authz.domain),
|
||||
Token: challengeToken(authz.domain, typ),
|
||||
URI: ca.serverURL("/challenge/%s/%d", typ, authzId),
|
||||
Token: challengeToken(authz.domain, typ, authzId),
|
||||
})
|
||||
}
|
||||
ca.authorizations[authz.domain] = authz
|
||||
ca.authorizations = append(ca.authorizations, authz)
|
||||
}
|
||||
return authz
|
||||
}
|
||||
|
||||
// authorized reports whether all authorizations for dnsNames have been satisfied.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) authorized(dnsNames []string) error {
|
||||
var noauthz []string
|
||||
for _, name := range dnsNames {
|
||||
authz, ok := ca.authorizations[name]
|
||||
if !ok || authz.Status != acme.StatusValid {
|
||||
noauthz = append(noauthz, name)
|
||||
}
|
||||
}
|
||||
if len(noauthz) > 0 {
|
||||
return fmt.Errorf("CAServer: no authz for %q", noauthz)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// leafCert issues a new certificate.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) {
|
||||
@@ -447,79 +539,231 @@ func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err erro
|
||||
return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
|
||||
}
|
||||
|
||||
// TODO: Only tls-alpn-01 is currently supported: implement http-01 and dns-01.
|
||||
func (ca *CAServer) validateChallenge(typ, identifier string) {
|
||||
// LeafCert issues a leaf certificate.
|
||||
func (ca *CAServer) LeafCert(name, keyType string, notBefore, notAfter time.Time) *tls.Certificate {
|
||||
if ca.url == "" {
|
||||
panic("LeafCert called before Start")
|
||||
}
|
||||
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
var pk crypto.Signer
|
||||
switch keyType {
|
||||
case "RSA":
|
||||
var err error
|
||||
pk, err = rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
ca.t.Fatal(err)
|
||||
}
|
||||
case "ECDSA":
|
||||
var err error
|
||||
pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
ca.t.Fatal(err)
|
||||
}
|
||||
default:
|
||||
panic("LeafCert: unknown key type")
|
||||
}
|
||||
ca.certCount++ // next leaf cert serial number
|
||||
leaf := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(ca.certCount)),
|
||||
Subject: pkix.Name{Organization: []string{"Test Acme Co"}},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: []string{name},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, pk.Public(), ca.rootKey)
|
||||
if err != nil {
|
||||
ca.t.Fatal(err)
|
||||
}
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{der},
|
||||
PrivateKey: pk,
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CAServer) validateChallenge(authz *authorization, typ string) {
|
||||
var err error
|
||||
switch typ {
|
||||
case "tls-alpn-01":
|
||||
err = ca.verifyALPNChallenge(identifier)
|
||||
err = ca.verifyALPNChallenge(authz)
|
||||
case "http-01":
|
||||
err = ca.verifyHTTPChallenge(authz)
|
||||
default:
|
||||
panic(fmt.Sprintf("validation of %q is not implemented", typ))
|
||||
}
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz := ca.authorizations[identifier]
|
||||
if err != nil {
|
||||
authz.Status = "invalid"
|
||||
} else {
|
||||
authz.Status = "valid"
|
||||
ca.validAuthz[authz.domain] = authz
|
||||
}
|
||||
log.Printf("validated %q for %q; authz status is now: %s", typ, identifier, authz.Status)
|
||||
ca.t.Logf("validated %q for %q, err: %v", typ, authz.domain, err)
|
||||
ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
|
||||
|
||||
ca.updatePendingOrders()
|
||||
}
|
||||
|
||||
func (ca *CAServer) updatePendingOrders() {
|
||||
// Update all pending orders.
|
||||
// An order becomes "ready" if all authorizations are "valid".
|
||||
// An order becomes "invalid" if any authorization is "invalid".
|
||||
// Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6
|
||||
OrdersLoop:
|
||||
for i, o := range ca.orders {
|
||||
if o.Status != acme.StatusPending {
|
||||
continue
|
||||
}
|
||||
var countValid int
|
||||
for _, zurl := range o.AuthzURLs {
|
||||
z, ok := ca.authorizations[path.Base(zurl)]
|
||||
if !ok {
|
||||
log.Printf("no authz %q for order %d", zurl, i)
|
||||
continue OrdersLoop
|
||||
}
|
||||
if z.Status == acme.StatusInvalid {
|
||||
o.Status = acme.StatusInvalid
|
||||
log.Printf("order %d is now invalid", i)
|
||||
continue OrdersLoop
|
||||
}
|
||||
if z.Status == acme.StatusValid {
|
||||
countValid++
|
||||
}
|
||||
|
||||
countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i)
|
||||
if countInvalid > 0 {
|
||||
o.Status = acme.StatusInvalid
|
||||
ca.t.Logf("order %d is now invalid", i)
|
||||
continue
|
||||
}
|
||||
if countValid == len(o.AuthzURLs) {
|
||||
o.Status = acme.StatusReady
|
||||
o.FinalizeURL = ca.serverURL("/new-cert/%d", i)
|
||||
log.Printf("order %d is now ready", i)
|
||||
ca.t.Logf("order %d is now ready", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CAServer) verifyALPNChallenge(domain string) error {
|
||||
func (ca *CAServer) validateAuthzURLs(urls []string, orderNum int) (countValid, countInvalid int) {
|
||||
for _, zurl := range urls {
|
||||
z, err := ca.storedAuthz(path.Base(zurl))
|
||||
if err != nil {
|
||||
ca.t.Logf("no authz %q for order %d", zurl, orderNum)
|
||||
continue
|
||||
}
|
||||
if z.Status == acme.StatusInvalid {
|
||||
countInvalid++
|
||||
}
|
||||
if z.Status == acme.StatusValid {
|
||||
countValid++
|
||||
}
|
||||
}
|
||||
return countValid, countInvalid
|
||||
}
|
||||
|
||||
func (ca *CAServer) verifyALPNChallenge(a *authorization) error {
|
||||
const acmeALPNProto = "acme-tls/1"
|
||||
|
||||
addr, err := ca.addr(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
addr, haveAddr := ca.addr(a.domain)
|
||||
getCert, haveGetCert := ca.getCert(a.domain)
|
||||
if !haveAddr && !haveGetCert {
|
||||
return fmt.Errorf("no resolution information for %q", a.domain)
|
||||
}
|
||||
conn, err := tls.Dial("tcp", addr, &tls.Config{
|
||||
ServerName: domain,
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{acmeALPNProto},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
if haveAddr && haveGetCert {
|
||||
return fmt.Errorf("overlapping resolution information for %q", a.domain)
|
||||
}
|
||||
if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
|
||||
return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
|
||||
|
||||
var crt *x509.Certificate
|
||||
switch {
|
||||
case haveAddr:
|
||||
conn, err := tls.Dial("tcp", addr, &tls.Config{
|
||||
ServerName: a.domain,
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{acmeALPNProto},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
|
||||
return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
|
||||
}
|
||||
if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
|
||||
return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
|
||||
}
|
||||
crt = conn.ConnectionState().PeerCertificates[0]
|
||||
case haveGetCert:
|
||||
hello := &tls.ClientHelloInfo{
|
||||
ServerName: a.domain,
|
||||
// TODO: support selecting ECDSA.
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
||||
SupportedProtos: []string{acme.ALPNProto},
|
||||
SupportedVersions: []uint16{tls.VersionTLS12},
|
||||
}
|
||||
c, err := getCert(hello)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
crt, err = x509.ParseCertificate(c.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
|
||||
return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
|
||||
|
||||
if err := crt.VerifyHostname(a.domain); err != nil {
|
||||
return fmt.Errorf("verifyALPNChallenge: VerifyHostname: %v", err)
|
||||
}
|
||||
// See RFC 8737, Section 6.1.
|
||||
oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
for _, x := range crt.Extensions {
|
||||
if x.Id.Equal(oid) {
|
||||
// TODO: check the token.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("verifyTokenCert: no id-pe-acmeIdentifier extension found")
|
||||
}
|
||||
|
||||
func (ca *CAServer) verifyHTTPChallenge(a *authorization) error {
|
||||
addr, haveAddr := ca.addr(a.domain)
|
||||
handler, haveHandler := ca.getHandler(a.domain)
|
||||
if !haveAddr && !haveHandler {
|
||||
return fmt.Errorf("no resolution information for %q", a.domain)
|
||||
}
|
||||
if haveAddr && haveHandler {
|
||||
return fmt.Errorf("overlapping resolution information for %q", a.domain)
|
||||
}
|
||||
|
||||
token := challengeToken(a.domain, "http-01", a.id)
|
||||
path := "/.well-known/acme-challenge/" + token
|
||||
|
||||
var body string
|
||||
switch {
|
||||
case haveAddr:
|
||||
t := &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, network, addr)
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest("GET", "http://"+a.domain+path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := t.RoundTrip(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http token: w.Code = %d; want %d", res.StatusCode, http.StatusOK)
|
||||
}
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = string(b)
|
||||
case haveHandler:
|
||||
r := httptest.NewRequest("GET", path, nil)
|
||||
r.Host = a.domain
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
return fmt.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
body = w.Body.String()
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(body, token) {
|
||||
return fmt.Errorf("http token value = %q; want 'token-http-01.' prefix", body)
|
||||
}
|
||||
// TODO: verify conn.ConnectionState().PeerCertificates[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -535,8 +779,8 @@ func decodePayload(v interface{}, r io.Reader) error {
|
||||
return json.Unmarshal(payload, v)
|
||||
}
|
||||
|
||||
func challengeToken(domain, challType string) string {
|
||||
return fmt.Sprintf("token-%s-%s", domain, challType)
|
||||
func challengeToken(domain, challType string, authzID int) string {
|
||||
return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
|
||||
}
|
||||
|
||||
func unique(a []string) []string {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
//
|
||||
// It enables one-line HTTPS servers:
|
||||
//
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
//
|
||||
// NewListener is a convenience function for a common configuration.
|
||||
// More complex or custom configurations can use the autocert.Manager
|
||||
@@ -72,7 +72,6 @@ func NewListener(domains ...string) net.Listener {
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: m.TLSConfig(),
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
@@ -80,7 +79,6 @@ func (m *Manager) Listener() net.Listener {
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
m *Manager
|
||||
conf *tls.Config
|
||||
|
||||
tcpListener net.Listener
|
||||
|
||||
+25
-10
@@ -21,8 +21,9 @@ type domainRenewal struct {
|
||||
ck certKey
|
||||
key crypto.Signer
|
||||
|
||||
timerMu sync.Mutex
|
||||
timer *time.Timer
|
||||
timerMu sync.Mutex
|
||||
timer *time.Timer
|
||||
timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running
|
||||
}
|
||||
|
||||
// start starts a cert renewal timer at the time
|
||||
@@ -38,16 +39,28 @@ func (dr *domainRenewal) start(exp time.Time) {
|
||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
||||
}
|
||||
|
||||
// stop stops the cert renewal timer.
|
||||
// If the timer is already stopped, calling stop is a noop.
|
||||
// stop stops the cert renewal timer and waits for any in-flight calls to renew
|
||||
// to complete. If the timer is already stopped, calling stop is a noop.
|
||||
func (dr *domainRenewal) stop() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
return
|
||||
for {
|
||||
if dr.timer == nil {
|
||||
return
|
||||
}
|
||||
if dr.timer.Stop() {
|
||||
dr.timer = nil
|
||||
return
|
||||
} else {
|
||||
// dr.timer fired, and we acquired dr.timerMu before the renew callback did.
|
||||
// (We know this because otherwise the renew callback would have reset dr.timer!)
|
||||
timerClose := make(chan struct{})
|
||||
dr.timerClose = timerClose
|
||||
dr.timerMu.Unlock()
|
||||
<-timerClose
|
||||
dr.timerMu.Lock()
|
||||
}
|
||||
}
|
||||
dr.timer.Stop()
|
||||
dr.timer = nil
|
||||
}
|
||||
|
||||
// renew is called periodically by a timer.
|
||||
@@ -55,7 +68,9 @@ func (dr *domainRenewal) stop() {
|
||||
func (dr *domainRenewal) renew() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
if dr.timerClose != nil {
|
||||
close(dr.timerClose)
|
||||
dr.timer, dr.timerClose = nil, nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,8 +82,8 @@ func (dr *domainRenewal) renew() {
|
||||
next = renewJitter / 2
|
||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||
}
|
||||
dr.timer = time.AfterFunc(next, dr.renew)
|
||||
testDidRenewLoop(next, err)
|
||||
dr.timer = time.AfterFunc(next, dr.renew)
|
||||
}
|
||||
|
||||
// updateState locks and replaces the relevant Manager.state item with the given
|
||||
|
||||
+92
-155
@@ -6,19 +6,13 @@ package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert/internal/acmetest"
|
||||
)
|
||||
|
||||
func TestRenewalNext(t *testing.T) {
|
||||
@@ -48,101 +42,47 @@ func TestRenewalNext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenewFromCache(t *testing.T) {
|
||||
// ACME CA server stub
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
}
|
||||
man := testManager(t)
|
||||
man.RenewBefore = 24 * time.Hour
|
||||
|
||||
switch r.URL.Path {
|
||||
// discovery
|
||||
case "/":
|
||||
if err := discoTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Fatalf("discoTmpl: %v", err)
|
||||
}
|
||||
// client key registration
|
||||
case "/new-reg":
|
||||
w.Write([]byte("{}"))
|
||||
// domain authorization
|
||||
case "/new-authz":
|
||||
w.Header().Set("Location", ca.URL+"/authz/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
// authorization status request done by Manager's revokePendingAuthz.
|
||||
case "/authz/1":
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
// cert request
|
||||
case "/new-cert":
|
||||
var req struct {
|
||||
CSR string `json:"csr"`
|
||||
}
|
||||
decodePayload(&req, r.Body)
|
||||
b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
|
||||
csr, err := x509.ParseCertificateRequest(b)
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: CSR: %v", err)
|
||||
}
|
||||
der, err := dummyCert(csr.PublicKey, exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: dummyCert: %v", err)
|
||||
}
|
||||
chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
|
||||
w.Header().Set("Link", chainUp)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(der)
|
||||
// CA chain cert
|
||||
case "/ca-cert":
|
||||
der, err := dummyCert(nil, "ca")
|
||||
if err != nil {
|
||||
t.Fatalf("ca-cert: dummyCert: %v", err)
|
||||
}
|
||||
w.Write(der)
|
||||
default:
|
||||
t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer ca.Close()
|
||||
ca := acmetest.NewCAServer(t).Start()
|
||||
ca.ResolveGetCertificate(exampleDomain, man.GetCertificate)
|
||||
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Cache: newMemCache(t),
|
||||
RenewBefore: 24 * time.Hour,
|
||||
Client: &acme.Client{
|
||||
DirectoryURL: ca.URL,
|
||||
},
|
||||
man.Client = &acme.Client{
|
||||
DirectoryURL: ca.URL(),
|
||||
}
|
||||
defer man.stopRenew()
|
||||
|
||||
// cache an almost expired cert
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
|
||||
c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// veriy the renewal happened
|
||||
// verify the renewal happened
|
||||
defer func() {
|
||||
// Stop the timers that read and execute testDidRenewLoop before restoring it.
|
||||
// Otherwise the timer callback may race with the deferred write.
|
||||
man.stopRenew()
|
||||
testDidRenewLoop = func(next time.Duration, err error) {}
|
||||
}()
|
||||
done := make(chan struct{})
|
||||
renewed := make(chan bool, 1)
|
||||
testDidRenewLoop = func(next time.Duration, err error) {
|
||||
defer close(done)
|
||||
defer func() {
|
||||
select {
|
||||
case renewed <- true:
|
||||
default:
|
||||
// The renewal timer uses a random backoff. If the first renewal fails for
|
||||
// some reason, we could end up with multiple calls here before the test
|
||||
// stops the timer.
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("testDidRenewLoop: %v", err)
|
||||
}
|
||||
// Next should be about 90 days:
|
||||
// dummyCert creates 90days expiry + account for man.RenewBefore.
|
||||
// CaServer creates 90days expiry + account for man.RenewBefore.
|
||||
// Previous expiration was within 1 min.
|
||||
future := 88 * 24 * time.Hour
|
||||
if next < future {
|
||||
@@ -153,7 +93,8 @@ func TestRenewFromCache(t *testing.T) {
|
||||
after := time.Now().Add(future)
|
||||
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
t.Errorf("man.cacheGet: %v", err)
|
||||
return
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.After(after) {
|
||||
t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
|
||||
@@ -164,11 +105,13 @@ func TestRenewFromCache(t *testing.T) {
|
||||
defer man.stateMu.Unlock()
|
||||
s := man.state[exampleCertKey]
|
||||
if s == nil {
|
||||
t.Fatalf("m.state[%q] is nil", exampleCertKey)
|
||||
t.Errorf("m.state[%q] is nil", exampleCertKey)
|
||||
return
|
||||
}
|
||||
tlscert, err = s.tlscert()
|
||||
if err != nil {
|
||||
t.Fatalf("s.tlscert: %v", err)
|
||||
t.Errorf("s.tlscert: %v", err)
|
||||
return
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.After(after) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
|
||||
@@ -180,55 +123,34 @@ func TestRenewFromCache(t *testing.T) {
|
||||
if _, err := man.GetCertificate(hello); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait for renew loop
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("renew took too long to occur")
|
||||
case <-done:
|
||||
}
|
||||
<-renewed
|
||||
}
|
||||
|
||||
func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Cache: newMemCache(t),
|
||||
RenewBefore: 24 * time.Hour,
|
||||
Client: &acme.Client{
|
||||
DirectoryURL: "invalid",
|
||||
},
|
||||
ca := acmetest.NewCAServer(t).Start()
|
||||
man := testManager(t)
|
||||
man.RenewBefore = 24 * time.Hour
|
||||
man.Client = &acme.Client{
|
||||
DirectoryURL: "invalid",
|
||||
}
|
||||
defer man.stopRenew()
|
||||
|
||||
// cache a recently renewed cert with a different private key
|
||||
newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
|
||||
if err != nil {
|
||||
newCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Hour*24*90))
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, newCert); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey, now)
|
||||
newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// set internal state to an almost expired cert
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key, now)
|
||||
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -237,20 +159,32 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
man.state = make(map[certKey]*certState)
|
||||
}
|
||||
s := &certState{
|
||||
key: key,
|
||||
cert: [][]byte{oldCert},
|
||||
key: oldCert.PrivateKey.(crypto.Signer),
|
||||
cert: oldCert.Certificate,
|
||||
leaf: oldLeaf,
|
||||
}
|
||||
man.state[exampleCertKey] = s
|
||||
man.stateMu.Unlock()
|
||||
|
||||
// veriy the renewal accepted the newer cached cert
|
||||
// verify the renewal accepted the newer cached cert
|
||||
defer func() {
|
||||
// Stop the timers that read and execute testDidRenewLoop before restoring it.
|
||||
// Otherwise the timer callback may race with the deferred write.
|
||||
man.stopRenew()
|
||||
testDidRenewLoop = func(next time.Duration, err error) {}
|
||||
}()
|
||||
done := make(chan struct{})
|
||||
renewed := make(chan bool, 1)
|
||||
testDidRenewLoop = func(next time.Duration, err error) {
|
||||
defer close(done)
|
||||
defer func() {
|
||||
select {
|
||||
case renewed <- true:
|
||||
default:
|
||||
// The renewal timer uses a random backoff. If the first renewal fails for
|
||||
// some reason, we could end up with multiple calls here before the test
|
||||
// stops the timer.
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("testDidRenewLoop: %v", err)
|
||||
}
|
||||
@@ -264,7 +198,8 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
// ensure the cached cert was not modified
|
||||
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
t.Errorf("man.cacheGet: %v", err)
|
||||
return
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
|
||||
t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
|
||||
@@ -275,30 +210,22 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
defer man.stateMu.Unlock()
|
||||
s := man.state[exampleCertKey]
|
||||
if s == nil {
|
||||
t.Fatalf("m.state[%q] is nil", exampleCertKey)
|
||||
t.Errorf("m.state[%q] is nil", exampleCertKey)
|
||||
return
|
||||
}
|
||||
stateKey := s.key.Public().(*ecdsa.PublicKey)
|
||||
if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
|
||||
t.Fatalf("state key was not updated from cache x: %v y: %v; want x: %v y: %v", stateKey.X, stateKey.Y, newKey.X, newKey.Y)
|
||||
if !stateKey.Equal(newLeaf.PublicKey) {
|
||||
t.Error("state key was not updated from cache")
|
||||
return
|
||||
}
|
||||
tlscert, err = s.tlscert()
|
||||
if err != nil {
|
||||
t.Fatalf("s.tlscert: %v", err)
|
||||
t.Errorf("s.tlscert: %v", err)
|
||||
return
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
|
||||
}
|
||||
|
||||
// verify the private key is replaced in the renewal state
|
||||
r := man.renewal[exampleCertKey]
|
||||
if r == nil {
|
||||
t.Fatalf("m.renewal[%q] is nil", exampleCertKey)
|
||||
}
|
||||
renewalKey := r.key.Public().(*ecdsa.PublicKey)
|
||||
if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
|
||||
t.Fatalf("renewal private key was not updated from cache x: %v y: %v; want x: %v y: %v", renewalKey.X, renewalKey.Y, newKey.X, newKey.Y)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// assert the expiring cert is returned from state
|
||||
@@ -312,21 +239,31 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
}
|
||||
|
||||
// trigger renew
|
||||
go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
|
||||
man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter)
|
||||
<-renewed
|
||||
func() {
|
||||
man.renewalMu.Lock()
|
||||
defer man.renewalMu.Unlock()
|
||||
|
||||
// wait for renew loop
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("renew took too long to occur")
|
||||
case <-done:
|
||||
// assert the new cert is returned from state after renew
|
||||
hello := clientHelloInfo(exampleDomain, algECDSA)
|
||||
tlscert, err := man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
// verify the private key is replaced in the renewal state
|
||||
r := man.renewal[exampleCertKey]
|
||||
if r == nil {
|
||||
t.Errorf("m.renewal[%q] is nil", exampleCertKey)
|
||||
return
|
||||
}
|
||||
if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
|
||||
renewalKey := r.key.Public().(*ecdsa.PublicKey)
|
||||
if !renewalKey.Equal(newLeaf.PublicKey) {
|
||||
t.Error("renewal private key was not updated from cache")
|
||||
}
|
||||
}()
|
||||
|
||||
// assert the new cert is returned from state after renew
|
||||
hello = clientHelloInfo(exampleDomain, algECDSA)
|
||||
tlscert, err = man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !newLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -10,8 +10,9 @@ import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -155,7 +156,7 @@ func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Respons
|
||||
}
|
||||
}
|
||||
|
||||
// postAsGet is POST-as-GET, a replacement for GET in RFC8555
|
||||
// postAsGet is POST-as-GET, a replacement for GET in RFC 8555
|
||||
// as described in https://tools.ietf.org/html/rfc8555#section-6.3.
|
||||
// It makes a POST request in KID form with zero JWS payload.
|
||||
// See nopayload doc comments in jws.go.
|
||||
@@ -215,6 +216,9 @@ func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body i
|
||||
func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
|
||||
kid := noKeyID
|
||||
if key == nil {
|
||||
if c.Key == nil {
|
||||
return nil, nil, errors.New("acme: Client.Key must be populated to make POST requests")
|
||||
}
|
||||
key = c.Key
|
||||
kid = c.accountKID(ctx)
|
||||
}
|
||||
@@ -306,7 +310,7 @@ func isRetriable(code int) bool {
|
||||
func responseError(resp *http.Response) error {
|
||||
// don't care if ReadAll returns an error:
|
||||
// json.Unmarshal will fail in that case anyway
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
e := &wireError{Status: resp.StatusCode}
|
||||
if err := json.Unmarshal(b, e); err != nil {
|
||||
// this is not a regular error response:
|
||||
|
||||
+20
-5
@@ -7,7 +7,7 @@ package acme
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
@@ -54,7 +54,7 @@ func TestErrorResponse(t *testing.T) {
|
||||
res := &http.Response{
|
||||
StatusCode: 400,
|
||||
Status: "400 Bad Request",
|
||||
Body: ioutil.NopCloser(strings.NewReader(s)),
|
||||
Body: io.NopCloser(strings.NewReader(s)),
|
||||
Header: http.Header{"X-Foo": {"bar"}},
|
||||
}
|
||||
err := responseError(res)
|
||||
@@ -115,8 +115,8 @@ func TestPostWithRetries(t *testing.T) {
|
||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||
t.Errorf("client.Authorize 1: %v", err)
|
||||
}
|
||||
if count != 4 {
|
||||
t.Errorf("total requests count: %d; want 4", count)
|
||||
if count != 3 {
|
||||
t.Errorf("total requests count: %d; want 3", count)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ func TestUserAgent(t *testing.T) {
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{}`))
|
||||
w.Write([]byte(`{"newOrder": "sure"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
@@ -238,3 +238,18 @@ func TestUserAgent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountKidLoop(t *testing.T) {
|
||||
// if Client.postNoRetry is called with a nil key argument
|
||||
// then Client.Key must be set, otherwise we fall into an
|
||||
// infinite loop (which also causes a deadlock).
|
||||
client := &Client{dir: &Directory{OrderURL: ":)"}}
|
||||
_, _, err := client.postNoRetry(context.Background(), nil, "", nil)
|
||||
if err == nil {
|
||||
t.Fatal("Client.postNoRetry didn't fail with a nil key")
|
||||
}
|
||||
expected := "acme: Client.Key must be populated to make POST requests"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("Unexpected error returned: wanted %q, got %q", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
//
|
||||
// A usage example:
|
||||
//
|
||||
// go run prober.go \
|
||||
// -d https://acme-staging-v02.api.letsencrypt.org/directory \
|
||||
// -f order \
|
||||
// -t http-01 \
|
||||
// -a :8080 \
|
||||
// -domain some.example.org
|
||||
// go run prober.go \
|
||||
// -d https://acme-staging-v02.api.letsencrypt.org/directory \
|
||||
// -f order \
|
||||
// -t http-01 \
|
||||
// -a :8080 \
|
||||
// -domain some.example.org
|
||||
//
|
||||
// The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080
|
||||
// in order for the test to be able to fulfill http-01 challenge.
|
||||
@@ -50,14 +50,13 @@ import (
|
||||
|
||||
var (
|
||||
// ACME CA directory URL.
|
||||
// Let's Encrypt v1 prod: https://acme-v01.api.letsencrypt.org/directory
|
||||
// Let's Encrypt v2 prod: https://acme-v02.api.letsencrypt.org/directory
|
||||
// Let's Encrypt v2 staging: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
// See the following for more CAs implementing ACME protocol:
|
||||
// https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment#CAs_&_PKIs_that_offer_ACME_certificates
|
||||
directory = flag.String("d", "", "ACME directory URL.")
|
||||
reginfo = flag.String("r", "", "ACME account registration info.")
|
||||
flow = flag.String("f", "", "Flow to run: order, preauthz (RFC8555) or preauthz02 (draft-02).")
|
||||
flow = flag.String("f", "", `Flow to run: "order" or "preauthz" (RFC8555).`)
|
||||
chaltyp = flag.String("t", "", "Challenge type: tls-alpn-01, http-01 or dns-01.")
|
||||
addr = flag.String("a", "", "Local server address for tls-alpn-01 and http-01.")
|
||||
dnsscript = flag.String("s", "", "Script to run for provisioning dns-01 challenges.")
|
||||
@@ -127,8 +126,6 @@ When running with dns-01 challenge type, use -s argument instead of -a.
|
||||
p.runOrder(ctx, identifiers)
|
||||
case "preauthz":
|
||||
p.runPreauthz(ctx, identifiers)
|
||||
case "preauthz02":
|
||||
p.runPreauthzLegacy(ctx, identifiers)
|
||||
default:
|
||||
log.Fatalf("unknown flow: %q", *flow)
|
||||
}
|
||||
@@ -276,50 +273,6 @@ func (p *prober) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prober) runPreauthzLegacy(ctx context.Context, identifiers []acme.AuthzID) {
|
||||
var zurls []string
|
||||
for _, id := range identifiers {
|
||||
z, err := authorize(ctx, p.client, id)
|
||||
if err != nil {
|
||||
log.Fatalf("AuthorizeID(%+v): %v", id, err)
|
||||
}
|
||||
if z.Status == acme.StatusValid {
|
||||
log.Printf("authz %s is valid; skipping", z.URI)
|
||||
continue
|
||||
}
|
||||
if err := p.fulfill(ctx, z); err != nil {
|
||||
log.Fatalf("fulfill(%s): %v", z.URI, err)
|
||||
}
|
||||
zurls = append(zurls, z.URI)
|
||||
log.Printf("authorized for %+v", id)
|
||||
}
|
||||
|
||||
// We should be all set now.
|
||||
log.Print("all authorizations are done")
|
||||
csr, certkey := newCSR(identifiers)
|
||||
der, curl, err := p.client.CreateCert(ctx, csr, 48*time.Hour, true)
|
||||
if err != nil {
|
||||
log.Fatalf("CreateCert: %v", err)
|
||||
}
|
||||
log.Printf("cert URL: %s", curl)
|
||||
if err := checkCert(der, identifiers); err != nil {
|
||||
p.errorf("invalid cert: %v", err)
|
||||
}
|
||||
|
||||
// Deactivate all authorizations we satisfied earlier.
|
||||
for _, v := range zurls {
|
||||
if err := p.client.RevokeAuthorization(ctx, v); err != nil {
|
||||
p.errorf("RevokAuthorization(%q): %v", v, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Try revoking the issued cert using its private key.
|
||||
if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
|
||||
p.errorf("RevokeCert: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *prober) fulfill(ctx context.Context, z *acme.Authorization) error {
|
||||
var chal *acme.Challenge
|
||||
for i, c := range z.Challenges {
|
||||
|
||||
+87
-17
@@ -7,6 +7,7 @@ package acme
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
@@ -14,16 +15,17 @@ import (
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// keyID is the account identity provided by a CA during registration.
|
||||
type keyID string
|
||||
// KeyID is the account key identity provided by a CA during registration.
|
||||
type KeyID string
|
||||
|
||||
// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noKeyID = keyID("")
|
||||
const noKeyID = KeyID("")
|
||||
|
||||
// noPayload indicates jwsEncodeJSON will encode zero-length octet string
|
||||
// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
|
||||
@@ -31,34 +33,70 @@ const noKeyID = keyID("")
|
||||
// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
|
||||
const noPayload = ""
|
||||
|
||||
// noNonce indicates that the nonce should be omitted from the protected header.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noNonce = ""
|
||||
|
||||
// jsonWebSignature can be easily serialized into a JWS following
|
||||
// https://tools.ietf.org/html/rfc7515#section-3.2.
|
||||
type jsonWebSignature struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format containing either kid or jwk
|
||||
// fields based on the provided keyID value.
|
||||
// fields based on the provided KeyID value.
|
||||
//
|
||||
// If kid is non-empty, its quoted value is inserted in the protected head
|
||||
// The claimset is marshalled using json.Marshal unless it is a string.
|
||||
// In which case it is inserted directly into the message.
|
||||
//
|
||||
// If kid is non-empty, its quoted value is inserted in the protected header
|
||||
// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
|
||||
// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
|
||||
//
|
||||
// If nonce is non-empty, its quoted value is inserted in the protected header.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
|
||||
if key == nil {
|
||||
return nil, errors.New("nil key")
|
||||
}
|
||||
alg, sha := jwsHasher(key.Public())
|
||||
if alg == "" || !sha.Available() {
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
var phead string
|
||||
headers := struct {
|
||||
Alg string `json:"alg"`
|
||||
KID string `json:"kid,omitempty"`
|
||||
JWK json.RawMessage `json:"jwk,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
URL string `json:"url"`
|
||||
}{
|
||||
Alg: alg,
|
||||
Nonce: nonce,
|
||||
URL: url,
|
||||
}
|
||||
switch kid {
|
||||
case noKeyID:
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
|
||||
headers.JWK = json.RawMessage(jwk)
|
||||
default:
|
||||
phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
|
||||
headers.KID = string(kid)
|
||||
}
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
phJSON, err := json.Marshal(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
|
||||
var payload string
|
||||
if claimset != noPayload {
|
||||
if val, ok := claimset.(string); ok {
|
||||
payload = val
|
||||
} else {
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -71,12 +109,7 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, ur
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
enc := jsonWebSignature{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
@@ -84,6 +117,43 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, ur
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwsWithMAC creates and signs a JWS using the given key and the HS256
|
||||
// algorithm. kid and url are included in the protected header. rawPayload
|
||||
// should not be base64-URL-encoded.
|
||||
func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
|
||||
if len(key) == 0 {
|
||||
return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
|
||||
}
|
||||
header := struct {
|
||||
Algorithm string `json:"alg"`
|
||||
KID string `json:"kid"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}{
|
||||
// Only HMAC-SHA256 is supported.
|
||||
Algorithm: "HS256",
|
||||
KID: kid,
|
||||
URL: url,
|
||||
}
|
||||
rawProtected, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protected := base64.RawURLEncoding.EncodeToString(rawProtected)
|
||||
payload := base64.RawURLEncoding.EncodeToString(rawPayload)
|
||||
|
||||
h := hmac.New(sha256.New, key)
|
||||
if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := h.Sum(nil)
|
||||
|
||||
return &jsonWebSignature{
|
||||
Protected: protected,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(mac),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
|
||||
+85
-4
@@ -195,8 +195,46 @@ func TestJWSEncodeJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSEncodeNoNonce(t *testing.T) {
|
||||
kid := KeyID("https://example.org/account/1")
|
||||
claims := "RawString"
|
||||
const (
|
||||
// {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
|
||||
protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYWNjb3VudC8xIiwidXJsIjoidXJsIn0"
|
||||
// "Raw String"
|
||||
payload = "RawString"
|
||||
)
|
||||
|
||||
b, err := jwsEncodeJSON(claims, testKeyEC, kid, "", "url")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var jws struct{ Protected, Payload, Signature string }
|
||||
if err := json.Unmarshal(b, &jws); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jws.Protected != protected {
|
||||
t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
|
||||
}
|
||||
if jws.Payload != payload {
|
||||
t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
|
||||
}
|
||||
|
||||
sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
|
||||
if err != nil {
|
||||
t.Fatalf("jws.Signature: %v", err)
|
||||
}
|
||||
r, s := big.NewInt(0), big.NewInt(0)
|
||||
r.SetBytes(sig[:len(sig)/2])
|
||||
s.SetBytes(sig[len(sig)/2:])
|
||||
h := sha256.Sum256([]byte(protected + "." + payload))
|
||||
if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) {
|
||||
t.Error("invalid signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSEncodeKID(t *testing.T) {
|
||||
kid := keyID("https://example.org/account/1")
|
||||
kid := KeyID("https://example.org/account/1")
|
||||
claims := struct{ Msg string }{"Hello JWS"}
|
||||
// JWS signed with testKeyEC
|
||||
const (
|
||||
@@ -376,7 +414,7 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var j struct{ Protected, Payload, Signature string }
|
||||
var j jsonWebSignature
|
||||
if err := json.Unmarshal(b, &j); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -386,13 +424,56 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||
if j.Payload != payload {
|
||||
t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
|
||||
}
|
||||
if j.Signature != tc.jwsig {
|
||||
t.Errorf("j.Signature = %q\nwant %q", j.Signature, tc.jwsig)
|
||||
if j.Sig != tc.jwsig {
|
||||
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSWithMAC(t *testing.T) {
|
||||
// Example from RFC 7520 Section 4.4.3.
|
||||
// https://tools.ietf.org/html/rfc7520#section-4.4.3
|
||||
b64Key := "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
|
||||
rawPayload := []byte("It\xe2\x80\x99s a dangerous business, Frodo, going out your " +
|
||||
"door. You step onto the road, and if you don't keep your feet, " +
|
||||
"there\xe2\x80\x99s no knowing where you might be swept off " +
|
||||
"to.")
|
||||
protected := "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" +
|
||||
"VlZjMxNGJjNzAzNyJ9"
|
||||
payload := "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg" +
|
||||
"Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h" +
|
||||
"ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi" +
|
||||
"gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m" +
|
||||
"ZiB0by4"
|
||||
sig := "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0"
|
||||
|
||||
key, err := base64.RawURLEncoding.DecodeString(b64Key)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode key: %q", b64Key)
|
||||
}
|
||||
got, err := jwsWithMAC(key, "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "", rawPayload)
|
||||
if err != nil {
|
||||
t.Fatalf("jwsWithMAC() = %q", err)
|
||||
}
|
||||
if got.Protected != protected {
|
||||
t.Errorf("got.Protected = %q\nwant %q", got.Protected, protected)
|
||||
}
|
||||
if got.Payload != payload {
|
||||
t.Errorf("got.Payload = %q\nwant %q", got.Payload, payload)
|
||||
}
|
||||
if got.Sig != sig {
|
||||
t.Errorf("got.Signature = %q\nwant %q", got.Sig, sig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSWithMACError(t *testing.T) {
|
||||
p := "{}"
|
||||
if _, err := jwsWithMAC(nil, "", "", []byte(p)); err == nil {
|
||||
t.Errorf("jwsWithMAC(nil, ...) = success; want err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWKThumbprintRSA(t *testing.T) {
|
||||
// Key example from RFC 7638
|
||||
const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
|
||||
|
||||
+93
-9
@@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -24,6 +23,9 @@ import (
|
||||
//
|
||||
// It only works with CAs implementing RFC 8555.
|
||||
func (c *Client) DeactivateReg(ctx context.Context) error {
|
||||
if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
|
||||
return err
|
||||
}
|
||||
url := string(c.accountKID(ctx))
|
||||
if url == "" {
|
||||
return ErrNoAccount
|
||||
@@ -37,22 +39,32 @@ func (c *Client) DeactivateReg(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerRFC is quivalent to c.Register but for CAs implementing RFC 8555.
|
||||
// registerRFC is equivalent to c.Register but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
// TODO: Implement externalAccountBinding.
|
||||
func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
c.cacheMu.Lock() // guard c.kid access
|
||||
defer c.cacheMu.Unlock()
|
||||
|
||||
req := struct {
|
||||
TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
ExternalAccountBinding *jsonWebSignature `json:"externalAccountBinding,omitempty"`
|
||||
}{
|
||||
Contact: acct.Contact,
|
||||
}
|
||||
if c.dir.Terms != "" {
|
||||
req.TermsAgreed = prompt(c.dir.Terms)
|
||||
}
|
||||
|
||||
// set 'externalAccountBinding' field if requested
|
||||
if acct.ExternalAccountBinding != nil {
|
||||
eabJWS, err := c.encodeExternalAccountBinding(acct.ExternalAccountBinding)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: failed to encode external account binding: %v", err)
|
||||
}
|
||||
req.ExternalAccountBinding = eabJWS
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(
|
||||
http.StatusOK, // account with this key already registered
|
||||
http.StatusCreated, // new account created
|
||||
@@ -68,14 +80,24 @@ func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tos
|
||||
}
|
||||
// Cache Account URL even if we return an error to the caller.
|
||||
// It is by all means a valid and usable "kid" value for future requests.
|
||||
c.kid = keyID(a.URI)
|
||||
c.KID = KeyID(a.URI)
|
||||
if res.StatusCode == http.StatusOK {
|
||||
return nil, ErrAccountAlreadyExists
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// updateGegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
|
||||
// encodeExternalAccountBinding will encode an external account binding stanza
|
||||
// as described in https://tools.ietf.org/html/rfc8555#section-7.3.4.
|
||||
func (c *Client) encodeExternalAccountBinding(eab *ExternalAccountBinding) (*jsonWebSignature, error) {
|
||||
jwk, err := jwkEncode(c.Key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jwsWithMAC(eab.Key, eab.KID, c.dir.RegURL, []byte(jwk))
|
||||
}
|
||||
|
||||
// updateRegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
|
||||
url := string(c.accountKID(ctx))
|
||||
@@ -95,7 +117,7 @@ func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error)
|
||||
return responseAccount(res)
|
||||
}
|
||||
|
||||
// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
|
||||
// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
|
||||
req := json.RawMessage(`{"onlyReturnExisting": true}`)
|
||||
@@ -128,6 +150,42 @@ func responseAccount(res *http.Response) (*Account, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// accountKeyRollover attempts to perform account key rollover.
|
||||
// On success it will change client.Key to the new key.
|
||||
func (c *Client) accountKeyRollover(ctx context.Context, newKey crypto.Signer) error {
|
||||
dir, err := c.Discover(ctx) // Also required by c.accountKID
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kid := c.accountKID(ctx)
|
||||
if kid == noKeyID {
|
||||
return ErrNoAccount
|
||||
}
|
||||
oldKey, err := jwkEncode(c.Key.Public())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := struct {
|
||||
Account string `json:"account"`
|
||||
OldKey json.RawMessage `json:"oldKey"`
|
||||
}{
|
||||
Account: string(kid),
|
||||
OldKey: json.RawMessage(oldKey),
|
||||
}
|
||||
inner, err := jwsEncodeJSON(payload, newKey, noKeyID, noNonce, dir.KeyChangeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, nil, dir.KeyChangeURL, base64.RawURLEncoding.EncodeToString(inner), wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
c.Key = newKey
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthorizeOrder initiates the order-based application for certificate issuance,
|
||||
// as opposed to pre-authorization in Authorize.
|
||||
// It is only supported by CAs implementing RFC 8555.
|
||||
@@ -331,7 +389,7 @@ func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][
|
||||
// Get all the bytes up to a sane maximum.
|
||||
// Account very roughly for base64 overhead.
|
||||
const max = maxCertChainSize + maxCertChainSize/33
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, max+1))
|
||||
b, err := io.ReadAll(io.LimitReader(res.Body, max+1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
|
||||
}
|
||||
@@ -390,3 +448,29 @@ func isAlreadyRevoked(err error) bool {
|
||||
e, ok := err.(*Error)
|
||||
return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked"
|
||||
}
|
||||
|
||||
// ListCertAlternates retrieves any alternate certificate chain URLs for the
|
||||
// given certificate chain URL. These alternate URLs can be passed to FetchCert
|
||||
// in order to retrieve the alternate certificate chains.
|
||||
//
|
||||
// If there are no alternate issuer certificate chains, a nil slice will be
|
||||
// returned.
|
||||
func (c *Client) ListCertAlternates(ctx context.Context, url string) ([]string, error) {
|
||||
if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// We don't need the body but we need to discard it so we don't end up
|
||||
// preventing keep-alive
|
||||
if _, err := io.Copy(io.Discard, res.Body); err != nil {
|
||||
return nil, fmt.Errorf("acme: cert alternates response stream: %v", err)
|
||||
}
|
||||
alts := linkHeader(res.Header, "alternate")
|
||||
return alts, nil
|
||||
}
|
||||
|
||||
+331
-57
@@ -7,17 +7,22 @@ package acme
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -59,7 +64,7 @@ func TestRFC_Discover(t *testing.T) {
|
||||
}`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
|
||||
}))
|
||||
defer ts.Close()
|
||||
c := Client{DirectoryURL: ts.URL}
|
||||
c := &Client{DirectoryURL: ts.URL}
|
||||
dir, err := c.Discover(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -143,7 +148,7 @@ func TestRFC_postKID(t *testing.T) {
|
||||
w.Header().Set("Location", "/account-1")
|
||||
w.Write([]byte(`{"status":"valid"}`))
|
||||
case "/post":
|
||||
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
head, err := decodeJWSHead(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
@@ -172,8 +177,7 @@ func TestRFC_postKID(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
ctx := context.Background()
|
||||
cl := &Client{
|
||||
Key: testKey,
|
||||
DirectoryURL: ts.URL,
|
||||
@@ -189,13 +193,13 @@ func TestRFC_postKID(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, _ := ioutil.ReadAll(res.Body) // don't care about err - just checking b
|
||||
b, _ := io.ReadAll(res.Body) // don't care about err - just checking b
|
||||
if string(b) != "pong" {
|
||||
t.Errorf("res.Body = %q; want pong", b)
|
||||
}
|
||||
}
|
||||
|
||||
// acmeServer simulates a subset of RFC8555 compliant CA.
|
||||
// acmeServer simulates a subset of RFC 8555 compliant CA.
|
||||
//
|
||||
// TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go.
|
||||
// It feels like this acmeServer is a sweet spot between usefulness and added complexity.
|
||||
@@ -229,6 +233,7 @@ func (s *acmeServer) start() {
|
||||
"newOrder": %q,
|
||||
"newAuthz": %q,
|
||||
"revokeCert": %q,
|
||||
"keyChange": %q,
|
||||
"meta": {"termsOfService": %q}
|
||||
}`,
|
||||
s.url("/acme/new-nonce"),
|
||||
@@ -236,6 +241,7 @@ func (s *acmeServer) start() {
|
||||
s.url("/acme/new-order"),
|
||||
s.url("/acme/new-authz"),
|
||||
s.url("/acme/revoke-cert"),
|
||||
s.url("/acme/key-change"),
|
||||
s.url("/terms"),
|
||||
)
|
||||
return
|
||||
@@ -290,7 +296,7 @@ func TestRFC_Register(t *testing.T) {
|
||||
"orders": %q
|
||||
}`, email, s.url("/accounts/1/orders"))
|
||||
|
||||
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
head, err := decodeJWSHead(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
@@ -309,8 +315,7 @@ func TestRFC_Register(t *testing.T) {
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
ctx := context.Background()
|
||||
cl := &Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: s.url("/"),
|
||||
@@ -341,7 +346,144 @@ func TestRFC_Register(t *testing.T) {
|
||||
if !didPrompt {
|
||||
t.Error("tos prompt wasn't called")
|
||||
}
|
||||
if v := cl.accountKID(ctx); v != keyID(okAccount.URI) {
|
||||
if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
|
||||
t.Errorf("account kid = %q; want %q", v, okAccount.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_RegisterExternalAccountBinding(t *testing.T) {
|
||||
eab := &ExternalAccountBinding{
|
||||
KID: "kid-1",
|
||||
Key: []byte("secret"),
|
||||
}
|
||||
|
||||
type protected struct {
|
||||
Algorithm string `json:"alg"`
|
||||
KID string `json:"kid"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
const email = "mailto:user@example.org"
|
||||
|
||||
s := newACMEServer()
|
||||
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", s.url("/accounts/1"))
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Protected string
|
||||
Contact []string
|
||||
TermsOfServiceAgreed bool
|
||||
ExternalaccountBinding struct {
|
||||
Protected string
|
||||
Payload string
|
||||
Signature string
|
||||
}
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
protData, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Protected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var prot protected
|
||||
err = json.Unmarshal(protData, &prot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(j.Contact, []string{email}) {
|
||||
t.Errorf("j.Contact = %v; want %v", j.Contact, []string{email})
|
||||
}
|
||||
if !j.TermsOfServiceAgreed {
|
||||
t.Error("j.TermsOfServiceAgreed = false; want true")
|
||||
}
|
||||
|
||||
// Ensure same KID.
|
||||
if prot.KID != eab.KID {
|
||||
t.Errorf("j.ExternalAccountBinding.KID = %s; want %s", prot.KID, eab.KID)
|
||||
}
|
||||
// Ensure expected Algorithm.
|
||||
if prot.Algorithm != "HS256" {
|
||||
t.Errorf("j.ExternalAccountBinding.Alg = %s; want %s",
|
||||
prot.Algorithm, "HS256")
|
||||
}
|
||||
|
||||
// Ensure same URL as outer JWS.
|
||||
url := fmt.Sprintf("http://%s/acme/new-account", r.Host)
|
||||
if prot.URL != url {
|
||||
t.Errorf("j.ExternalAccountBinding.URL = %s; want %s",
|
||||
prot.URL, url)
|
||||
}
|
||||
|
||||
// Ensure payload is base64URL encoded string of JWK in outer JWS
|
||||
jwk, err := jwkEncode(testKeyEC.Public())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
decodedPayload, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jwk != string(decodedPayload) {
|
||||
t.Errorf("j.ExternalAccountBinding.Payload = %s; want %s", decodedPayload, jwk)
|
||||
}
|
||||
|
||||
// Check signature on inner external account binding JWS
|
||||
hmac := hmac.New(sha256.New, []byte("secret"))
|
||||
_, err = hmac.Write([]byte(j.ExternalaccountBinding.Protected + "." + j.ExternalaccountBinding.Payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mac := hmac.Sum(nil)
|
||||
encodedMAC := base64.RawURLEncoding.EncodeToString(mac)
|
||||
|
||||
if !bytes.Equal([]byte(encodedMAC), []byte(j.ExternalaccountBinding.Signature)) {
|
||||
t.Errorf("j.ExternalAccountBinding.Signature = %v; want %v",
|
||||
[]byte(j.ExternalaccountBinding.Signature), encodedMAC)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", s.url("/accounts/1"))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
b, _ := json.Marshal([]string{email})
|
||||
fmt.Fprintf(w, `{"status":"valid","orders":"%s","contact":%s}`, s.url("/accounts/1/orders"), b)
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
ctx := context.Background()
|
||||
cl := &Client{
|
||||
Key: testKeyEC,
|
||||
DirectoryURL: s.url("/"),
|
||||
}
|
||||
|
||||
var didPrompt bool
|
||||
a := &Account{Contact: []string{email}, ExternalAccountBinding: eab}
|
||||
acct, err := cl.Register(ctx, a, func(tos string) bool {
|
||||
didPrompt = true
|
||||
terms := s.url("/terms")
|
||||
if tos != terms {
|
||||
t.Errorf("tos = %q; want %q", tos, terms)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
okAccount := &Account{
|
||||
URI: s.url("/accounts/1"),
|
||||
Status: StatusValid,
|
||||
Contact: []string{email},
|
||||
OrdersURL: s.url("/accounts/1/orders"),
|
||||
}
|
||||
if !reflect.DeepEqual(acct, okAccount) {
|
||||
t.Errorf("acct = %+v; want %+v", acct, okAccount)
|
||||
}
|
||||
if !didPrompt {
|
||||
t.Error("tos prompt wasn't called")
|
||||
}
|
||||
if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
|
||||
t.Errorf("account kid = %q; want %q", v, okAccount.URI)
|
||||
}
|
||||
}
|
||||
@@ -361,7 +503,7 @@ func TestRFC_RegisterExisting(t *testing.T) {
|
||||
if err != ErrAccountAlreadyExists {
|
||||
t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists)
|
||||
}
|
||||
kid := keyID(s.url("/accounts/1"))
|
||||
kid := KeyID(s.url("/accounts/1"))
|
||||
if v := cl.accountKID(context.Background()); v != kid {
|
||||
t.Errorf("account kid = %q; want %q", v, kid)
|
||||
}
|
||||
@@ -383,7 +525,7 @@ func TestRFC_UpdateReg(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
|
||||
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
head, err := decodeJWSHead(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
@@ -480,6 +622,128 @@ func TestRFC_GetRegOtherError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_AccountKeyRollover(t *testing.T) {
|
||||
s := newACMEServer()
|
||||
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", s.url("/accounts/1"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
})
|
||||
s.handle("/acme/key-change", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
if err := cl.AccountKeyRollover(context.Background(), testKeyEC384); err != nil {
|
||||
t.Errorf("AccountKeyRollover: %v, wanted no error", err)
|
||||
} else if cl.Key != testKeyEC384 {
|
||||
t.Error("AccountKeyRollover did not rotate the client key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_DeactivateReg(t *testing.T) {
|
||||
const email = "mailto:user@example.org"
|
||||
curStatus := StatusValid
|
||||
|
||||
type account struct {
|
||||
Status string `json:"status"`
|
||||
Contact []string `json:"contact"`
|
||||
AcceptTOS bool `json:"termsOfServiceAgreed"`
|
||||
Orders string `json:"orders"`
|
||||
}
|
||||
|
||||
s := newACMEServer()
|
||||
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", s.url("/accounts/1"))
|
||||
w.WriteHeader(http.StatusOK) // 200 means existing account
|
||||
json.NewEncoder(w).Encode(account{
|
||||
Status: curStatus,
|
||||
Contact: []string{email},
|
||||
AcceptTOS: true,
|
||||
Orders: s.url("/accounts/1/orders"),
|
||||
})
|
||||
|
||||
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
head, err := decodeJWSHead(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
return
|
||||
}
|
||||
if len(head.JWK) == 0 {
|
||||
t.Error("head.JWK is empty")
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
Contact []string `json:"contact"`
|
||||
AcceptTOS bool `json:"termsOfServiceAgreed"`
|
||||
OnlyExisting bool `json:"onlyReturnExisting"`
|
||||
}
|
||||
decodeJWSRequest(t, &req, bytes.NewReader(b))
|
||||
if !req.OnlyExisting {
|
||||
t.Errorf("req.OnlyReturnExisting = %t; want = %t", req.OnlyExisting, true)
|
||||
}
|
||||
})
|
||||
s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
if curStatus == StatusValid {
|
||||
curStatus = StatusDeactivated
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
s.error(w, &wireError{
|
||||
Status: http.StatusUnauthorized,
|
||||
Type: "urn:ietf:params:acme:error:unauthorized",
|
||||
})
|
||||
}
|
||||
var req account
|
||||
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
|
||||
head, err := decodeJWSHead(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
return
|
||||
}
|
||||
if len(head.JWK) != 0 {
|
||||
t.Error("head.JWK is not empty")
|
||||
}
|
||||
if !strings.HasSuffix(head.KID, "/accounts/1") {
|
||||
t.Errorf("head.KID = %q; want suffix /accounts/1", head.KID)
|
||||
}
|
||||
|
||||
decodeJWSRequest(t, &req, bytes.NewReader(b))
|
||||
if req.Status != StatusDeactivated {
|
||||
t.Errorf("req.Status = %q; want = %q", req.Status, StatusDeactivated)
|
||||
}
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
if err := cl.DeactivateReg(context.Background()); err != nil {
|
||||
t.Errorf("DeactivateReg: %v, wanted no error", err)
|
||||
}
|
||||
if err := cl.DeactivateReg(context.Background()); err == nil {
|
||||
t.Errorf("DeactivateReg: %v, wanted error for unauthorized", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRF_DeactivateRegNoAccount(t *testing.T) {
|
||||
s := newACMEServer()
|
||||
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.error(w, &wireError{
|
||||
Status: http.StatusBadRequest,
|
||||
Type: "urn:ietf:params:acme:error:accountDoesNotExist",
|
||||
})
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
if err := cl.DeactivateReg(context.Background()); !errors.Is(err, ErrNoAccount) {
|
||||
t.Errorf("DeactivateReg: %v, wanted ErrNoAccount", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_AuthorizeOrder(t *testing.T) {
|
||||
s := newACMEServer()
|
||||
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -600,24 +864,13 @@ func testWaitOrderStatus(t *testing.T, okStatus string) {
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
var order *Order
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
order, err = cl.WaitOrder(context.Background(), s.url("/orders/1"))
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("WaitOrder took too long to return")
|
||||
case <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("WaitOrder: %v", err)
|
||||
}
|
||||
if order.Status != okStatus {
|
||||
t.Errorf("order.Status = %q; want %q", order.Status, okStatus)
|
||||
}
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
order, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
|
||||
if err != nil {
|
||||
t.Fatalf("WaitOrder: %v", err)
|
||||
}
|
||||
if order.Status != okStatus {
|
||||
t.Errorf("order.Status = %q; want %q", order.Status, okStatus)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,30 +895,20 @@ func TestRFC_WaitOrderError(t *testing.T) {
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
_, err = cl.WaitOrder(context.Background(), s.url("/orders/1"))
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("WaitOrder took too long to return")
|
||||
case <-done:
|
||||
if err == nil {
|
||||
t.Fatal("WaitOrder returned nil error")
|
||||
}
|
||||
e, ok := err.(*OrderError)
|
||||
if !ok {
|
||||
t.Fatalf("err = %v (%T); want OrderError", err, err)
|
||||
}
|
||||
if e.OrderURL != s.url("/orders/1") {
|
||||
t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
|
||||
}
|
||||
if e.Status != StatusInvalid {
|
||||
t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
|
||||
}
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
_, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
|
||||
if err == nil {
|
||||
t.Fatal("WaitOrder returned nil error")
|
||||
}
|
||||
e, ok := err.(*OrderError)
|
||||
if !ok {
|
||||
t.Fatalf("err = %v (%T); want OrderError", err, err)
|
||||
}
|
||||
if e.OrderURL != s.url("/orders/1") {
|
||||
t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
|
||||
}
|
||||
if e.Status != StatusInvalid {
|
||||
t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,8 +948,7 @@ func TestRFC_CreateOrderCert(t *testing.T) {
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
ctx := context.Background()
|
||||
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
|
||||
@@ -741,3 +983,35 @@ func TestRFC_AlreadyRevokedCert(t *testing.T) {
|
||||
t.Fatalf("RevokeCert: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_ListCertAlternates(t *testing.T) {
|
||||
s := newACMEServer()
|
||||
s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/pem-certificate-chain")
|
||||
w.Header().Add("Link", `<https://example.com/crt/2>;rel="alternate"`)
|
||||
w.Header().Add("Link", `<https://example.com/crt/3>; rel="alternate"`)
|
||||
w.Header().Add("Link", `<https://example.com/acme>; rel="index"`)
|
||||
})
|
||||
s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/pem-certificate-chain")
|
||||
})
|
||||
s.start()
|
||||
defer s.close()
|
||||
|
||||
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
|
||||
crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt"))
|
||||
if err != nil {
|
||||
t.Fatalf("ListCertAlternates: %v", err)
|
||||
}
|
||||
want := []string{"https://example.com/crt/2", "https://example.com/crt/3"}
|
||||
if !reflect.DeepEqual(crts, want) {
|
||||
t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want)
|
||||
}
|
||||
crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2"))
|
||||
if err != nil {
|
||||
t.Fatalf("ListCertAlternates: %v", err)
|
||||
}
|
||||
if crts != nil {
|
||||
t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts)
|
||||
}
|
||||
}
|
||||
|
||||
+83
-17
@@ -57,6 +57,32 @@ var (
|
||||
ErrNoAccount = errors.New("acme: account does not exist")
|
||||
)
|
||||
|
||||
// A Subproblem describes an ACME subproblem as reported in an Error.
|
||||
type Subproblem struct {
|
||||
// Type is a URI reference that identifies the problem type,
|
||||
// typically in a "urn:acme:error:xxx" form.
|
||||
Type string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Instance indicates a URL that the client should direct a human user to visit
|
||||
// in order for instructions on how to agree to the updated Terms of Service.
|
||||
// In such an event CA sets StatusCode to 403, Type to
|
||||
// "urn:ietf:params:acme:error:userActionRequired", and adds a Link header with relation
|
||||
// "terms-of-service" containing the latest TOS URL.
|
||||
Instance string
|
||||
// Identifier may contain the ACME identifier that the error is for.
|
||||
Identifier *AuthzID
|
||||
}
|
||||
|
||||
func (sp Subproblem) String() string {
|
||||
str := fmt.Sprintf("%s: ", sp.Type)
|
||||
if sp.Identifier != nil {
|
||||
str += fmt.Sprintf("[%s: %s] ", sp.Identifier.Type, sp.Identifier.Value)
|
||||
}
|
||||
str += sp.Detail
|
||||
return str
|
||||
}
|
||||
|
||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||
type Error struct {
|
||||
@@ -76,10 +102,21 @@ type Error struct {
|
||||
// Header is the original server error response headers.
|
||||
// It may be nil.
|
||||
Header http.Header
|
||||
// Subproblems may contain more detailed information about the individual problems
|
||||
// that caused the error. This field is only sent by RFC 8555 compatible ACME
|
||||
// servers. Defined in RFC 8555 Section 6.7.1.
|
||||
Subproblems []Subproblem
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
if len(e.Subproblems) > 0 {
|
||||
str += fmt.Sprintf("; subproblems:")
|
||||
for _, sp := range e.Subproblems {
|
||||
str += fmt.Sprintf("\n\t%s", sp)
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// AuthorizationError indicates that an authorization for an identifier
|
||||
@@ -102,7 +139,12 @@ func (a *AuthorizationError) Error() string {
|
||||
for i, err := range a.Errors {
|
||||
e[i] = err.Error()
|
||||
}
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
|
||||
if a.Identifier != "" {
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
// OrderError is returned from Client's order related methods.
|
||||
@@ -194,6 +236,28 @@ type Account struct {
|
||||
//
|
||||
// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
|
||||
Certificates string
|
||||
|
||||
// ExternalAccountBinding represents an arbitrary binding to an account of
|
||||
// the CA which the ACME server is tied to.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
|
||||
ExternalAccountBinding *ExternalAccountBinding
|
||||
}
|
||||
|
||||
// ExternalAccountBinding contains the data needed to form a request with
|
||||
// an external account binding.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
|
||||
type ExternalAccountBinding struct {
|
||||
// KID is the Key ID of the symmetric MAC key that the CA provides to
|
||||
// identify an external account from ACME.
|
||||
KID string
|
||||
|
||||
// Key is the bytes of the symmetric key that the CA provides to identify
|
||||
// the account. Key must correspond to the KID.
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (e *ExternalAccountBinding) String() string {
|
||||
return fmt.Sprintf("&{KID: %q, Key: redacted}", e.KID)
|
||||
}
|
||||
|
||||
// Directory is ACME server discovery data.
|
||||
@@ -233,7 +297,7 @@ type Directory struct {
|
||||
|
||||
// CAA consists of lowercase hostname elements, which the ACME server
|
||||
// recognises as referring to itself for the purposes of CAA record validation
|
||||
// as defined in RFC6844.
|
||||
// as defined in RFC 6844.
|
||||
CAA []string
|
||||
|
||||
// ExternalAccountRequired indicates that the CA requires for all account-related
|
||||
@@ -241,14 +305,6 @@ type Directory struct {
|
||||
ExternalAccountRequired bool
|
||||
}
|
||||
|
||||
// rfcCompliant reports whether the ACME server implements RFC 8555.
|
||||
// Note that some servers may have incomplete RFC implementation
|
||||
// even if the returned value is true.
|
||||
// If rfcCompliant reports false, the server most likely implements draft-02.
|
||||
func (d *Directory) rfcCompliant() bool {
|
||||
return d.OrderURL != ""
|
||||
}
|
||||
|
||||
// Order represents a client's request for a certificate.
|
||||
// It tracks the request flow progress through to issuance.
|
||||
type Order struct {
|
||||
@@ -384,7 +440,7 @@ func DomainIDs(names ...string) []AuthzID {
|
||||
|
||||
// IPIDs creates a slice of AuthzID with "ip" identifier type.
|
||||
// Each element of addr is textual form of an address as defined
|
||||
// in RFC1123 Section 2.1 for IPv4 and in RFC5952 Section 4 for IPv6.
|
||||
// in RFC 1123 Section 2.1 for IPv4 and in RFC 5952 Section 4 for IPv6.
|
||||
func IPIDs(addr ...string) []AuthzID {
|
||||
a := make([]AuthzID, len(addr))
|
||||
for i, v := range addr {
|
||||
@@ -407,6 +463,7 @@ type wireAuthz struct {
|
||||
Wildcard bool
|
||||
Challenges []wireChallenge
|
||||
Combinations [][]int
|
||||
Error *wireError
|
||||
}
|
||||
|
||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
@@ -430,11 +487,17 @@ func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||
URI: uri,
|
||||
Identifier: z.Identifier.Value,
|
||||
}
|
||||
|
||||
if z.Error != nil {
|
||||
err.Errors = append(err.Errors, z.Error.error(nil))
|
||||
}
|
||||
|
||||
for _, raw := range z.Challenges {
|
||||
if raw.Error != nil {
|
||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -499,20 +562,23 @@ func (c *wireChallenge) challenge() *Challenge {
|
||||
// wireError is a subset of fields of the Problem Details object
|
||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
||||
type wireError struct {
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
Instance string
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
Instance string
|
||||
Subproblems []Subproblem
|
||||
}
|
||||
|
||||
func (e *wireError) error(h http.Header) *Error {
|
||||
return &Error{
|
||||
err := &Error{
|
||||
StatusCode: e.Status,
|
||||
ProblemType: e.Type,
|
||||
Detail: e.Detail,
|
||||
Instance: e.Instance,
|
||||
Header: h,
|
||||
Subproblems: e.Subproblems,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||
|
||||
@@ -7,10 +7,23 @@ package acme
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExternalAccountBindingString(t *testing.T) {
|
||||
eab := ExternalAccountBinding{
|
||||
KID: "kid",
|
||||
Key: []byte("key"),
|
||||
}
|
||||
got := eab.String()
|
||||
want := `&{KID: "kid", Key: redacted}`
|
||||
if got != want {
|
||||
t.Errorf("eab.String() = %q, want: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit(t *testing.T) {
|
||||
now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
|
||||
f := timeNow
|
||||
@@ -61,3 +74,146 @@ func TestRateLimit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationError(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
err *AuthorizationError
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
desc: "when auth error identifier is set",
|
||||
err: &AuthorizationError{
|
||||
Identifier: "domain.com",
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for domain.com prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
},
|
||||
msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
|
||||
},
|
||||
|
||||
{
|
||||
desc: "when auth error identifier is unset",
|
||||
err: &AuthorizationError{
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for domain.com prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
},
|
||||
msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.err.Error() != tt.msg {
|
||||
t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubproblems(t *testing.T) {
|
||||
tests := []struct {
|
||||
wire wireError
|
||||
expectedOut Error
|
||||
}{
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
},
|
||||
},
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
out := tc.wire.error(nil)
|
||||
if !reflect.DeepEqual(*out, tc.expectedOut) {
|
||||
t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorStringerWithSubproblems(t *testing.T) {
|
||||
err := Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem"
|
||||
if err.Error() != expectedStr {
|
||||
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.12
|
||||
//go:build go1.12
|
||||
|
||||
package acme
|
||||
|
||||
|
||||
+4
-6
@@ -11,8 +11,7 @@
|
||||
// If you aren't sure which function you need, use Argon2id (IDKey) and
|
||||
// the parameter recommendations for your scenario.
|
||||
//
|
||||
//
|
||||
// Argon2i
|
||||
// # Argon2i
|
||||
//
|
||||
// Argon2i (implemented by Key) is the side-channel resistant version of Argon2.
|
||||
// It uses data-independent memory access, which is preferred for password
|
||||
@@ -21,8 +20,7 @@
|
||||
// parameters (taken from [2]) for non-interactive operations are time=3 and to
|
||||
// use the maximum available memory.
|
||||
//
|
||||
//
|
||||
// Argon2id
|
||||
// # Argon2id
|
||||
//
|
||||
// Argon2id (implemented by IDKey) is a hybrid version of Argon2 combining
|
||||
// Argon2i and Argon2d. It uses data-independent memory access for the first
|
||||
@@ -59,7 +57,7 @@ const (
|
||||
// For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||
// 32-byte key) by doing:
|
||||
//
|
||||
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
||||
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number.
|
||||
// If using that amount of memory (32 MB) is not possible in some contexts then
|
||||
@@ -83,7 +81,7 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
|
||||
// For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||
// 32-byte key) by doing:
|
||||
//
|
||||
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
||||
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number.
|
||||
// If using that amount of memory (64 MB) is not possible in some contexts then
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -199,8 +199,8 @@ TEXT ·mixBlocksSSE2(SB), 4, $0-32
|
||||
MOVQ out+0(FP), DX
|
||||
MOVQ a+8(FP), AX
|
||||
MOVQ b+16(FP), BX
|
||||
MOVQ a+24(FP), CX
|
||||
MOVQ $128, BP
|
||||
MOVQ c+24(FP), CX
|
||||
MOVQ $128, DI
|
||||
|
||||
loop:
|
||||
MOVOU 0(AX), X0
|
||||
@@ -213,7 +213,7 @@ loop:
|
||||
ADDQ $16, BX
|
||||
ADDQ $16, CX
|
||||
ADDQ $16, DX
|
||||
SUBQ $2, BP
|
||||
SUBQ $2, DI
|
||||
JA loop
|
||||
RET
|
||||
|
||||
@@ -222,8 +222,8 @@ TEXT ·xorBlocksSSE2(SB), 4, $0-32
|
||||
MOVQ out+0(FP), DX
|
||||
MOVQ a+8(FP), AX
|
||||
MOVQ b+16(FP), BX
|
||||
MOVQ a+24(FP), CX
|
||||
MOVQ $128, BP
|
||||
MOVQ c+24(FP), CX
|
||||
MOVQ $128, DI
|
||||
|
||||
loop:
|
||||
MOVOU 0(AX), X0
|
||||
@@ -238,6 +238,6 @@ loop:
|
||||
ADDQ $16, BX
|
||||
ADDQ $16, CX
|
||||
ADDQ $16, DX
|
||||
SUBQ $2, BP
|
||||
SUBQ $2, DI
|
||||
JA loop
|
||||
RET
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine gccgo
|
||||
//go:build !amd64 || purego || !gc
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
+10
-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), int(MinCost), int(MaxCost))
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -82,11 +82,20 @@ type hashed struct {
|
||||
minor byte
|
||||
}
|
||||
|
||||
// ErrPasswordTooLong is returned when the password passed to
|
||||
// GenerateFromPassword is too long (i.e. > 72 bytes).
|
||||
var ErrPasswordTooLong = errors.New("bcrypt: password length exceeds 72 bytes")
|
||||
|
||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||
// to compare the returned hashed password with its cleartext version.
|
||||
// GenerateFromPassword does not accept passwords longer than 72 bytes, which
|
||||
// is the longest password bcrypt will operate on.
|
||||
func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
|
||||
if len(password) > 72 {
|
||||
return nil, ErrPasswordTooLong
|
||||
}
|
||||
p, err := newFromPassword(password, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -241,3 +241,10 @@ func TestNoSideEffectsFromCompare(t *testing.T) {
|
||||
t.Errorf("got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordTooLong(t *testing.T) {
|
||||
_, err := GenerateFromPassword(make([]byte, 73), 1)
|
||||
if err != ErrPasswordTooLong {
|
||||
t.Errorf("unexpected error: got %q, want %q", err, ErrPasswordTooLong)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xb.
|
||||
//
|
||||
// BLAKE2b is optimized for 64-bit platforms—including NEON-enabled ARMs—and
|
||||
// produces digests of any size between 1 and 64 bytes.
|
||||
// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xb see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
+45
-51
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -282,14 +282,12 @@ TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, DX
|
||||
MOVQ SP, R9
|
||||
ADDQ $31, R9
|
||||
ANDQ $~31, R9
|
||||
MOVQ R9, SP
|
||||
ADDQ $31, DX
|
||||
ANDQ $~31, DX
|
||||
|
||||
MOVQ CX, 16(SP)
|
||||
MOVQ CX, 16(DX)
|
||||
XORQ CX, CX
|
||||
MOVQ CX, 24(SP)
|
||||
MOVQ CX, 24(DX)
|
||||
|
||||
VMOVDQU ·AVX2_c40<>(SB), Y4
|
||||
VMOVDQU ·AVX2_c48<>(SB), Y5
|
||||
@@ -301,33 +299,33 @@ TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment
|
||||
|
||||
MOVQ 0(BX), R8
|
||||
MOVQ 8(BX), R9
|
||||
MOVQ R9, 8(SP)
|
||||
MOVQ R9, 8(DX)
|
||||
|
||||
loop:
|
||||
ADDQ $128, R8
|
||||
MOVQ R8, 0(SP)
|
||||
MOVQ R8, 0(DX)
|
||||
CMPQ R8, $128
|
||||
JGE noinc
|
||||
INCQ R9
|
||||
MOVQ R9, 8(SP)
|
||||
MOVQ R9, 8(DX)
|
||||
|
||||
noinc:
|
||||
VMOVDQA Y8, Y0
|
||||
VMOVDQA Y9, Y1
|
||||
VMOVDQA Y6, Y2
|
||||
VPXOR 0(SP), Y7, Y3
|
||||
VPXOR 0(DX), Y7, Y3
|
||||
|
||||
LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15()
|
||||
VMOVDQA Y12, 32(SP)
|
||||
VMOVDQA Y13, 64(SP)
|
||||
VMOVDQA Y14, 96(SP)
|
||||
VMOVDQA Y15, 128(SP)
|
||||
VMOVDQA Y12, 32(DX)
|
||||
VMOVDQA Y13, 64(DX)
|
||||
VMOVDQA Y14, 96(DX)
|
||||
VMOVDQA Y15, 128(DX)
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3()
|
||||
VMOVDQA Y12, 160(SP)
|
||||
VMOVDQA Y13, 192(SP)
|
||||
VMOVDQA Y14, 224(SP)
|
||||
VMOVDQA Y15, 256(SP)
|
||||
VMOVDQA Y12, 160(DX)
|
||||
VMOVDQA Y13, 192(DX)
|
||||
VMOVDQA Y14, 224(DX)
|
||||
VMOVDQA Y15, 256(DX)
|
||||
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4()
|
||||
@@ -347,8 +345,8 @@ noinc:
|
||||
LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0()
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
|
||||
ROUND_AVX2(32(SP), 64(SP), 96(SP), 128(SP), Y10, Y4, Y5)
|
||||
ROUND_AVX2(160(SP), 192(SP), 224(SP), 256(SP), Y10, Y4, Y5)
|
||||
ROUND_AVX2(32(DX), 64(DX), 96(DX), 128(DX), Y10, Y4, Y5)
|
||||
ROUND_AVX2(160(DX), 192(DX), 224(DX), 256(DX), Y10, Y4, Y5)
|
||||
|
||||
VPXOR Y0, Y8, Y8
|
||||
VPXOR Y1, Y9, Y9
|
||||
@@ -366,7 +364,6 @@ noinc:
|
||||
VMOVDQU Y9, 32(AX)
|
||||
VZEROUPPER
|
||||
|
||||
MOVQ DX, SP
|
||||
RET
|
||||
|
||||
#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA
|
||||
@@ -584,11 +581,9 @@ TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
MOVQ blocks_base+24(FP), SI
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, BP
|
||||
MOVQ SP, R9
|
||||
ADDQ $15, R9
|
||||
ANDQ $~15, R9
|
||||
MOVQ R9, SP
|
||||
MOVQ SP, R10
|
||||
ADDQ $15, R10
|
||||
ANDQ $~15, R10
|
||||
|
||||
VMOVDQU ·AVX_c40<>(SB), X0
|
||||
VMOVDQU ·AVX_c48<>(SB), X1
|
||||
@@ -596,8 +591,8 @@ TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
VMOVDQA X1, X9
|
||||
|
||||
VMOVDQU ·AVX_iv3<>(SB), X0
|
||||
VMOVDQA X0, 0(SP)
|
||||
XORQ CX, 0(SP) // 0(SP) = ·AVX_iv3 ^ (CX || 0)
|
||||
VMOVDQA X0, 0(R10)
|
||||
XORQ CX, 0(R10) // 0(R10) = ·AVX_iv3 ^ (CX || 0)
|
||||
|
||||
VMOVDQU 0(AX), X10
|
||||
VMOVDQU 16(AX), X11
|
||||
@@ -624,35 +619,35 @@ noinc:
|
||||
VMOVDQU ·AVX_iv2<>(SB), X6
|
||||
|
||||
VPXOR X15, X6, X6
|
||||
VMOVDQA 0(SP), X7
|
||||
VMOVDQA 0(R10), X7
|
||||
|
||||
LOAD_MSG_AVX_0_2_4_6_1_3_5_7()
|
||||
VMOVDQA X12, 16(SP)
|
||||
VMOVDQA X13, 32(SP)
|
||||
VMOVDQA X14, 48(SP)
|
||||
VMOVDQA X15, 64(SP)
|
||||
VMOVDQA X12, 16(R10)
|
||||
VMOVDQA X13, 32(R10)
|
||||
VMOVDQA X14, 48(R10)
|
||||
VMOVDQA X15, 64(R10)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15)
|
||||
VMOVDQA X12, 80(SP)
|
||||
VMOVDQA X13, 96(SP)
|
||||
VMOVDQA X14, 112(SP)
|
||||
VMOVDQA X15, 128(SP)
|
||||
VMOVDQA X12, 80(R10)
|
||||
VMOVDQA X13, 96(R10)
|
||||
VMOVDQA X14, 112(R10)
|
||||
VMOVDQA X15, 128(R10)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6)
|
||||
VMOVDQA X12, 144(SP)
|
||||
VMOVDQA X13, 160(SP)
|
||||
VMOVDQA X14, 176(SP)
|
||||
VMOVDQA X15, 192(SP)
|
||||
VMOVDQA X12, 144(R10)
|
||||
VMOVDQA X13, 160(R10)
|
||||
VMOVDQA X14, 176(R10)
|
||||
VMOVDQA X15, 192(R10)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
LOAD_MSG_AVX_1_0_11_5_12_2_7_3()
|
||||
VMOVDQA X12, 208(SP)
|
||||
VMOVDQA X13, 224(SP)
|
||||
VMOVDQA X14, 240(SP)
|
||||
VMOVDQA X15, 256(SP)
|
||||
VMOVDQA X12, 208(R10)
|
||||
VMOVDQA X13, 224(R10)
|
||||
VMOVDQA X14, 240(R10)
|
||||
VMOVDQA X15, 256(R10)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
@@ -712,14 +707,14 @@ noinc:
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(SP), 96(SP), 112(SP), 128(SP), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(SP), 160(SP), 176(SP), 192(SP), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(SP), 224(SP), 240(SP), 256(SP), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
VMOVDQU 32(AX), X14
|
||||
@@ -746,5 +741,4 @@ noinc:
|
||||
MOVQ R9, 8(BX)
|
||||
VZEROUPPER
|
||||
|
||||
MOVQ BP, SP
|
||||
RET
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// +build !go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package blake2b
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
func init() {
|
||||
useSSE4 = cpu.X86.HasSSE41
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte)
|
||||
|
||||
func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) {
|
||||
if useSSE4 {
|
||||
hashBlocksSSE4(h, c, flag, blocks)
|
||||
} else {
|
||||
hashBlocksGeneric(h, c, flag, blocks)
|
||||
}
|
||||
}
|
||||
+27
-30
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -118,15 +118,13 @@ TEXT ·hashBlocksSSE4(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
MOVQ blocks_base+24(FP), SI
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, BP
|
||||
MOVQ SP, R9
|
||||
ADDQ $15, R9
|
||||
ANDQ $~15, R9
|
||||
MOVQ R9, SP
|
||||
MOVQ SP, R10
|
||||
ADDQ $15, R10
|
||||
ANDQ $~15, R10
|
||||
|
||||
MOVOU ·iv3<>(SB), X0
|
||||
MOVO X0, 0(SP)
|
||||
XORQ CX, 0(SP) // 0(SP) = ·iv3 ^ (CX || 0)
|
||||
MOVO X0, 0(R10)
|
||||
XORQ CX, 0(R10) // 0(R10) = ·iv3 ^ (CX || 0)
|
||||
|
||||
MOVOU ·c40<>(SB), X13
|
||||
MOVOU ·c48<>(SB), X14
|
||||
@@ -156,35 +154,35 @@ noinc:
|
||||
MOVOU ·iv2<>(SB), X6
|
||||
|
||||
PXOR X8, X6
|
||||
MOVO 0(SP), X7
|
||||
MOVO 0(R10), X7
|
||||
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7)
|
||||
MOVO X8, 16(SP)
|
||||
MOVO X9, 32(SP)
|
||||
MOVO X10, 48(SP)
|
||||
MOVO X11, 64(SP)
|
||||
MOVO X8, 16(R10)
|
||||
MOVO X9, 32(R10)
|
||||
MOVO X10, 48(R10)
|
||||
MOVO X11, 64(R10)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 8, 10, 12, 14, 9, 11, 13, 15)
|
||||
MOVO X8, 80(SP)
|
||||
MOVO X9, 96(SP)
|
||||
MOVO X10, 112(SP)
|
||||
MOVO X11, 128(SP)
|
||||
MOVO X8, 80(R10)
|
||||
MOVO X9, 96(R10)
|
||||
MOVO X10, 112(R10)
|
||||
MOVO X11, 128(R10)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 14, 4, 9, 13, 10, 8, 15, 6)
|
||||
MOVO X8, 144(SP)
|
||||
MOVO X9, 160(SP)
|
||||
MOVO X10, 176(SP)
|
||||
MOVO X11, 192(SP)
|
||||
MOVO X8, 144(R10)
|
||||
MOVO X9, 160(R10)
|
||||
MOVO X10, 176(R10)
|
||||
MOVO X11, 192(R10)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 1, 0, 11, 5, 12, 2, 7, 3)
|
||||
MOVO X8, 208(SP)
|
||||
MOVO X9, 224(SP)
|
||||
MOVO X10, 240(SP)
|
||||
MOVO X11, 256(SP)
|
||||
MOVO X8, 208(R10)
|
||||
MOVO X9, 224(R10)
|
||||
MOVO X10, 240(R10)
|
||||
MOVO X11, 256(R10)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
@@ -244,14 +242,14 @@ noinc:
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(SP), 96(SP), 112(SP), 128(SP), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(SP), 160(SP), 176(SP), 192(SP), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(SP), 224(SP), 240(SP), 256(SP), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
MOVOU 32(AX), X10
|
||||
@@ -277,5 +275,4 @@ noinc:
|
||||
MOVQ R8, 0(BX)
|
||||
MOVQ R9, 8(BX)
|
||||
|
||||
MOVQ BP, SP
|
||||
RET
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine gccgo
|
||||
//go:build !amd64 || purego || !gc
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package blake2b
|
||||
|
||||
import (
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Package blake2s implements the BLAKE2s hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xs.
|
||||
//
|
||||
// BLAKE2s is optimized for 8- to 32-bit platforms and produces digests of any
|
||||
// size between 1 and 32 bytes.
|
||||
// For a detailed specification of BLAKE2s see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xs see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine
|
||||
//go:build 386 && gc && !purego
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+43
-49
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build 386,!gccgo,!appengine
|
||||
//go:build 386 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -297,19 +297,17 @@ TEXT ·hashBlocksSSE2(SB), 0, $672-24 // frame = 656 + 16 byte alignment
|
||||
MOVL blocks_base+12(FP), SI
|
||||
MOVL blocks_len+16(FP), DX
|
||||
|
||||
MOVL SP, BP
|
||||
MOVL SP, DI
|
||||
ADDL $15, DI
|
||||
ANDL $~15, DI
|
||||
MOVL DI, SP
|
||||
|
||||
MOVL CX, 8(SP)
|
||||
MOVL CX, 8(DI)
|
||||
MOVL 0(BX), CX
|
||||
MOVL CX, 0(SP)
|
||||
MOVL CX, 0(DI)
|
||||
MOVL 4(BX), CX
|
||||
MOVL CX, 4(SP)
|
||||
MOVL CX, 4(DI)
|
||||
XORL CX, CX
|
||||
MOVL CX, 12(SP)
|
||||
MOVL CX, 12(DI)
|
||||
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 16(AX), X1
|
||||
@@ -321,22 +319,22 @@ loop:
|
||||
MOVOU iv0<>(SB), X6
|
||||
MOVOU iv1<>(SB), X7
|
||||
|
||||
MOVO 0(SP), X3
|
||||
MOVO 0(DI), X3
|
||||
PADDQ X2, X3
|
||||
PXOR X3, X7
|
||||
MOVO X3, 0(SP)
|
||||
MOVO X3, 0(DI)
|
||||
|
||||
PRECOMPUTE(SP, 16, SI, CX)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X3)
|
||||
PRECOMPUTE(DI, 16, SI, CX)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3)
|
||||
|
||||
PXOR X4, X0
|
||||
PXOR X5, X1
|
||||
@@ -347,15 +345,14 @@ loop:
|
||||
SUBL $64, DX
|
||||
JNE loop
|
||||
|
||||
MOVL 0(SP), CX
|
||||
MOVL 0(DI), CX
|
||||
MOVL CX, 0(BX)
|
||||
MOVL 4(SP), CX
|
||||
MOVL 4(DI), CX
|
||||
MOVL CX, 4(BX)
|
||||
|
||||
MOVOU X0, 0(AX)
|
||||
MOVOU X1, 16(AX)
|
||||
|
||||
MOVL BP, SP
|
||||
RET
|
||||
|
||||
// func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte)
|
||||
@@ -366,54 +363,52 @@ TEXT ·hashBlocksSSSE3(SB), 0, $704-24 // frame = 688 + 16 byte alignment
|
||||
MOVL blocks_base+12(FP), SI
|
||||
MOVL blocks_len+16(FP), DX
|
||||
|
||||
MOVL SP, BP
|
||||
MOVL SP, DI
|
||||
ADDL $15, DI
|
||||
ANDL $~15, DI
|
||||
MOVL DI, SP
|
||||
|
||||
MOVL CX, 8(SP)
|
||||
MOVL CX, 8(DI)
|
||||
MOVL 0(BX), CX
|
||||
MOVL CX, 0(SP)
|
||||
MOVL CX, 0(DI)
|
||||
MOVL 4(BX), CX
|
||||
MOVL CX, 4(SP)
|
||||
MOVL CX, 4(DI)
|
||||
XORL CX, CX
|
||||
MOVL CX, 12(SP)
|
||||
MOVL CX, 12(DI)
|
||||
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 16(AX), X1
|
||||
MOVOU counter<>(SB), X2
|
||||
|
||||
loop:
|
||||
MOVO X0, 656(SP)
|
||||
MOVO X1, 672(SP)
|
||||
MOVO X0, 656(DI)
|
||||
MOVO X1, 672(DI)
|
||||
MOVO X0, X4
|
||||
MOVO X1, X5
|
||||
MOVOU iv0<>(SB), X6
|
||||
MOVOU iv1<>(SB), X7
|
||||
|
||||
MOVO 0(SP), X3
|
||||
MOVO 0(DI), X3
|
||||
PADDQ X2, X3
|
||||
PXOR X3, X7
|
||||
MOVO X3, 0(SP)
|
||||
MOVO X3, 0(DI)
|
||||
|
||||
MOVOU rol16<>(SB), X0
|
||||
MOVOU rol8<>(SB), X1
|
||||
|
||||
PRECOMPUTE(SP, 16, SI, CX)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X3, X0, X1)
|
||||
PRECOMPUTE(DI, 16, SI, CX)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3, X0, X1)
|
||||
|
||||
MOVO 656(SP), X0
|
||||
MOVO 672(SP), X1
|
||||
MOVO 656(DI), X0
|
||||
MOVO 672(DI), X1
|
||||
PXOR X4, X0
|
||||
PXOR X5, X1
|
||||
PXOR X6, X0
|
||||
@@ -423,13 +418,12 @@ loop:
|
||||
SUBL $64, DX
|
||||
JNE loop
|
||||
|
||||
MOVL 0(SP), CX
|
||||
MOVL 0(DI), CX
|
||||
MOVL CX, 0(BX)
|
||||
MOVL 4(SP), CX
|
||||
MOVL 4(DI), CX
|
||||
MOVL CX, 4(BX)
|
||||
|
||||
MOVOU X0, 0(AX)
|
||||
MOVOU X1, 16(AX)
|
||||
|
||||
MOVL BP, SP
|
||||
RET
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+31
-37
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -317,30 +317,30 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVL R15, 8*4+off+576(dst)
|
||||
|
||||
#define BLAKE2s_SSE2() \
|
||||
PRECOMPUTE_MSG(SP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X8)
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(BP), 32(BP), 48(BP), 64(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(BP), 32+64(BP), 48+64(BP), 64+64(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(BP), 32+128(BP), 48+128(BP), 64+128(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(BP), 32+192(BP), 48+192(BP), 64+192(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(BP), 32+256(BP), 48+256(BP), 64+256(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(BP), 32+320(BP), 48+320(BP), 64+320(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(BP), 32+384(BP), 48+384(BP), 64+384(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(BP), 32+448(BP), 48+448(BP), 64+448(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(BP), 32+512(BP), 48+512(BP), 64+512(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(BP), 32+576(BP), 48+576(BP), 64+576(BP), X8)
|
||||
|
||||
#define BLAKE2s_SSSE3() \
|
||||
PRECOMPUTE_MSG(SP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X8, X13, X14)
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(BP), 32(BP), 48(BP), 64(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(BP), 32+64(BP), 48+64(BP), 64+64(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(BP), 32+128(BP), 48+128(BP), 64+128(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(BP), 32+192(BP), 48+192(BP), 64+192(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(BP), 32+256(BP), 48+256(BP), 64+256(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(BP), 32+320(BP), 48+320(BP), 64+320(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(BP), 32+384(BP), 48+384(BP), 64+384(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(BP), 32+448(BP), 48+448(BP), 64+448(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(BP), 32+512(BP), 48+512(BP), 64+512(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(BP), 32+576(BP), 48+576(BP), 64+576(BP), X8, X13, X14)
|
||||
|
||||
#define BLAKE2s_SSE4() \
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15); \
|
||||
@@ -372,16 +372,12 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVQ blocks_len, DX; \
|
||||
\
|
||||
MOVQ SP, BP; \
|
||||
MOVQ SP, R9; \
|
||||
ADDQ $15, R9; \
|
||||
ANDQ $~15, R9; \
|
||||
MOVQ R9, SP; \
|
||||
ADDQ $15, BP; \
|
||||
ANDQ $~15, BP; \
|
||||
\
|
||||
MOVQ 0(BX), R9; \
|
||||
MOVQ R9, 0(SP); \
|
||||
XORQ R9, R9; \
|
||||
MOVQ R9, 8(SP); \
|
||||
MOVL CX, 8(SP); \
|
||||
MOVQ R9, 0(BP); \
|
||||
MOVQ CX, 8(BP); \
|
||||
\
|
||||
MOVOU 0(AX), X0; \
|
||||
MOVOU 16(AX), X1; \
|
||||
@@ -391,7 +387,7 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVOU counter<>(SB), X12; \
|
||||
MOVOU rol16<>(SB), X13; \
|
||||
MOVOU rol8<>(SB), X14; \
|
||||
MOVO 0(SP), X15; \
|
||||
MOVO 0(BP), X15; \
|
||||
\
|
||||
loop: \
|
||||
MOVO X0, X4; \
|
||||
@@ -413,14 +409,12 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
SUBQ $64, DX; \
|
||||
JNE loop; \
|
||||
\
|
||||
MOVO X15, 0(SP); \
|
||||
MOVQ 0(SP), R9; \
|
||||
MOVO X15, 0(BP); \
|
||||
MOVQ 0(BP), R9; \
|
||||
MOVQ R9, 0(BX); \
|
||||
\
|
||||
MOVOU X0, 0(AX); \
|
||||
MOVOU X1, 16(AX); \
|
||||
\
|
||||
MOVQ BP, SP
|
||||
MOVOU X1, 16(AX)
|
||||
|
||||
// func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte)
|
||||
TEXT ·hashBlocksSSE2(SB), 0, $672-48 // frame = 656 + 16 byte alignment
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64,!386 gccgo appengine
|
||||
//go:build (!amd64 && !386) || !gc || purego
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
//go:build go1.9
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ type G2 struct {
|
||||
p *twistPoint
|
||||
}
|
||||
|
||||
// RandomG1 returns x and g₂ˣ where x is a random, non-zero number read from r.
|
||||
// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r.
|
||||
func RandomG2(r io.Reader) (*big.Int, *G2, error) {
|
||||
var k *big.Int
|
||||
var err error
|
||||
|
||||
+7
-4
@@ -13,7 +13,10 @@
|
||||
// golang.org/x/crypto/chacha20poly1305).
|
||||
package cast5 // import "golang.org/x/crypto/cast5"
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
const BlockSize = 8
|
||||
const KeySize = 16
|
||||
@@ -241,19 +244,19 @@ func (c *Cipher) keySchedule(in []byte) {
|
||||
// These are the three 'f' functions. See RFC 2144, section 2.2.
|
||||
func f1(d, m uint32, r uint8) uint32 {
|
||||
t := m + d
|
||||
I := (t << r) | (t >> (32 - r))
|
||||
I := bits.RotateLeft32(t, int(r))
|
||||
return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
|
||||
}
|
||||
|
||||
func f2(d, m uint32, r uint8) uint32 {
|
||||
t := m ^ d
|
||||
I := (t << r) | (t >> (32 - r))
|
||||
I := bits.RotateLeft32(t, int(r))
|
||||
return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
|
||||
}
|
||||
|
||||
func f3(d, m uint32, r uint8) uint32 {
|
||||
t := m - d
|
||||
I := (t << r) | (t >> (32 - r))
|
||||
I := bits.RotateLeft32(t, int(r))
|
||||
return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.11
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
package chacha20
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.11
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
|
||||
+89
-55
@@ -12,7 +12,7 @@ import (
|
||||
"errors"
|
||||
"math/bits"
|
||||
|
||||
"golang.org/x/crypto/internal/subtle"
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,10 +42,14 @@ type Cipher struct {
|
||||
|
||||
// The last len bytes of buf are leftover key stream bytes from the previous
|
||||
// XORKeyStream invocation. The size of buf depends on how many blocks are
|
||||
// computed at a time.
|
||||
// computed at a time by xorKeyStreamBlocks.
|
||||
buf [bufSize]byte
|
||||
len int
|
||||
|
||||
// overflow is set when the counter overflowed, no more blocks can be
|
||||
// generated, and the next XORKeyStream call should panic.
|
||||
overflow bool
|
||||
|
||||
// The counter-independent results of the first round are cached after they
|
||||
// are computed the first time.
|
||||
precompDone bool
|
||||
@@ -89,6 +93,7 @@ func newUnauthenticatedCipher(c *Cipher, key, nonce []byte) (*Cipher, error) {
|
||||
return nil, errors.New("chacha20: wrong nonce size")
|
||||
}
|
||||
|
||||
key, nonce = key[:KeySize], nonce[:NonceSize] // bounds check elimination hint
|
||||
c.key = [8]uint32{
|
||||
binary.LittleEndian.Uint32(key[0:4]),
|
||||
binary.LittleEndian.Uint32(key[4:8]),
|
||||
@@ -136,6 +141,36 @@ func quarterRound(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
|
||||
return a, b, c, d
|
||||
}
|
||||
|
||||
// SetCounter sets the Cipher counter. The next invocation of XORKeyStream will
|
||||
// behave as if (64 * counter) bytes had been encrypted so far.
|
||||
//
|
||||
// To prevent accidental counter reuse, SetCounter panics if counter is less
|
||||
// than the current value.
|
||||
//
|
||||
// Note that the execution time of XORKeyStream is not independent of the
|
||||
// counter value.
|
||||
func (s *Cipher) SetCounter(counter uint32) {
|
||||
// Internally, s may buffer multiple blocks, which complicates this
|
||||
// implementation slightly. When checking whether the counter has rolled
|
||||
// back, we must use both s.counter and s.len to determine how many blocks
|
||||
// we have already output.
|
||||
outputCounter := s.counter - uint32(s.len)/blockSize
|
||||
if s.overflow || counter < outputCounter {
|
||||
panic("chacha20: SetCounter attempted to rollback counter")
|
||||
}
|
||||
|
||||
// In the general case, we set the new counter value and reset s.len to 0,
|
||||
// causing the next call to XORKeyStream to refill the buffer. However, if
|
||||
// we're advancing within the existing buffer, we can save work by simply
|
||||
// setting s.len.
|
||||
if counter < s.counter {
|
||||
s.len = int(s.counter-counter) * blockSize
|
||||
} else {
|
||||
s.counter = counter
|
||||
s.len = 0
|
||||
}
|
||||
}
|
||||
|
||||
// XORKeyStream XORs each byte in the given slice with a byte from the
|
||||
// cipher's key stream. Dst and src must overlap entirely or not at all.
|
||||
//
|
||||
@@ -154,7 +189,7 @@ func (s *Cipher) XORKeyStream(dst, src []byte) {
|
||||
panic("chacha20: output smaller than input")
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
if subtle.InexactOverlap(dst, src) {
|
||||
if alias.InexactOverlap(dst, src) {
|
||||
panic("chacha20: invalid buffer overlap")
|
||||
}
|
||||
|
||||
@@ -169,34 +204,52 @@ func (s *Cipher) XORKeyStream(dst, src []byte) {
|
||||
dst[i] = src[i] ^ b
|
||||
}
|
||||
s.len -= len(keyStream)
|
||||
src = src[len(keyStream):]
|
||||
dst = dst[len(keyStream):]
|
||||
dst, src = dst[len(keyStream):], src[len(keyStream):]
|
||||
}
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
const blocksPerBuf = bufSize / blockSize
|
||||
numBufs := (uint64(len(src)) + bufSize - 1) / bufSize
|
||||
if uint64(s.counter)+numBufs*blocksPerBuf >= 1<<32 {
|
||||
// If we'd need to let the counter overflow and keep generating output,
|
||||
// panic immediately. If instead we'd only reach the last block, remember
|
||||
// not to generate any more output after the buffer is drained.
|
||||
numBlocks := (uint64(len(src)) + blockSize - 1) / blockSize
|
||||
if s.overflow || uint64(s.counter)+numBlocks > 1<<32 {
|
||||
panic("chacha20: counter overflow")
|
||||
} else if uint64(s.counter)+numBlocks == 1<<32 {
|
||||
s.overflow = true
|
||||
}
|
||||
|
||||
// xorKeyStreamBlocks implementations expect input lengths that are a
|
||||
// multiple of bufSize. Platform-specific ones process multiple blocks at a
|
||||
// time, so have bufSizes that are a multiple of blockSize.
|
||||
|
||||
rem := len(src) % bufSize
|
||||
full := len(src) - rem
|
||||
|
||||
full := len(src) - len(src)%bufSize
|
||||
if full > 0 {
|
||||
s.xorKeyStreamBlocks(dst[:full], src[:full])
|
||||
}
|
||||
dst, src = dst[full:], src[full:]
|
||||
|
||||
// If using a multi-block xorKeyStreamBlocks would overflow, use the generic
|
||||
// one that does one block at a time.
|
||||
const blocksPerBuf = bufSize / blockSize
|
||||
if uint64(s.counter)+blocksPerBuf > 1<<32 {
|
||||
s.buf = [bufSize]byte{}
|
||||
numBlocks := (len(src) + blockSize - 1) / blockSize
|
||||
buf := s.buf[bufSize-numBlocks*blockSize:]
|
||||
copy(buf, src)
|
||||
s.xorKeyStreamBlocksGeneric(buf, buf)
|
||||
s.len = len(buf) - copy(dst, buf)
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a partial (multi-)block, pad it for xorKeyStreamBlocks, and
|
||||
// keep the leftover keystream for the next XORKeyStream invocation.
|
||||
if rem > 0 {
|
||||
if len(src) > 0 {
|
||||
s.buf = [bufSize]byte{}
|
||||
copy(s.buf[:], src[full:])
|
||||
copy(s.buf[:], src)
|
||||
s.xorKeyStreamBlocks(s.buf[:], s.buf[:])
|
||||
s.len = bufSize - copy(dst[full:], s.buf[:])
|
||||
s.len = bufSize - copy(dst, s.buf[:])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +286,9 @@ func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) {
|
||||
s.precompDone = true
|
||||
}
|
||||
|
||||
for i := 0; i < len(src); i += blockSize {
|
||||
// A condition of len(src) > 0 would be sufficient, but this also
|
||||
// acts as a bounds check elimination hint.
|
||||
for len(src) >= 64 && len(dst) >= 64 {
|
||||
// The remainder of the first column round.
|
||||
fcr0, fcr4, fcr8, fcr12 := quarterRound(c0, c4, c8, s.counter)
|
||||
|
||||
@@ -258,49 +313,28 @@ func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) {
|
||||
x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
// Finally, add back the initial state to generate the key stream.
|
||||
x0 += c0
|
||||
x1 += c1
|
||||
x2 += c2
|
||||
x3 += c3
|
||||
x4 += c4
|
||||
x5 += c5
|
||||
x6 += c6
|
||||
x7 += c7
|
||||
x8 += c8
|
||||
x9 += c9
|
||||
x10 += c10
|
||||
x11 += c11
|
||||
x12 += s.counter
|
||||
x13 += c13
|
||||
x14 += c14
|
||||
x15 += c15
|
||||
// Add back the initial state to generate the key stream, then
|
||||
// XOR the key stream with the source and write out the result.
|
||||
addXor(dst[0:4], src[0:4], x0, c0)
|
||||
addXor(dst[4:8], src[4:8], x1, c1)
|
||||
addXor(dst[8:12], src[8:12], x2, c2)
|
||||
addXor(dst[12:16], src[12:16], x3, c3)
|
||||
addXor(dst[16:20], src[16:20], x4, c4)
|
||||
addXor(dst[20:24], src[20:24], x5, c5)
|
||||
addXor(dst[24:28], src[24:28], x6, c6)
|
||||
addXor(dst[28:32], src[28:32], x7, c7)
|
||||
addXor(dst[32:36], src[32:36], x8, c8)
|
||||
addXor(dst[36:40], src[36:40], x9, c9)
|
||||
addXor(dst[40:44], src[40:44], x10, c10)
|
||||
addXor(dst[44:48], src[44:48], x11, c11)
|
||||
addXor(dst[48:52], src[48:52], x12, s.counter)
|
||||
addXor(dst[52:56], src[52:56], x13, c13)
|
||||
addXor(dst[56:60], src[56:60], x14, c14)
|
||||
addXor(dst[60:64], src[60:64], x15, c15)
|
||||
|
||||
s.counter += 1
|
||||
if s.counter == 0 {
|
||||
panic("chacha20: internal error: counter overflow")
|
||||
}
|
||||
|
||||
in, out := src[i:], dst[i:]
|
||||
in, out = in[:blockSize], out[:blockSize] // bounds check elimination hint
|
||||
|
||||
// XOR the key stream with the source and write out the result.
|
||||
xor(out[0:], in[0:], x0)
|
||||
xor(out[4:], in[4:], x1)
|
||||
xor(out[8:], in[8:], x2)
|
||||
xor(out[12:], in[12:], x3)
|
||||
xor(out[16:], in[16:], x4)
|
||||
xor(out[20:], in[20:], x5)
|
||||
xor(out[24:], in[24:], x6)
|
||||
xor(out[28:], in[28:], x7)
|
||||
xor(out[32:], in[32:], x8)
|
||||
xor(out[36:], in[36:], x9)
|
||||
xor(out[40:], in[40:], x10)
|
||||
xor(out[44:], in[44:], x11)
|
||||
xor(out[48:], in[48:], x12)
|
||||
xor(out[52:], in[52:], x13)
|
||||
xor(out[56:], in[56:], x14)
|
||||
xor(out[60:], in[60:], x15)
|
||||
src, dst = src[blockSize:], dst[blockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !arm64,!s390x,!ppc64le arm64,!go1.11 gccgo appengine
|
||||
//go:build (!arm64 && !s390x && !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.
|
||||
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
package chacha20
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// The differences in this and the original implementation are
|
||||
// due to the calling conventions and initialization of constants.
|
||||
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
package chacha20
|
||||
|
||||
@@ -14,6 +14,7 @@ const bufSize = 256
|
||||
|
||||
// xorKeyStreamVX is an assembly implementation of XORKeyStream. It must only
|
||||
// be called when the vector facility is available. Implementation in asm_s390x.s.
|
||||
//
|
||||
//go:noescape
|
||||
func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
#include "go_asm.h"
|
||||
#include "textflag.h"
|
||||
|
||||
+102
-1
@@ -110,6 +110,107 @@ func TestStep(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCounter(t *testing.T) {
|
||||
newCipher := func() *Cipher {
|
||||
s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
return s
|
||||
}
|
||||
s := newCipher()
|
||||
src := bytes.Repeat([]byte("test"), 32) // two 64-byte blocks
|
||||
dst1 := make([]byte, len(src))
|
||||
s.XORKeyStream(dst1, src)
|
||||
// advance counter to 1 and xor second block
|
||||
s = newCipher()
|
||||
s.SetCounter(1)
|
||||
dst2 := make([]byte, len(src))
|
||||
s.XORKeyStream(dst2[64:], src[64:])
|
||||
if !bytes.Equal(dst1[64:], dst2[64:]) {
|
||||
t.Error("failed to produce identical output using SetCounter")
|
||||
}
|
||||
|
||||
// test again with unaligned blocks; SetCounter should reset the buffer
|
||||
s = newCipher()
|
||||
s.XORKeyStream(dst1[:70], src[:70])
|
||||
s = newCipher()
|
||||
s.XORKeyStream([]byte{0}, []byte{0})
|
||||
s.SetCounter(1)
|
||||
s.XORKeyStream(dst2[64:70], src[64:70])
|
||||
if !bytes.Equal(dst1[64:70], dst2[64:70]) {
|
||||
t.Error("SetCounter did not reset buffer")
|
||||
}
|
||||
|
||||
// advancing to a lower counter value should cause a panic
|
||||
panics := func(fn func()) (p bool) {
|
||||
defer func() { p = recover() != nil }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
if !panics(func() { s.SetCounter(0) }) {
|
||||
t.Error("counter decreasing should trigger a panic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastBlock(t *testing.T) {
|
||||
panics := func(fn func()) (p bool) {
|
||||
defer func() { p = recover() != nil }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
|
||||
checkLastBlock := func(b []byte) {
|
||||
t.Helper()
|
||||
// Hardcoded result to check all implementations generate the same output.
|
||||
lastBlock := "ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d" +
|
||||
"92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d"
|
||||
if got := hex.EncodeToString(b); got != lastBlock {
|
||||
t.Errorf("wrong output for the last block, got %q, want %q", got, lastBlock)
|
||||
}
|
||||
}
|
||||
|
||||
// setting the counter to 0xffffffff and crypting multiple blocks should
|
||||
// trigger a panic
|
||||
s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff)
|
||||
blocks := make([]byte, blockSize*2)
|
||||
if !panics(func() { s.XORKeyStream(blocks, blocks) }) {
|
||||
t.Error("crypting multiple blocks should trigger a panic")
|
||||
}
|
||||
|
||||
// setting the counter to 0xffffffff - 1 and crypting two blocks should not
|
||||
// trigger a panic
|
||||
s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff - 1)
|
||||
if panics(func() { s.XORKeyStream(blocks, blocks) }) {
|
||||
t.Error("crypting the last blocks should not trigger a panic")
|
||||
}
|
||||
checkLastBlock(blocks[blockSize:])
|
||||
// once all the keystream is spent, setting the counter should panic
|
||||
if !panics(func() { s.SetCounter(0xffffffff) }) {
|
||||
t.Error("setting the counter after overflow should trigger a panic")
|
||||
}
|
||||
// crypting a subsequent block *should* panic
|
||||
block := make([]byte, blockSize)
|
||||
if !panics(func() { s.XORKeyStream(block, block) }) {
|
||||
t.Error("crypting after overflow should trigger a panic")
|
||||
}
|
||||
|
||||
// if we crypt less than a full block, we should be able to crypt the rest
|
||||
// in a subsequent call without panicking
|
||||
s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff)
|
||||
if panics(func() { s.XORKeyStream(block[:7], block[:7]) }) {
|
||||
t.Error("crypting part of the last block should not trigger a panic")
|
||||
}
|
||||
if panics(func() { s.XORKeyStream(block[7:], block[7:]) }) {
|
||||
t.Error("crypting part of the last block should not trigger a panic")
|
||||
}
|
||||
checkLastBlock(block)
|
||||
// as before, a third call should trigger a panic because all keystream is spent
|
||||
if !panics(func() { s.XORKeyStream(block[:1], block[:1]) }) {
|
||||
t.Error("crypting after overflow should trigger a panic")
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkChaCha20(b *testing.B, step, count int) {
|
||||
tot := step * count
|
||||
src := make([]byte, tot)
|
||||
@@ -137,7 +238,7 @@ func BenchmarkChaCha20(b *testing.B) {
|
||||
benchmarkChaCha20(b, 10, 25)
|
||||
})
|
||||
b.Run("4096", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 256, 1)
|
||||
benchmarkChaCha20(b, 4096, 1)
|
||||
})
|
||||
b.Run("100x40", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 100, 40)
|
||||
|
||||
+9
-8
@@ -13,10 +13,10 @@ const unaligned = runtime.GOARCH == "386" ||
|
||||
runtime.GOARCH == "ppc64le" ||
|
||||
runtime.GOARCH == "s390x"
|
||||
|
||||
// xor reads a little endian uint32 from src, XORs it with u and
|
||||
// addXor reads a little endian uint32 from src, XORs it with (a + b) and
|
||||
// places the result in little endian byte order in dst.
|
||||
func xor(dst, src []byte, u uint32) {
|
||||
_, _ = src[3], dst[3] // eliminate bounds checks
|
||||
func addXor(dst, src []byte, a, b uint32) {
|
||||
_, _ = src[3], dst[3] // bounds check elimination hint
|
||||
if unaligned {
|
||||
// The compiler should optimize this code into
|
||||
// 32-bit unaligned little endian loads and stores.
|
||||
@@ -27,15 +27,16 @@ func xor(dst, src []byte, u uint32) {
|
||||
v |= uint32(src[1]) << 8
|
||||
v |= uint32(src[2]) << 16
|
||||
v |= uint32(src[3]) << 24
|
||||
v ^= u
|
||||
v ^= a + b
|
||||
dst[0] = byte(v)
|
||||
dst[1] = byte(v >> 8)
|
||||
dst[2] = byte(v >> 16)
|
||||
dst[3] = byte(v >> 24)
|
||||
} else {
|
||||
dst[0] = src[0] ^ byte(u)
|
||||
dst[1] = src[1] ^ byte(u>>8)
|
||||
dst[2] = src[2] ^ byte(u>>16)
|
||||
dst[3] = src[3] ^ byte(u>>24)
|
||||
a += b
|
||||
dst[0] = src[0] ^ byte(a)
|
||||
dst[1] = src[1] ^ byte(a>>8)
|
||||
dst[2] = src[2] ^ byte(a>>16)
|
||||
dst[3] = src[3] ^ byte(a>>24)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ const (
|
||||
// NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305
|
||||
// variant of this AEAD, in bytes.
|
||||
NonceSizeX = 24
|
||||
|
||||
// Overhead is the size of the Poly1305 authentication tag, and the
|
||||
// difference between a ciphertext length and its plaintext.
|
||||
Overhead = 16
|
||||
)
|
||||
|
||||
type chacha20poly1305 struct {
|
||||
@@ -47,7 +51,7 @@ func (c *chacha20poly1305) NonceSize() int {
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) Overhead() int {
|
||||
return 16
|
||||
return Overhead
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/crypto/internal/subtle"
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
@@ -55,7 +55,7 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
setupState(&state, &c.key, nonce)
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||
if subtle.InexactOverlap(out, plaintext) {
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
||||
@@ -72,7 +72,7 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
|
||||
|
||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||
if subtle.InexactOverlap(out, ciphertext) {
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// This file was originally from https://golang.org/cl/24717 by Vlad Krasnov of CloudFlare.
|
||||
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
//go:build gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
// General register allocation
|
||||
@@ -183,11 +183,31 @@ GLOBL ·andMask<>(SB), (NOPTR+RODATA), $240
|
||||
#define shiftD1Right BYTE $0x66; BYTE $0x45; BYTE $0x0f; BYTE $0x3a; BYTE $0x0f; BYTE $0xd2; BYTE $0x04 // PALIGNR $4, X10, X10
|
||||
#define shiftD2Right BYTE $0x66; BYTE $0x45; BYTE $0x0f; BYTE $0x3a; BYTE $0x0f; BYTE $0xdb; BYTE $0x04 // PALIGNR $4, X11, X11
|
||||
#define shiftD3Right BYTE $0x66; BYTE $0x45; BYTE $0x0f; BYTE $0x3a; BYTE $0x0f; BYTE $0xff; BYTE $0x04 // PALIGNR $4, X15, X15
|
||||
|
||||
// Some macros
|
||||
|
||||
// ROL rotates the uint32s in register R left by N bits, using temporary T.
|
||||
#define ROL(N, R, T) \
|
||||
MOVO R, T; PSLLL $(N), T; PSRLL $(32-(N)), R; PXOR T, R
|
||||
|
||||
// ROL16 rotates the uint32s in register R left by 16, using temporary T if needed.
|
||||
#ifdef GOAMD64_v2
|
||||
#define ROL16(R, T) PSHUFB ·rol16<>(SB), R
|
||||
#else
|
||||
#define ROL16(R, T) ROL(16, R, T)
|
||||
#endif
|
||||
|
||||
// ROL8 rotates the uint32s in register R left by 8, using temporary T if needed.
|
||||
#ifdef GOAMD64_v2
|
||||
#define ROL8(R, T) PSHUFB ·rol8<>(SB), R
|
||||
#else
|
||||
#define ROL8(R, T) ROL(8, R, T)
|
||||
#endif
|
||||
|
||||
#define chachaQR(A, B, C, D, T) \
|
||||
PADDD B, A; PXOR A, D; PSHUFB ·rol16<>(SB), D \
|
||||
PADDD B, A; PXOR A, D; ROL16(D, T) \
|
||||
PADDD D, C; PXOR C, B; MOVO B, T; PSLLL $12, T; PSRLL $20, B; PXOR T, B \
|
||||
PADDD B, A; PXOR A, D; PSHUFB ·rol8<>(SB), D \
|
||||
PADDD B, A; PXOR A, D; ROL8(D, T) \
|
||||
PADDD D, C; PXOR C, B; MOVO B, T; PSLLL $7, T; PSRLL $25, B; PXOR T, B
|
||||
|
||||
#define chachaQR_AVX2(A, B, C, D, T) \
|
||||
|
||||
@@ -8,60 +8,68 @@ import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/internal/subtle"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
"golang.org/x/crypto/internal/poly1305"
|
||||
)
|
||||
|
||||
func roundTo16(n int) int {
|
||||
return 16 * ((n + 15) / 16)
|
||||
func writeWithPadding(p *poly1305.MAC, b []byte) {
|
||||
p.Write(b)
|
||||
if rem := len(b) % 16; rem != 0 {
|
||||
var buf [16]byte
|
||||
padLen := 16 - rem
|
||||
p.Write(buf[:padLen])
|
||||
}
|
||||
}
|
||||
|
||||
func writeUint64(p *poly1305.MAC, n int) {
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], uint64(n))
|
||||
p.Write(buf[:])
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
|
||||
if subtle.InexactOverlap(out, plaintext) {
|
||||
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
|
||||
var polyKey, discardBuf [32]byte
|
||||
var polyKey [32]byte
|
||||
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
|
||||
s.XORKeyStream(polyKey[:], polyKey[:])
|
||||
s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes
|
||||
s.XORKeyStream(out, plaintext)
|
||||
s.SetCounter(1) // set the counter to 1, skipping 32 bytes
|
||||
s.XORKeyStream(ciphertext, plaintext)
|
||||
|
||||
polyInput := make([]byte, roundTo16(len(additionalData))+roundTo16(len(plaintext))+8+8)
|
||||
copy(polyInput, additionalData)
|
||||
copy(polyInput[roundTo16(len(additionalData)):], out[:len(plaintext)])
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-16:], uint64(len(additionalData)))
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-8:], uint64(len(plaintext)))
|
||||
|
||||
var tag [poly1305.TagSize]byte
|
||||
poly1305.Sum(&tag, polyInput, &polyKey)
|
||||
copy(out[len(plaintext):], tag[:])
|
||||
p := poly1305.New(&polyKey)
|
||||
writeWithPadding(p, additionalData)
|
||||
writeWithPadding(p, ciphertext)
|
||||
writeUint64(p, len(additionalData))
|
||||
writeUint64(p, len(plaintext))
|
||||
p.Sum(tag[:0])
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
var tag [poly1305.TagSize]byte
|
||||
copy(tag[:], ciphertext[len(ciphertext)-16:])
|
||||
tag := ciphertext[len(ciphertext)-16:]
|
||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||
|
||||
var polyKey, discardBuf [32]byte
|
||||
var polyKey [32]byte
|
||||
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
|
||||
s.XORKeyStream(polyKey[:], polyKey[:])
|
||||
s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes
|
||||
s.SetCounter(1) // set the counter to 1, skipping 32 bytes
|
||||
|
||||
polyInput := make([]byte, roundTo16(len(additionalData))+roundTo16(len(ciphertext))+8+8)
|
||||
copy(polyInput, additionalData)
|
||||
copy(polyInput[roundTo16(len(additionalData)):], ciphertext)
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-16:], uint64(len(additionalData)))
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-8:], uint64(len(ciphertext)))
|
||||
p := poly1305.New(&polyKey)
|
||||
writeWithPadding(p, additionalData)
|
||||
writeWithPadding(p, ciphertext)
|
||||
writeUint64(p, len(additionalData))
|
||||
writeUint64(p, len(ciphertext))
|
||||
|
||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||
if subtle.InexactOverlap(out, ciphertext) {
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
if !poly1305.Verify(&tag, polyInput, &polyKey) {
|
||||
if !p.Verify(tag) {
|
||||
for i := range out {
|
||||
out[i] = 0
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 !go1.7 gccgo appengine
|
||||
//go:build !amd64 || !gc || purego
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
mathrand "math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
@@ -220,36 +219,50 @@ func BenchmarkChacha20Poly1305(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
var key = make([]byte, KeySize)
|
||||
|
||||
func ExampleNewX() {
|
||||
aead, err := NewX(key)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to instantiate XChaCha20-Poly1305:", err)
|
||||
// key should be randomly generated or derived from a function like Argon2.
|
||||
key := make([]byte, KeySize)
|
||||
if _, err := cryptorand.Read(key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, msg := range []string{
|
||||
"Attack at dawn.",
|
||||
"The eagle has landed.",
|
||||
"Gophers, gophers, gophers everywhere!",
|
||||
} {
|
||||
// Encryption.
|
||||
nonce := make([]byte, NonceSizeX)
|
||||
aead, err := NewX(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Encryption.
|
||||
var encryptedMsg []byte
|
||||
{
|
||||
msg := []byte("Gophers, gophers, gophers everywhere!")
|
||||
|
||||
// Select a random nonce, and leave capacity for the ciphertext.
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())
|
||||
if _, err := cryptorand.Read(nonce); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ciphertext := aead.Seal(nil, nonce, []byte(msg), nil)
|
||||
|
||||
// Decryption.
|
||||
// Encrypt the message and append the ciphertext to the nonce.
|
||||
encryptedMsg = aead.Seal(nonce, nonce, msg, nil)
|
||||
}
|
||||
|
||||
// Decryption.
|
||||
{
|
||||
if len(encryptedMsg) < aead.NonceSize() {
|
||||
panic("ciphertext too short")
|
||||
}
|
||||
|
||||
// Split nonce and ciphertext.
|
||||
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
|
||||
|
||||
// Decrypt the message and check it wasn't tampered with.
|
||||
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to decrypt or authenticate message:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", plaintext)
|
||||
}
|
||||
|
||||
// Output: Attack at dawn.
|
||||
// The eagle has landed.
|
||||
// Gophers, gophers, gophers everywhere!
|
||||
// Output: Gophers, gophers, gophers everywhere!
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (*xchacha20poly1305) NonceSize() int {
|
||||
}
|
||||
|
||||
func (*xchacha20poly1305) Overhead() int {
|
||||
return 16
|
||||
return Overhead
|
||||
}
|
||||
|
||||
func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
|
||||
+119
-46
@@ -81,7 +81,7 @@ func (b *Builder) AddASN1BigInt(n *big.Int) {
|
||||
for i := range bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if bytes[0]&0x80 == 0 {
|
||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||
c.add(0xff)
|
||||
}
|
||||
c.add(bytes...)
|
||||
@@ -117,6 +117,19 @@ func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
|
||||
})
|
||||
}
|
||||
|
||||
// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
|
||||
func (b *Builder) AddASN1UTCTime(t time.Time) {
|
||||
b.AddASN1(asn1.UTCTime, func(c *Builder) {
|
||||
// As utilized by the X.509 profile, UTCTime can only
|
||||
// represent the years 1950 through 2049.
|
||||
if t.Year() < 1950 || t.Year() >= 2050 {
|
||||
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
|
||||
return
|
||||
}
|
||||
c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
|
||||
})
|
||||
}
|
||||
|
||||
// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
|
||||
// support BIT STRINGs that are not a whole number of bytes.
|
||||
func (b *Builder) AddASN1BitString(data []byte) {
|
||||
@@ -230,12 +243,12 @@ func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
|
||||
|
||||
// String
|
||||
|
||||
// ReadASN1Boolean decodes an ASN.1 INTEGER and converts it to a boolean
|
||||
// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean
|
||||
// representation into out and advances. It reports whether the read
|
||||
// was successful.
|
||||
func (s *String) ReadASN1Boolean(out *bool) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.INTEGER) || len(bytes) != 1 {
|
||||
if !s.ReadASN1(&bytes, asn1.BOOLEAN) || len(bytes) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -251,36 +264,35 @@ func (s *String) ReadASN1Boolean(out *bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
|
||||
|
||||
// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does
|
||||
// not point to an integer or to a big.Int, it panics. It reports whether the
|
||||
// read was successful.
|
||||
// not point to an integer, to a big.Int, or to a []byte it panics. Only
|
||||
// positive and zero values can be decoded into []byte, and they are returned as
|
||||
// big-endian binary values that share memory with s. Positive values will have
|
||||
// no leading zeroes, and zero will be returned as a single zero byte.
|
||||
// ReadASN1Integer reports whether the read was successful.
|
||||
func (s *String) ReadASN1Integer(out interface{}) bool {
|
||||
if reflect.TypeOf(out).Kind() != reflect.Ptr {
|
||||
panic("out is not a pointer")
|
||||
}
|
||||
switch reflect.ValueOf(out).Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
switch out := out.(type) {
|
||||
case *int, *int8, *int16, *int32, *int64:
|
||||
var i int64
|
||||
if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
|
||||
return false
|
||||
}
|
||||
reflect.ValueOf(out).Elem().SetInt(i)
|
||||
return true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
case *uint, *uint8, *uint16, *uint32, *uint64:
|
||||
var u uint64
|
||||
if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) {
|
||||
return false
|
||||
}
|
||||
reflect.ValueOf(out).Elem().SetUint(u)
|
||||
return true
|
||||
case reflect.Struct:
|
||||
if reflect.TypeOf(out).Elem() == bigIntType {
|
||||
return s.readASN1BigInt(out.(*big.Int))
|
||||
}
|
||||
case *big.Int:
|
||||
return s.readASN1BigInt(out)
|
||||
case *[]byte:
|
||||
return s.readASN1Bytes(out)
|
||||
default:
|
||||
panic("out does not point to an integer type")
|
||||
}
|
||||
panic("out does not point to an integer type")
|
||||
}
|
||||
|
||||
func checkASN1Integer(bytes []byte) bool {
|
||||
@@ -320,6 +332,21 @@ func (s *String) readASN1BigInt(out *big.Int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *String) readASN1Bytes(out *[]byte) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
|
||||
return false
|
||||
}
|
||||
if bytes[0]&0x80 == 0x80 {
|
||||
return false
|
||||
}
|
||||
for len(bytes) > 1 && bytes[0] == 0 {
|
||||
bytes = bytes[1:]
|
||||
}
|
||||
*out = bytes
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *String) readASN1Int64(out *int64) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) {
|
||||
@@ -394,11 +421,24 @@ func (s *String) ReadASN1Enum(out *int) bool {
|
||||
func (s *String) readBase128Int(out *int) bool {
|
||||
ret := 0
|
||||
for i := 0; len(*s) > 0; i++ {
|
||||
if i == 4 {
|
||||
if i == 5 {
|
||||
return false
|
||||
}
|
||||
// Avoid overflowing int on a 32-bit platform.
|
||||
// We don't want different behavior based on the architecture.
|
||||
if ret >= 1<<(31-7) {
|
||||
return false
|
||||
}
|
||||
ret <<= 7
|
||||
b := s.read(1)[0]
|
||||
|
||||
// ITU-T X.690, section 8.19.2:
|
||||
// The subidentifier shall be encoded in the fewest possible octets,
|
||||
// that is, the leading octet of the subidentifier shall not have the value 0x80.
|
||||
if i == 0 && b == 0x80 {
|
||||
return false
|
||||
}
|
||||
|
||||
ret |= int(b & 0x7f)
|
||||
if b&0x80 == 0 {
|
||||
*out = ret
|
||||
@@ -466,6 +506,45 @@ func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
const defaultUTCTimeFormatStr = "060102150405Z0700"
|
||||
|
||||
// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadASN1UTCTime(out *time.Time) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.UTCTime) {
|
||||
return false
|
||||
}
|
||||
t := string(bytes)
|
||||
|
||||
formatStr := defaultUTCTimeFormatStr
|
||||
var err error
|
||||
res, err := time.Parse(formatStr, t)
|
||||
if err != nil {
|
||||
// Fallback to minute precision if we can't parse second
|
||||
// precision. If we are following X.509 or X.690 we shouldn't
|
||||
// support this, but we do.
|
||||
formatStr = "0601021504Z0700"
|
||||
res, err = time.Parse(formatStr, t)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if serialized := res.Format(formatStr); serialized != t {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.Year() >= 2050 {
|
||||
// UTCTime interprets the low order digits 50-99 as 1950-99.
|
||||
// This only applies to its use in the X.509 profile.
|
||||
// See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
|
||||
res = res.AddDate(-100, 0, 0)
|
||||
}
|
||||
*out = res
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||
@@ -475,7 +554,7 @@ func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
paddingBits := uint8(bytes[0])
|
||||
paddingBits := bytes[0]
|
||||
bytes = bytes[1:]
|
||||
if paddingBits > 7 ||
|
||||
len(bytes) == 0 && paddingBits != 0 ||
|
||||
@@ -488,7 +567,7 @@ func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It is
|
||||
// ReadASN1BitStringAsBytes decodes an ASN.1 BIT STRING into out and advances. It is
|
||||
// an error if the BIT STRING is not a whole number of bytes. It reports
|
||||
// whether the read was successful.
|
||||
func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||
@@ -497,7 +576,7 @@ func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
paddingBits := uint8(bytes[0])
|
||||
paddingBits := bytes[0]
|
||||
if paddingBits != 0 {
|
||||
return false
|
||||
}
|
||||
@@ -597,34 +676,27 @@ func (s *String) SkipOptionalASN1(tag asn1.Tag) bool {
|
||||
return s.ReadASN1(&unused, tag)
|
||||
}
|
||||
|
||||
// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER
|
||||
// explicitly tagged with tag into out and advances. If no element with a
|
||||
// matching tag is present, it writes defaultValue into out instead. If out
|
||||
// does not point to an integer or to a big.Int, it panics. It reports
|
||||
// whether the read was successful.
|
||||
// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER explicitly
|
||||
// tagged with tag into out and advances. If no element with a matching tag is
|
||||
// present, it writes defaultValue into out instead. Otherwise, it behaves like
|
||||
// ReadASN1Integer.
|
||||
func (s *String) ReadOptionalASN1Integer(out interface{}, tag asn1.Tag, defaultValue interface{}) bool {
|
||||
if reflect.TypeOf(out).Kind() != reflect.Ptr {
|
||||
panic("out is not a pointer")
|
||||
}
|
||||
var present bool
|
||||
var i String
|
||||
if !s.ReadOptionalASN1(&i, &present, tag) {
|
||||
return false
|
||||
}
|
||||
if !present {
|
||||
switch reflect.ValueOf(out).Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
switch out.(type) {
|
||||
case *int, *int8, *int16, *int32, *int64,
|
||||
*uint, *uint8, *uint16, *uint32, *uint64, *[]byte:
|
||||
reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue))
|
||||
case reflect.Struct:
|
||||
if reflect.TypeOf(out).Elem() != bigIntType {
|
||||
panic("invalid integer type")
|
||||
}
|
||||
if reflect.TypeOf(defaultValue).Kind() != reflect.Ptr ||
|
||||
reflect.TypeOf(defaultValue).Elem() != bigIntType {
|
||||
case *big.Int:
|
||||
if defaultValue, ok := defaultValue.(*big.Int); ok {
|
||||
out.(*big.Int).Set(defaultValue)
|
||||
} else {
|
||||
panic("out points to big.Int, but defaultValue does not")
|
||||
}
|
||||
out.(*big.Int).Set(defaultValue.(*big.Int))
|
||||
default:
|
||||
panic("invalid integer type")
|
||||
}
|
||||
@@ -661,13 +733,14 @@ func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
|
||||
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
|
||||
// It reports whether the operation was successful.
|
||||
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
|
||||
// ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN
|
||||
// explicitly tagged with tag into out and advances. If no element with a
|
||||
// matching tag is present, it sets "out" to defaultValue instead. It reports
|
||||
// whether the read was successful.
|
||||
func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue bool) bool {
|
||||
var present bool
|
||||
var child String
|
||||
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
|
||||
if !s.ReadOptionalASN1(&child, &present, tag) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -676,7 +749,7 @@ func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
return s.ReadASN1Boolean(out)
|
||||
return child.ReadASN1Boolean(out)
|
||||
}
|
||||
|
||||
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
|
||||
|
||||
@@ -115,6 +115,28 @@ func TestReadASN1OptionalInteger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultBool = false
|
||||
|
||||
var optionalBoolTestData = []readASN1Test{
|
||||
{"empty", []byte{}, 0xa0, true, false},
|
||||
{"invalid", []byte{0xa1, 0x3, 0x1, 0x2, 0x7f}, 0xa1, false, defaultBool},
|
||||
{"missing", []byte{0xa1, 0x3, 0x1, 0x1, 0x7f}, 0xa0, true, defaultBool},
|
||||
{"present", []byte{0xa1, 0x3, 0x1, 0x1, 0xff}, 0xa1, true, true},
|
||||
}
|
||||
|
||||
func TestReadASN1OptionalBoolean(t *testing.T) {
|
||||
for _, test := range optionalBoolTestData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
in := String(test.in)
|
||||
var out bool
|
||||
ok := in.ReadOptionalASN1Boolean(&out, test.tag, defaultBool)
|
||||
if ok != test.ok || ok && out != test.out.(bool) {
|
||||
t.Errorf("in.ReadOptionalASN1Boolean() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1IntegerSigned(t *testing.T) {
|
||||
testData64 := []struct {
|
||||
in []byte
|
||||
@@ -154,6 +176,32 @@ func TestReadASN1IntegerSigned(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Repeat the same cases, reading into a []byte.
|
||||
t.Run("bytes", func(t *testing.T) {
|
||||
for i, test := range testData64 {
|
||||
in := String(test.in)
|
||||
var out []byte
|
||||
ok := in.ReadASN1Integer(&out)
|
||||
if test.out < 0 {
|
||||
if ok {
|
||||
t.Errorf("#%d: in.ReadASN1Integer(%d) = %v, want false", i, test.out, ok)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v, want true", i, ok)
|
||||
continue
|
||||
}
|
||||
n := new(big.Int).SetBytes(out).Int64()
|
||||
if n != test.out {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %x, want %d", i, ok, out, test.out)
|
||||
}
|
||||
if out[0] == 0 && len(out) > 1 {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v; out = %x, has leading zeroes", i, ok, out)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Repeat with the implicit-tagging functions
|
||||
t.Run("WithTag", func(t *testing.T) {
|
||||
for i, test := range testData64 {
|
||||
@@ -247,6 +295,10 @@ func TestASN1ObjectIdentifier(t *testing.T) {
|
||||
{[]byte{6, 4, 85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}},
|
||||
{[]byte{6, 3, 0x81, 0x34, 0x03}, true, []int{2, 100, 3}},
|
||||
{[]byte{6, 7, 85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}},
|
||||
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
|
||||
{[]byte{6, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
|
||||
}
|
||||
|
||||
for i, test := range testData {
|
||||
@@ -311,6 +363,37 @@ func TestReadASN1GeneralizedTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1UTCTime(t *testing.T) {
|
||||
testData := []struct {
|
||||
in string
|
||||
ok bool
|
||||
out time.Time
|
||||
}{
|
||||
{"000102030405Z", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"500102030405Z", true, time.Date(1950, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"490102030405Z", true, time.Date(2049, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"990102030405Z", true, time.Date(1999, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"250102030405Z", true, time.Date(2025, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"750102030405Z", true, time.Date(1975, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"000102030405+0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", 9*60*60+5*60))},
|
||||
{"000102030405-0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", -9*60*60-5*60))},
|
||||
{"0001020304Z", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.UTC)},
|
||||
{"5001020304Z", true, time.Date(1950, 01, 02, 03, 04, 00, 0, time.UTC)},
|
||||
{"0001020304+0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", 9*60*60+5*60))},
|
||||
{"0001020304-0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", -9*60*60-5*60))},
|
||||
{"000102030405Z0700", false, time.Time{}},
|
||||
{"000102030405", false, time.Time{}},
|
||||
}
|
||||
for i, test := range testData {
|
||||
in := String(append([]byte{byte(asn1.UTCTime), byte(len(test.in))}, test.in...))
|
||||
var out time.Time
|
||||
ok := in.ReadASN1UTCTime(&out)
|
||||
if ok != test.ok || ok && !reflect.DeepEqual(out, test.out) {
|
||||
t.Errorf("#%d: in.ReadASN1UTCTime() = %v, want %v; out = %q, want %q", i, ok, test.ok, out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1BitString(t *testing.T) {
|
||||
testData := []struct {
|
||||
in []byte
|
||||
@@ -335,3 +418,40 @@ func TestReadASN1BitString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddASN1BigInt(t *testing.T) {
|
||||
x := big.NewInt(-1)
|
||||
var b Builder
|
||||
b.AddASN1BigInt(x)
|
||||
got, err := b.Bytes()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error adding -1: %v", err)
|
||||
}
|
||||
s := String(got)
|
||||
var y big.Int
|
||||
ok := s.ReadASN1Integer(&y)
|
||||
if !ok || x.Cmp(&y) != 0 {
|
||||
t.Errorf("unexpected bytes %v, want %v", &y, x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1Boolean(t *testing.T) {
|
||||
testData := []struct {
|
||||
in []byte
|
||||
ok bool
|
||||
out bool
|
||||
}{
|
||||
{[]byte{}, false, false},
|
||||
{[]byte{0x01, 0x01, 0x00}, true, false},
|
||||
{[]byte{0x01, 0x01, 0xff}, true, true},
|
||||
{[]byte{0x01, 0x01, 0x01}, false, false},
|
||||
}
|
||||
for i, test := range testData {
|
||||
in := String(test.in)
|
||||
var out bool
|
||||
ok := in.ReadASN1Boolean(&out)
|
||||
if ok != test.ok || ok && (out != test.out) {
|
||||
t.Errorf("#%d: in.ReadASN1Boolean() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-10
@@ -95,6 +95,16 @@ func (b *Builder) AddUint32(v uint32) {
|
||||
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint48 appends a big-endian, 48-bit value to the byte string.
|
||||
func (b *Builder) AddUint48(v uint64) {
|
||||
b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint64 appends a big-endian, 64-bit value to the byte string.
|
||||
func (b *Builder) AddUint64(v uint64) {
|
||||
b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddBytes appends a sequence of bytes to the byte string.
|
||||
func (b *Builder) AddBytes(v []byte) {
|
||||
b.add(v...)
|
||||
@@ -106,13 +116,13 @@ func (b *Builder) AddBytes(v []byte) {
|
||||
// supplied to them. The child builder passed to the continuation can be used
|
||||
// to build the content of the length-prefixed sequence. For example:
|
||||
//
|
||||
// parent := cryptobyte.NewBuilder()
|
||||
// parent.AddUint8LengthPrefixed(func (child *Builder) {
|
||||
// child.AddUint8(42)
|
||||
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
|
||||
// grandchild.AddUint8(5)
|
||||
// })
|
||||
// })
|
||||
// parent := cryptobyte.NewBuilder()
|
||||
// parent.AddUint8LengthPrefixed(func (child *Builder) {
|
||||
// child.AddUint8(42)
|
||||
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
|
||||
// grandchild.AddUint8(5)
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// It is an error to write more bytes to the child than allowed by the reserved
|
||||
// length prefix. After the continuation returns, the child must be considered
|
||||
@@ -298,9 +308,9 @@ func (b *Builder) add(bytes ...byte) {
|
||||
b.result = append(b.result, bytes...)
|
||||
}
|
||||
|
||||
// Unwrite rolls back n bytes written directly to the Builder. An attempt by a
|
||||
// child builder passed to a continuation to unwrite bytes from its parent will
|
||||
// panic.
|
||||
// Unwrite rolls back non-negative n bytes written directly to the Builder.
|
||||
// An attempt by a child builder passed to a continuation to unwrite bytes
|
||||
// from its parent will panic.
|
||||
func (b *Builder) Unwrite(n int) {
|
||||
if b.err != nil {
|
||||
return
|
||||
@@ -312,6 +322,9 @@ func (b *Builder) Unwrite(n int) {
|
||||
if length < 0 {
|
||||
panic("cryptobyte: internal error")
|
||||
}
|
||||
if n < 0 {
|
||||
panic("cryptobyte: attempted to unwrite negative number of bytes")
|
||||
}
|
||||
if n > length {
|
||||
panic("cryptobyte: attempted to unwrite more than was written")
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func TestUint24(t *testing.T) {
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint32
|
||||
if !s.ReadUint24(&v) {
|
||||
t.Error("ReadUint8() = false, want true")
|
||||
t.Error("ReadUint24() = false, want true")
|
||||
}
|
||||
if v != 0xfffefd {
|
||||
t.Errorf("v = %d, want fffefd", v)
|
||||
@@ -169,7 +169,7 @@ func TestUint32(t *testing.T) {
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint32
|
||||
if !s.ReadUint32(&v) {
|
||||
t.Error("ReadUint8() = false, want true")
|
||||
t.Error("ReadUint32() = false, want true")
|
||||
}
|
||||
if v != 0xfffefdfc {
|
||||
t.Errorf("v = %x, want fffefdfc", v)
|
||||
@@ -179,6 +179,47 @@ func TestUint32(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint48(t *testing.T) {
|
||||
var b Builder
|
||||
var u uint64 = 0xfefcff3cfdfc
|
||||
b.AddUint48(u)
|
||||
if err := builderBytesEq(&b, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint48(&v) {
|
||||
t.Error("ReadUint48() = false, want true")
|
||||
}
|
||||
if v != u {
|
||||
t.Errorf("v = %x, want %x", v, u)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint64(0xf2fefefcff3cfdfc)
|
||||
if err := builderBytesEq(&b, 242, 254, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint64(&v) {
|
||||
t.Error("ReadUint64() = false, want true")
|
||||
}
|
||||
if v != 0xf2fefefcff3cfdfc {
|
||||
t.Errorf("v = %x, want f2fefefcff3cfdfc", v)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUMultiple(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint8(23)
|
||||
|
||||
@@ -81,6 +81,28 @@ func (s *String) ReadUint32(out *uint32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint48(out *uint64) bool {
|
||||
v := s.read(6)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5])
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint64(out *uint64) bool {
|
||||
v := s.read(8)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7])
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *String) readUnsigned(out *uint32, length int) bool {
|
||||
v := s.read(length)
|
||||
if v == nil {
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
// Package curve25519 provides an implementation of the X25519 function, which
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||
// See RFC 7748.
|
||||
//
|
||||
// Starting in Go 1.20, this package is a wrapper for the X25519 implementation
|
||||
// in the crypto/ecdh package.
|
||||
package curve25519 // import "golang.org/x/crypto/curve25519"
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ScalarMult sets dst to the product scalar * point.
|
||||
//
|
||||
// Deprecated: when provided a low-order point, ScalarMult will set dst to all
|
||||
@@ -27,7 +25,7 @@ func ScalarMult(dst, scalar, point *[32]byte) {
|
||||
// It is recommended to use the X25519 function with Basepoint instead, as
|
||||
// copying into fixed size arrays can lead to unexpected bugs.
|
||||
func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||
ScalarMult(dst, scalar, &basePoint)
|
||||
scalarBaseMult(dst, scalar)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -40,21 +38,10 @@ const (
|
||||
// Basepoint is the canonical Curve25519 generator.
|
||||
var Basepoint []byte
|
||||
|
||||
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
var basePoint = [32]byte{9}
|
||||
|
||||
func init() { Basepoint = basePoint[:] }
|
||||
|
||||
func checkBasepoint() {
|
||||
if subtle.ConstantTimeCompare(Basepoint, []byte{
|
||||
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}) != 1 {
|
||||
panic("curve25519: global Basepoint value was modified")
|
||||
}
|
||||
}
|
||||
|
||||
// X25519 returns the result of the scalar multiplication (scalar * point),
|
||||
// according to RFC 7748, Section 5. scalar, point and the return value are
|
||||
// slices of 32 bytes.
|
||||
@@ -70,26 +57,3 @@ func X25519(scalar, point []byte) ([]byte, error) {
|
||||
var dst [32]byte
|
||||
return x25519(&dst, scalar, point)
|
||||
}
|
||||
|
||||
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||
var in [32]byte
|
||||
if l := len(scalar); l != 32 {
|
||||
return nil, fmt.Errorf("bad scalar length: %d, expected %d", l, 32)
|
||||
}
|
||||
if l := len(point); l != 32 {
|
||||
return nil, fmt.Errorf("bad point length: %d, expected %d", l, 32)
|
||||
}
|
||||
copy(in[:], scalar)
|
||||
if &point[0] == &Basepoint[0] {
|
||||
checkBasepoint()
|
||||
ScalarBaseMult(dst, &in)
|
||||
} else {
|
||||
var base, zero [32]byte
|
||||
copy(base[:], point)
|
||||
ScalarMult(dst, &in, &base)
|
||||
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
|
||||
return nil, fmt.Errorf("bad input point: low order point")
|
||||
}
|
||||
}
|
||||
return dst[:], nil
|
||||
}
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// +build amd64,!gccgo,!appengine,!purego
|
||||
|
||||
package curve25519
|
||||
|
||||
// These functions are implemented in the .s files. The names of the functions
|
||||
// in the rest of the file are also taken from the SUPERCOP sources to help
|
||||
// people following along.
|
||||
|
||||
//go:noescape
|
||||
|
||||
func cswap(inout *[5]uint64, v uint64)
|
||||
|
||||
//go:noescape
|
||||
|
||||
func ladderstep(inout *[5][5]uint64)
|
||||
|
||||
//go:noescape
|
||||
|
||||
func freeze(inout *[5]uint64)
|
||||
|
||||
//go:noescape
|
||||
|
||||
func mul(dest, a, b *[5]uint64)
|
||||
|
||||
//go:noescape
|
||||
|
||||
func square(out, in *[5]uint64)
|
||||
|
||||
// mladder uses a Montgomery ladder to calculate (xr/zr) *= s.
|
||||
func mladder(xr, zr *[5]uint64, s *[32]byte) {
|
||||
var work [5][5]uint64
|
||||
|
||||
work[0] = *xr
|
||||
setint(&work[1], 1)
|
||||
setint(&work[2], 0)
|
||||
work[3] = *xr
|
||||
setint(&work[4], 1)
|
||||
|
||||
j := uint(6)
|
||||
var prevbit byte
|
||||
|
||||
for i := 31; i >= 0; i-- {
|
||||
for j < 8 {
|
||||
bit := ((*s)[i] >> j) & 1
|
||||
swap := bit ^ prevbit
|
||||
prevbit = bit
|
||||
cswap(&work[1], uint64(swap))
|
||||
ladderstep(&work)
|
||||
j--
|
||||
}
|
||||
j = 7
|
||||
}
|
||||
|
||||
*xr = work[1]
|
||||
*zr = work[2]
|
||||
}
|
||||
|
||||
func scalarMult(out, in, base *[32]byte) {
|
||||
var e [32]byte
|
||||
copy(e[:], (*in)[:])
|
||||
e[0] &= 248
|
||||
e[31] &= 127
|
||||
e[31] |= 64
|
||||
|
||||
var t, z [5]uint64
|
||||
unpack(&t, base)
|
||||
mladder(&t, &z, &e)
|
||||
invert(&z, &z)
|
||||
mul(&t, &t, &z)
|
||||
pack(out, &t)
|
||||
}
|
||||
|
||||
func setint(r *[5]uint64, v uint64) {
|
||||
r[0] = v
|
||||
r[1] = 0
|
||||
r[2] = 0
|
||||
r[3] = 0
|
||||
r[4] = 0
|
||||
}
|
||||
|
||||
// unpack sets r = x where r consists of 5, 51-bit limbs in little-endian
|
||||
// order.
|
||||
func unpack(r *[5]uint64, x *[32]byte) {
|
||||
r[0] = uint64(x[0]) |
|
||||
uint64(x[1])<<8 |
|
||||
uint64(x[2])<<16 |
|
||||
uint64(x[3])<<24 |
|
||||
uint64(x[4])<<32 |
|
||||
uint64(x[5])<<40 |
|
||||
uint64(x[6]&7)<<48
|
||||
|
||||
r[1] = uint64(x[6])>>3 |
|
||||
uint64(x[7])<<5 |
|
||||
uint64(x[8])<<13 |
|
||||
uint64(x[9])<<21 |
|
||||
uint64(x[10])<<29 |
|
||||
uint64(x[11])<<37 |
|
||||
uint64(x[12]&63)<<45
|
||||
|
||||
r[2] = uint64(x[12])>>6 |
|
||||
uint64(x[13])<<2 |
|
||||
uint64(x[14])<<10 |
|
||||
uint64(x[15])<<18 |
|
||||
uint64(x[16])<<26 |
|
||||
uint64(x[17])<<34 |
|
||||
uint64(x[18])<<42 |
|
||||
uint64(x[19]&1)<<50
|
||||
|
||||
r[3] = uint64(x[19])>>1 |
|
||||
uint64(x[20])<<7 |
|
||||
uint64(x[21])<<15 |
|
||||
uint64(x[22])<<23 |
|
||||
uint64(x[23])<<31 |
|
||||
uint64(x[24])<<39 |
|
||||
uint64(x[25]&15)<<47
|
||||
|
||||
r[4] = uint64(x[25])>>4 |
|
||||
uint64(x[26])<<4 |
|
||||
uint64(x[27])<<12 |
|
||||
uint64(x[28])<<20 |
|
||||
uint64(x[29])<<28 |
|
||||
uint64(x[30])<<36 |
|
||||
uint64(x[31]&127)<<44
|
||||
}
|
||||
|
||||
// pack sets out = x where out is the usual, little-endian form of the 5,
|
||||
// 51-bit limbs in x.
|
||||
func pack(out *[32]byte, x *[5]uint64) {
|
||||
t := *x
|
||||
freeze(&t)
|
||||
|
||||
out[0] = byte(t[0])
|
||||
out[1] = byte(t[0] >> 8)
|
||||
out[2] = byte(t[0] >> 16)
|
||||
out[3] = byte(t[0] >> 24)
|
||||
out[4] = byte(t[0] >> 32)
|
||||
out[5] = byte(t[0] >> 40)
|
||||
out[6] = byte(t[0] >> 48)
|
||||
|
||||
out[6] ^= byte(t[1]<<3) & 0xf8
|
||||
out[7] = byte(t[1] >> 5)
|
||||
out[8] = byte(t[1] >> 13)
|
||||
out[9] = byte(t[1] >> 21)
|
||||
out[10] = byte(t[1] >> 29)
|
||||
out[11] = byte(t[1] >> 37)
|
||||
out[12] = byte(t[1] >> 45)
|
||||
|
||||
out[12] ^= byte(t[2]<<6) & 0xc0
|
||||
out[13] = byte(t[2] >> 2)
|
||||
out[14] = byte(t[2] >> 10)
|
||||
out[15] = byte(t[2] >> 18)
|
||||
out[16] = byte(t[2] >> 26)
|
||||
out[17] = byte(t[2] >> 34)
|
||||
out[18] = byte(t[2] >> 42)
|
||||
out[19] = byte(t[2] >> 50)
|
||||
|
||||
out[19] ^= byte(t[3]<<1) & 0xfe
|
||||
out[20] = byte(t[3] >> 7)
|
||||
out[21] = byte(t[3] >> 15)
|
||||
out[22] = byte(t[3] >> 23)
|
||||
out[23] = byte(t[3] >> 31)
|
||||
out[24] = byte(t[3] >> 39)
|
||||
out[25] = byte(t[3] >> 47)
|
||||
|
||||
out[25] ^= byte(t[4]<<4) & 0xf0
|
||||
out[26] = byte(t[4] >> 4)
|
||||
out[27] = byte(t[4] >> 12)
|
||||
out[28] = byte(t[4] >> 20)
|
||||
out[29] = byte(t[4] >> 28)
|
||||
out[30] = byte(t[4] >> 36)
|
||||
out[31] = byte(t[4] >> 44)
|
||||
}
|
||||
|
||||
// invert calculates r = x^-1 mod p using Fermat's little theorem.
|
||||
func invert(r *[5]uint64, x *[5]uint64) {
|
||||
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t [5]uint64
|
||||
|
||||
square(&z2, x) /* 2 */
|
||||
square(&t, &z2) /* 4 */
|
||||
square(&t, &t) /* 8 */
|
||||
mul(&z9, &t, x) /* 9 */
|
||||
mul(&z11, &z9, &z2) /* 11 */
|
||||
square(&t, &z11) /* 22 */
|
||||
mul(&z2_5_0, &t, &z9) /* 2^5 - 2^0 = 31 */
|
||||
|
||||
square(&t, &z2_5_0) /* 2^6 - 2^1 */
|
||||
for i := 1; i < 5; i++ { /* 2^20 - 2^10 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&z2_10_0, &t, &z2_5_0) /* 2^10 - 2^0 */
|
||||
|
||||
square(&t, &z2_10_0) /* 2^11 - 2^1 */
|
||||
for i := 1; i < 10; i++ { /* 2^20 - 2^10 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&z2_20_0, &t, &z2_10_0) /* 2^20 - 2^0 */
|
||||
|
||||
square(&t, &z2_20_0) /* 2^21 - 2^1 */
|
||||
for i := 1; i < 20; i++ { /* 2^40 - 2^20 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&t, &t, &z2_20_0) /* 2^40 - 2^0 */
|
||||
|
||||
square(&t, &t) /* 2^41 - 2^1 */
|
||||
for i := 1; i < 10; i++ { /* 2^50 - 2^10 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&z2_50_0, &t, &z2_10_0) /* 2^50 - 2^0 */
|
||||
|
||||
square(&t, &z2_50_0) /* 2^51 - 2^1 */
|
||||
for i := 1; i < 50; i++ { /* 2^100 - 2^50 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&z2_100_0, &t, &z2_50_0) /* 2^100 - 2^0 */
|
||||
|
||||
square(&t, &z2_100_0) /* 2^101 - 2^1 */
|
||||
for i := 1; i < 100; i++ { /* 2^200 - 2^100 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&t, &t, &z2_100_0) /* 2^200 - 2^0 */
|
||||
|
||||
square(&t, &t) /* 2^201 - 2^1 */
|
||||
for i := 1; i < 50; i++ { /* 2^250 - 2^50 */
|
||||
square(&t, &t)
|
||||
}
|
||||
mul(&t, &t, &z2_50_0) /* 2^250 - 2^0 */
|
||||
|
||||
square(&t, &t) /* 2^251 - 2^1 */
|
||||
square(&t, &t) /* 2^252 - 2^2 */
|
||||
square(&t, &t) /* 2^253 - 2^3 */
|
||||
|
||||
square(&t, &t) /* 2^254 - 2^4 */
|
||||
|
||||
square(&t, &t) /* 2^255 - 2^5 */
|
||||
mul(r, &t, &z11) /* 2^255 - 21 */
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.20
|
||||
|
||||
package curve25519
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/curve25519/internal/field"
|
||||
)
|
||||
|
||||
func scalarMult(dst, scalar, point *[32]byte) {
|
||||
var e [32]byte
|
||||
|
||||
copy(e[:], scalar[:])
|
||||
e[0] &= 248
|
||||
e[31] &= 127
|
||||
e[31] |= 64
|
||||
|
||||
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
|
||||
x1.SetBytes(point[:])
|
||||
x2.One()
|
||||
x3.Set(&x1)
|
||||
z3.One()
|
||||
|
||||
swap := 0
|
||||
for pos := 254; pos >= 0; pos-- {
|
||||
b := e[pos/8] >> uint(pos&7)
|
||||
b &= 1
|
||||
swap ^= int(b)
|
||||
x2.Swap(&x3, swap)
|
||||
z2.Swap(&z3, swap)
|
||||
swap = int(b)
|
||||
|
||||
tmp0.Subtract(&x3, &z3)
|
||||
tmp1.Subtract(&x2, &z2)
|
||||
x2.Add(&x2, &z2)
|
||||
z2.Add(&x3, &z3)
|
||||
z3.Multiply(&tmp0, &x2)
|
||||
z2.Multiply(&z2, &tmp1)
|
||||
tmp0.Square(&tmp1)
|
||||
tmp1.Square(&x2)
|
||||
x3.Add(&z3, &z2)
|
||||
z2.Subtract(&z3, &z2)
|
||||
x2.Multiply(&tmp1, &tmp0)
|
||||
tmp1.Subtract(&tmp1, &tmp0)
|
||||
z2.Square(&z2)
|
||||
|
||||
z3.Mult32(&tmp1, 121666)
|
||||
x3.Square(&x3)
|
||||
tmp0.Add(&tmp0, &z3)
|
||||
z3.Multiply(&x1, &z2)
|
||||
z2.Multiply(&tmp1, &tmp0)
|
||||
}
|
||||
|
||||
x2.Swap(&x3, swap)
|
||||
z2.Swap(&z3, swap)
|
||||
|
||||
z2.Invert(&z2)
|
||||
x2.Multiply(&x2, &z2)
|
||||
copy(dst[:], x2.Bytes())
|
||||
}
|
||||
|
||||
func scalarBaseMult(dst, scalar *[32]byte) {
|
||||
checkBasepoint()
|
||||
scalarMult(dst, scalar, &basePoint)
|
||||
}
|
||||
|
||||
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||
var in [32]byte
|
||||
if l := len(scalar); l != 32 {
|
||||
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
|
||||
}
|
||||
if l := len(point); l != 32 {
|
||||
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
|
||||
}
|
||||
copy(in[:], scalar)
|
||||
if &point[0] == &Basepoint[0] {
|
||||
scalarBaseMult(dst, &in)
|
||||
} else {
|
||||
var base, zero [32]byte
|
||||
copy(base[:], point)
|
||||
scalarMult(dst, &in, &base)
|
||||
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
|
||||
return nil, errors.New("bad input point: low order point")
|
||||
}
|
||||
}
|
||||
return dst[:], nil
|
||||
}
|
||||
|
||||
func checkBasepoint() {
|
||||
if subtle.ConstantTimeCompare(Basepoint, []byte{
|
||||
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}) != 1 {
|
||||
panic("curve25519: global Basepoint value was modified")
|
||||
}
|
||||
}
|
||||
@@ -1,828 +0,0 @@
|
||||
// Copyright 2013 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 curve25519
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// This code is a port of the public domain, "ref10" implementation of
|
||||
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
|
||||
|
||||
// fieldElement represents an element of the field GF(2^255 - 19). An element
|
||||
// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
|
||||
// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
|
||||
// context.
|
||||
type fieldElement [10]int32
|
||||
|
||||
func feZero(fe *fieldElement) {
|
||||
for i := range fe {
|
||||
fe[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func feOne(fe *fieldElement) {
|
||||
feZero(fe)
|
||||
fe[0] = 1
|
||||
}
|
||||
|
||||
func feAdd(dst, a, b *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = a[i] + b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func feSub(dst, a, b *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = a[i] - b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func feCopy(dst, src *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = src[i]
|
||||
}
|
||||
}
|
||||
|
||||
// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0.
|
||||
//
|
||||
// Preconditions: b in {0,1}.
|
||||
func feCSwap(f, g *fieldElement, b int32) {
|
||||
b = -b
|
||||
for i := range f {
|
||||
t := b & (f[i] ^ g[i])
|
||||
f[i] ^= t
|
||||
g[i] ^= t
|
||||
}
|
||||
}
|
||||
|
||||
// load3 reads a 24-bit, little-endian value from in.
|
||||
func load3(in []byte) int64 {
|
||||
var r int64
|
||||
r = int64(in[0])
|
||||
r |= int64(in[1]) << 8
|
||||
r |= int64(in[2]) << 16
|
||||
return r
|
||||
}
|
||||
|
||||
// load4 reads a 32-bit, little-endian value from in.
|
||||
func load4(in []byte) int64 {
|
||||
return int64(binary.LittleEndian.Uint32(in))
|
||||
}
|
||||
|
||||
func feFromBytes(dst *fieldElement, src *[32]byte) {
|
||||
h0 := load4(src[:])
|
||||
h1 := load3(src[4:]) << 6
|
||||
h2 := load3(src[7:]) << 5
|
||||
h3 := load3(src[10:]) << 3
|
||||
h4 := load3(src[13:]) << 2
|
||||
h5 := load4(src[16:])
|
||||
h6 := load3(src[20:]) << 7
|
||||
h7 := load3(src[23:]) << 5
|
||||
h8 := load3(src[26:]) << 4
|
||||
h9 := (load3(src[29:]) & 0x7fffff) << 2
|
||||
|
||||
var carry [10]int64
|
||||
carry[9] = (h9 + 1<<24) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
carry[1] = (h1 + 1<<24) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[3] = (h3 + 1<<24) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[5] = (h5 + 1<<24) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
carry[7] = (h7 + 1<<24) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[0] = (h0 + 1<<25) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[2] = (h2 + 1<<25) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[4] = (h4 + 1<<25) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[6] = (h6 + 1<<25) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
carry[8] = (h8 + 1<<25) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
dst[0] = int32(h0)
|
||||
dst[1] = int32(h1)
|
||||
dst[2] = int32(h2)
|
||||
dst[3] = int32(h3)
|
||||
dst[4] = int32(h4)
|
||||
dst[5] = int32(h5)
|
||||
dst[6] = int32(h6)
|
||||
dst[7] = int32(h7)
|
||||
dst[8] = int32(h8)
|
||||
dst[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feToBytes marshals h to s.
|
||||
// Preconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
//
|
||||
// Write p=2^255-19; q=floor(h/p).
|
||||
// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
|
||||
//
|
||||
// Proof:
|
||||
// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
|
||||
// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4.
|
||||
//
|
||||
// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
|
||||
// Then 0<y<1.
|
||||
//
|
||||
// Write r=h-pq.
|
||||
// Have 0<=r<=p-1=2^255-20.
|
||||
// Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.
|
||||
//
|
||||
// Write x=r+19(2^-255)r+y.
|
||||
// Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
|
||||
//
|
||||
// Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
|
||||
// so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
|
||||
func feToBytes(s *[32]byte, h *fieldElement) {
|
||||
var carry [10]int32
|
||||
|
||||
q := (19*h[9] + (1 << 24)) >> 25
|
||||
q = (h[0] + q) >> 26
|
||||
q = (h[1] + q) >> 25
|
||||
q = (h[2] + q) >> 26
|
||||
q = (h[3] + q) >> 25
|
||||
q = (h[4] + q) >> 26
|
||||
q = (h[5] + q) >> 25
|
||||
q = (h[6] + q) >> 26
|
||||
q = (h[7] + q) >> 25
|
||||
q = (h[8] + q) >> 26
|
||||
q = (h[9] + q) >> 25
|
||||
|
||||
// Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20.
|
||||
h[0] += 19 * q
|
||||
// Goal: Output h-2^255 q, which is between 0 and 2^255-20.
|
||||
|
||||
carry[0] = h[0] >> 26
|
||||
h[1] += carry[0]
|
||||
h[0] -= carry[0] << 26
|
||||
carry[1] = h[1] >> 25
|
||||
h[2] += carry[1]
|
||||
h[1] -= carry[1] << 25
|
||||
carry[2] = h[2] >> 26
|
||||
h[3] += carry[2]
|
||||
h[2] -= carry[2] << 26
|
||||
carry[3] = h[3] >> 25
|
||||
h[4] += carry[3]
|
||||
h[3] -= carry[3] << 25
|
||||
carry[4] = h[4] >> 26
|
||||
h[5] += carry[4]
|
||||
h[4] -= carry[4] << 26
|
||||
carry[5] = h[5] >> 25
|
||||
h[6] += carry[5]
|
||||
h[5] -= carry[5] << 25
|
||||
carry[6] = h[6] >> 26
|
||||
h[7] += carry[6]
|
||||
h[6] -= carry[6] << 26
|
||||
carry[7] = h[7] >> 25
|
||||
h[8] += carry[7]
|
||||
h[7] -= carry[7] << 25
|
||||
carry[8] = h[8] >> 26
|
||||
h[9] += carry[8]
|
||||
h[8] -= carry[8] << 26
|
||||
carry[9] = h[9] >> 25
|
||||
h[9] -= carry[9] << 25
|
||||
// h10 = carry9
|
||||
|
||||
// Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
|
||||
// Have h[0]+...+2^230 h[9] between 0 and 2^255-1;
|
||||
// evidently 2^255 h10-2^255 q = 0.
|
||||
// Goal: Output h[0]+...+2^230 h[9].
|
||||
|
||||
s[0] = byte(h[0] >> 0)
|
||||
s[1] = byte(h[0] >> 8)
|
||||
s[2] = byte(h[0] >> 16)
|
||||
s[3] = byte((h[0] >> 24) | (h[1] << 2))
|
||||
s[4] = byte(h[1] >> 6)
|
||||
s[5] = byte(h[1] >> 14)
|
||||
s[6] = byte((h[1] >> 22) | (h[2] << 3))
|
||||
s[7] = byte(h[2] >> 5)
|
||||
s[8] = byte(h[2] >> 13)
|
||||
s[9] = byte((h[2] >> 21) | (h[3] << 5))
|
||||
s[10] = byte(h[3] >> 3)
|
||||
s[11] = byte(h[3] >> 11)
|
||||
s[12] = byte((h[3] >> 19) | (h[4] << 6))
|
||||
s[13] = byte(h[4] >> 2)
|
||||
s[14] = byte(h[4] >> 10)
|
||||
s[15] = byte(h[4] >> 18)
|
||||
s[16] = byte(h[5] >> 0)
|
||||
s[17] = byte(h[5] >> 8)
|
||||
s[18] = byte(h[5] >> 16)
|
||||
s[19] = byte((h[5] >> 24) | (h[6] << 1))
|
||||
s[20] = byte(h[6] >> 7)
|
||||
s[21] = byte(h[6] >> 15)
|
||||
s[22] = byte((h[6] >> 23) | (h[7] << 3))
|
||||
s[23] = byte(h[7] >> 5)
|
||||
s[24] = byte(h[7] >> 13)
|
||||
s[25] = byte((h[7] >> 21) | (h[8] << 4))
|
||||
s[26] = byte(h[8] >> 4)
|
||||
s[27] = byte(h[8] >> 12)
|
||||
s[28] = byte((h[8] >> 20) | (h[9] << 6))
|
||||
s[29] = byte(h[9] >> 2)
|
||||
s[30] = byte(h[9] >> 10)
|
||||
s[31] = byte(h[9] >> 18)
|
||||
}
|
||||
|
||||
// feMul calculates h = f * g
|
||||
// Can overlap h with f or g.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
//
|
||||
// Notes on implementation strategy:
|
||||
//
|
||||
// Using schoolbook multiplication.
|
||||
// Karatsuba would save a little in some cost models.
|
||||
//
|
||||
// Most multiplications by 2 and 19 are 32-bit precomputations;
|
||||
// cheaper than 64-bit postcomputations.
|
||||
//
|
||||
// There is one remaining multiplication by 19 in the carry chain;
|
||||
// one *19 precomputation can be merged into this,
|
||||
// but the resulting data flow is considerably less clean.
|
||||
//
|
||||
// There are 12 carries below.
|
||||
// 10 of them are 2-way parallelizable and vectorizable.
|
||||
// Can get away with 11 carries, but then data flow is much deeper.
|
||||
//
|
||||
// With tighter constraints on inputs can squeeze carries into int32.
|
||||
func feMul(h, f, g *fieldElement) {
|
||||
f0 := f[0]
|
||||
f1 := f[1]
|
||||
f2 := f[2]
|
||||
f3 := f[3]
|
||||
f4 := f[4]
|
||||
f5 := f[5]
|
||||
f6 := f[6]
|
||||
f7 := f[7]
|
||||
f8 := f[8]
|
||||
f9 := f[9]
|
||||
g0 := g[0]
|
||||
g1 := g[1]
|
||||
g2 := g[2]
|
||||
g3 := g[3]
|
||||
g4 := g[4]
|
||||
g5 := g[5]
|
||||
g6 := g[6]
|
||||
g7 := g[7]
|
||||
g8 := g[8]
|
||||
g9 := g[9]
|
||||
g1_19 := 19 * g1 // 1.4*2^29
|
||||
g2_19 := 19 * g2 // 1.4*2^30; still ok
|
||||
g3_19 := 19 * g3
|
||||
g4_19 := 19 * g4
|
||||
g5_19 := 19 * g5
|
||||
g6_19 := 19 * g6
|
||||
g7_19 := 19 * g7
|
||||
g8_19 := 19 * g8
|
||||
g9_19 := 19 * g9
|
||||
f1_2 := 2 * f1
|
||||
f3_2 := 2 * f3
|
||||
f5_2 := 2 * f5
|
||||
f7_2 := 2 * f7
|
||||
f9_2 := 2 * f9
|
||||
f0g0 := int64(f0) * int64(g0)
|
||||
f0g1 := int64(f0) * int64(g1)
|
||||
f0g2 := int64(f0) * int64(g2)
|
||||
f0g3 := int64(f0) * int64(g3)
|
||||
f0g4 := int64(f0) * int64(g4)
|
||||
f0g5 := int64(f0) * int64(g5)
|
||||
f0g6 := int64(f0) * int64(g6)
|
||||
f0g7 := int64(f0) * int64(g7)
|
||||
f0g8 := int64(f0) * int64(g8)
|
||||
f0g9 := int64(f0) * int64(g9)
|
||||
f1g0 := int64(f1) * int64(g0)
|
||||
f1g1_2 := int64(f1_2) * int64(g1)
|
||||
f1g2 := int64(f1) * int64(g2)
|
||||
f1g3_2 := int64(f1_2) * int64(g3)
|
||||
f1g4 := int64(f1) * int64(g4)
|
||||
f1g5_2 := int64(f1_2) * int64(g5)
|
||||
f1g6 := int64(f1) * int64(g6)
|
||||
f1g7_2 := int64(f1_2) * int64(g7)
|
||||
f1g8 := int64(f1) * int64(g8)
|
||||
f1g9_38 := int64(f1_2) * int64(g9_19)
|
||||
f2g0 := int64(f2) * int64(g0)
|
||||
f2g1 := int64(f2) * int64(g1)
|
||||
f2g2 := int64(f2) * int64(g2)
|
||||
f2g3 := int64(f2) * int64(g3)
|
||||
f2g4 := int64(f2) * int64(g4)
|
||||
f2g5 := int64(f2) * int64(g5)
|
||||
f2g6 := int64(f2) * int64(g6)
|
||||
f2g7 := int64(f2) * int64(g7)
|
||||
f2g8_19 := int64(f2) * int64(g8_19)
|
||||
f2g9_19 := int64(f2) * int64(g9_19)
|
||||
f3g0 := int64(f3) * int64(g0)
|
||||
f3g1_2 := int64(f3_2) * int64(g1)
|
||||
f3g2 := int64(f3) * int64(g2)
|
||||
f3g3_2 := int64(f3_2) * int64(g3)
|
||||
f3g4 := int64(f3) * int64(g4)
|
||||
f3g5_2 := int64(f3_2) * int64(g5)
|
||||
f3g6 := int64(f3) * int64(g6)
|
||||
f3g7_38 := int64(f3_2) * int64(g7_19)
|
||||
f3g8_19 := int64(f3) * int64(g8_19)
|
||||
f3g9_38 := int64(f3_2) * int64(g9_19)
|
||||
f4g0 := int64(f4) * int64(g0)
|
||||
f4g1 := int64(f4) * int64(g1)
|
||||
f4g2 := int64(f4) * int64(g2)
|
||||
f4g3 := int64(f4) * int64(g3)
|
||||
f4g4 := int64(f4) * int64(g4)
|
||||
f4g5 := int64(f4) * int64(g5)
|
||||
f4g6_19 := int64(f4) * int64(g6_19)
|
||||
f4g7_19 := int64(f4) * int64(g7_19)
|
||||
f4g8_19 := int64(f4) * int64(g8_19)
|
||||
f4g9_19 := int64(f4) * int64(g9_19)
|
||||
f5g0 := int64(f5) * int64(g0)
|
||||
f5g1_2 := int64(f5_2) * int64(g1)
|
||||
f5g2 := int64(f5) * int64(g2)
|
||||
f5g3_2 := int64(f5_2) * int64(g3)
|
||||
f5g4 := int64(f5) * int64(g4)
|
||||
f5g5_38 := int64(f5_2) * int64(g5_19)
|
||||
f5g6_19 := int64(f5) * int64(g6_19)
|
||||
f5g7_38 := int64(f5_2) * int64(g7_19)
|
||||
f5g8_19 := int64(f5) * int64(g8_19)
|
||||
f5g9_38 := int64(f5_2) * int64(g9_19)
|
||||
f6g0 := int64(f6) * int64(g0)
|
||||
f6g1 := int64(f6) * int64(g1)
|
||||
f6g2 := int64(f6) * int64(g2)
|
||||
f6g3 := int64(f6) * int64(g3)
|
||||
f6g4_19 := int64(f6) * int64(g4_19)
|
||||
f6g5_19 := int64(f6) * int64(g5_19)
|
||||
f6g6_19 := int64(f6) * int64(g6_19)
|
||||
f6g7_19 := int64(f6) * int64(g7_19)
|
||||
f6g8_19 := int64(f6) * int64(g8_19)
|
||||
f6g9_19 := int64(f6) * int64(g9_19)
|
||||
f7g0 := int64(f7) * int64(g0)
|
||||
f7g1_2 := int64(f7_2) * int64(g1)
|
||||
f7g2 := int64(f7) * int64(g2)
|
||||
f7g3_38 := int64(f7_2) * int64(g3_19)
|
||||
f7g4_19 := int64(f7) * int64(g4_19)
|
||||
f7g5_38 := int64(f7_2) * int64(g5_19)
|
||||
f7g6_19 := int64(f7) * int64(g6_19)
|
||||
f7g7_38 := int64(f7_2) * int64(g7_19)
|
||||
f7g8_19 := int64(f7) * int64(g8_19)
|
||||
f7g9_38 := int64(f7_2) * int64(g9_19)
|
||||
f8g0 := int64(f8) * int64(g0)
|
||||
f8g1 := int64(f8) * int64(g1)
|
||||
f8g2_19 := int64(f8) * int64(g2_19)
|
||||
f8g3_19 := int64(f8) * int64(g3_19)
|
||||
f8g4_19 := int64(f8) * int64(g4_19)
|
||||
f8g5_19 := int64(f8) * int64(g5_19)
|
||||
f8g6_19 := int64(f8) * int64(g6_19)
|
||||
f8g7_19 := int64(f8) * int64(g7_19)
|
||||
f8g8_19 := int64(f8) * int64(g8_19)
|
||||
f8g9_19 := int64(f8) * int64(g9_19)
|
||||
f9g0 := int64(f9) * int64(g0)
|
||||
f9g1_38 := int64(f9_2) * int64(g1_19)
|
||||
f9g2_19 := int64(f9) * int64(g2_19)
|
||||
f9g3_38 := int64(f9_2) * int64(g3_19)
|
||||
f9g4_19 := int64(f9) * int64(g4_19)
|
||||
f9g5_38 := int64(f9_2) * int64(g5_19)
|
||||
f9g6_19 := int64(f9) * int64(g6_19)
|
||||
f9g7_38 := int64(f9_2) * int64(g7_19)
|
||||
f9g8_19 := int64(f9) * int64(g8_19)
|
||||
f9g9_38 := int64(f9_2) * int64(g9_19)
|
||||
h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38
|
||||
h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19
|
||||
h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38
|
||||
h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19
|
||||
h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38
|
||||
h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19
|
||||
h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38
|
||||
h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19
|
||||
h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38
|
||||
h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0
|
||||
var carry [10]int64
|
||||
|
||||
// |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38))
|
||||
// i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8
|
||||
// |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19))
|
||||
// i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
// |h0| <= 2^25
|
||||
// |h4| <= 2^25
|
||||
// |h1| <= 1.51*2^58
|
||||
// |h5| <= 1.51*2^58
|
||||
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
// |h1| <= 2^24; from now on fits into int32
|
||||
// |h5| <= 2^24; from now on fits into int32
|
||||
// |h2| <= 1.21*2^59
|
||||
// |h6| <= 1.21*2^59
|
||||
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
// |h2| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h6| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h3| <= 1.51*2^58
|
||||
// |h7| <= 1.51*2^58
|
||||
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
// |h3| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h7| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h4| <= 1.52*2^33
|
||||
// |h8| <= 1.52*2^33
|
||||
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
// |h4| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h8| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h5| <= 1.01*2^24
|
||||
// |h9| <= 1.51*2^58
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
// |h9| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h0| <= 1.8*2^37
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
// |h0| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h1| <= 1.01*2^24
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feSquare calculates h = f*f. Can overlap h with f.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
func feSquare(h, f *fieldElement) {
|
||||
f0 := f[0]
|
||||
f1 := f[1]
|
||||
f2 := f[2]
|
||||
f3 := f[3]
|
||||
f4 := f[4]
|
||||
f5 := f[5]
|
||||
f6 := f[6]
|
||||
f7 := f[7]
|
||||
f8 := f[8]
|
||||
f9 := f[9]
|
||||
f0_2 := 2 * f0
|
||||
f1_2 := 2 * f1
|
||||
f2_2 := 2 * f2
|
||||
f3_2 := 2 * f3
|
||||
f4_2 := 2 * f4
|
||||
f5_2 := 2 * f5
|
||||
f6_2 := 2 * f6
|
||||
f7_2 := 2 * f7
|
||||
f5_38 := 38 * f5 // 1.31*2^30
|
||||
f6_19 := 19 * f6 // 1.31*2^30
|
||||
f7_38 := 38 * f7 // 1.31*2^30
|
||||
f8_19 := 19 * f8 // 1.31*2^30
|
||||
f9_38 := 38 * f9 // 1.31*2^30
|
||||
f0f0 := int64(f0) * int64(f0)
|
||||
f0f1_2 := int64(f0_2) * int64(f1)
|
||||
f0f2_2 := int64(f0_2) * int64(f2)
|
||||
f0f3_2 := int64(f0_2) * int64(f3)
|
||||
f0f4_2 := int64(f0_2) * int64(f4)
|
||||
f0f5_2 := int64(f0_2) * int64(f5)
|
||||
f0f6_2 := int64(f0_2) * int64(f6)
|
||||
f0f7_2 := int64(f0_2) * int64(f7)
|
||||
f0f8_2 := int64(f0_2) * int64(f8)
|
||||
f0f9_2 := int64(f0_2) * int64(f9)
|
||||
f1f1_2 := int64(f1_2) * int64(f1)
|
||||
f1f2_2 := int64(f1_2) * int64(f2)
|
||||
f1f3_4 := int64(f1_2) * int64(f3_2)
|
||||
f1f4_2 := int64(f1_2) * int64(f4)
|
||||
f1f5_4 := int64(f1_2) * int64(f5_2)
|
||||
f1f6_2 := int64(f1_2) * int64(f6)
|
||||
f1f7_4 := int64(f1_2) * int64(f7_2)
|
||||
f1f8_2 := int64(f1_2) * int64(f8)
|
||||
f1f9_76 := int64(f1_2) * int64(f9_38)
|
||||
f2f2 := int64(f2) * int64(f2)
|
||||
f2f3_2 := int64(f2_2) * int64(f3)
|
||||
f2f4_2 := int64(f2_2) * int64(f4)
|
||||
f2f5_2 := int64(f2_2) * int64(f5)
|
||||
f2f6_2 := int64(f2_2) * int64(f6)
|
||||
f2f7_2 := int64(f2_2) * int64(f7)
|
||||
f2f8_38 := int64(f2_2) * int64(f8_19)
|
||||
f2f9_38 := int64(f2) * int64(f9_38)
|
||||
f3f3_2 := int64(f3_2) * int64(f3)
|
||||
f3f4_2 := int64(f3_2) * int64(f4)
|
||||
f3f5_4 := int64(f3_2) * int64(f5_2)
|
||||
f3f6_2 := int64(f3_2) * int64(f6)
|
||||
f3f7_76 := int64(f3_2) * int64(f7_38)
|
||||
f3f8_38 := int64(f3_2) * int64(f8_19)
|
||||
f3f9_76 := int64(f3_2) * int64(f9_38)
|
||||
f4f4 := int64(f4) * int64(f4)
|
||||
f4f5_2 := int64(f4_2) * int64(f5)
|
||||
f4f6_38 := int64(f4_2) * int64(f6_19)
|
||||
f4f7_38 := int64(f4) * int64(f7_38)
|
||||
f4f8_38 := int64(f4_2) * int64(f8_19)
|
||||
f4f9_38 := int64(f4) * int64(f9_38)
|
||||
f5f5_38 := int64(f5) * int64(f5_38)
|
||||
f5f6_38 := int64(f5_2) * int64(f6_19)
|
||||
f5f7_76 := int64(f5_2) * int64(f7_38)
|
||||
f5f8_38 := int64(f5_2) * int64(f8_19)
|
||||
f5f9_76 := int64(f5_2) * int64(f9_38)
|
||||
f6f6_19 := int64(f6) * int64(f6_19)
|
||||
f6f7_38 := int64(f6) * int64(f7_38)
|
||||
f6f8_38 := int64(f6_2) * int64(f8_19)
|
||||
f6f9_38 := int64(f6) * int64(f9_38)
|
||||
f7f7_38 := int64(f7) * int64(f7_38)
|
||||
f7f8_38 := int64(f7_2) * int64(f8_19)
|
||||
f7f9_76 := int64(f7_2) * int64(f9_38)
|
||||
f8f8_19 := int64(f8) * int64(f8_19)
|
||||
f8f9_38 := int64(f8) * int64(f9_38)
|
||||
f9f9_38 := int64(f9) * int64(f9_38)
|
||||
h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
||||
h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
|
||||
h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
|
||||
h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
|
||||
h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
|
||||
h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
|
||||
h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
|
||||
h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
|
||||
h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
|
||||
h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
|
||||
var carry [10]int64
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feMul121666 calculates h = f * 121666. Can overlap h with f.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
func feMul121666(h, f *fieldElement) {
|
||||
h0 := int64(f[0]) * 121666
|
||||
h1 := int64(f[1]) * 121666
|
||||
h2 := int64(f[2]) * 121666
|
||||
h3 := int64(f[3]) * 121666
|
||||
h4 := int64(f[4]) * 121666
|
||||
h5 := int64(f[5]) * 121666
|
||||
h6 := int64(f[6]) * 121666
|
||||
h7 := int64(f[7]) * 121666
|
||||
h8 := int64(f[8]) * 121666
|
||||
h9 := int64(f[9]) * 121666
|
||||
var carry [10]int64
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feInvert sets out = z^-1.
|
||||
func feInvert(out, z *fieldElement) {
|
||||
var t0, t1, t2, t3 fieldElement
|
||||
var i int
|
||||
|
||||
feSquare(&t0, z)
|
||||
for i = 1; i < 1; i++ {
|
||||
feSquare(&t0, &t0)
|
||||
}
|
||||
feSquare(&t1, &t0)
|
||||
for i = 1; i < 2; i++ {
|
||||
feSquare(&t1, &t1)
|
||||
}
|
||||
feMul(&t1, z, &t1)
|
||||
feMul(&t0, &t0, &t1)
|
||||
feSquare(&t2, &t0)
|
||||
for i = 1; i < 1; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t1, &t2)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 5; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 10; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t2, &t2, &t1)
|
||||
feSquare(&t3, &t2)
|
||||
for i = 1; i < 20; i++ {
|
||||
feSquare(&t3, &t3)
|
||||
}
|
||||
feMul(&t2, &t3, &t2)
|
||||
feSquare(&t2, &t2)
|
||||
for i = 1; i < 10; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 50; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t2, &t2, &t1)
|
||||
feSquare(&t3, &t2)
|
||||
for i = 1; i < 100; i++ {
|
||||
feSquare(&t3, &t3)
|
||||
}
|
||||
feMul(&t2, &t3, &t2)
|
||||
feSquare(&t2, &t2)
|
||||
for i = 1; i < 50; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t1, &t1)
|
||||
for i = 1; i < 5; i++ {
|
||||
feSquare(&t1, &t1)
|
||||
}
|
||||
feMul(out, &t1, &t0)
|
||||
}
|
||||
|
||||
func scalarMultGeneric(out, in, base *[32]byte) {
|
||||
var e [32]byte
|
||||
|
||||
copy(e[:], in[:])
|
||||
e[0] &= 248
|
||||
e[31] &= 127
|
||||
e[31] |= 64
|
||||
|
||||
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
|
||||
feFromBytes(&x1, base)
|
||||
feOne(&x2)
|
||||
feCopy(&x3, &x1)
|
||||
feOne(&z3)
|
||||
|
||||
swap := int32(0)
|
||||
for pos := 254; pos >= 0; pos-- {
|
||||
b := e[pos/8] >> uint(pos&7)
|
||||
b &= 1
|
||||
swap ^= int32(b)
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
swap = int32(b)
|
||||
|
||||
feSub(&tmp0, &x3, &z3)
|
||||
feSub(&tmp1, &x2, &z2)
|
||||
feAdd(&x2, &x2, &z2)
|
||||
feAdd(&z2, &x3, &z3)
|
||||
feMul(&z3, &tmp0, &x2)
|
||||
feMul(&z2, &z2, &tmp1)
|
||||
feSquare(&tmp0, &tmp1)
|
||||
feSquare(&tmp1, &x2)
|
||||
feAdd(&x3, &z3, &z2)
|
||||
feSub(&z2, &z3, &z2)
|
||||
feMul(&x2, &tmp1, &tmp0)
|
||||
feSub(&tmp1, &tmp1, &tmp0)
|
||||
feSquare(&z2, &z2)
|
||||
feMul121666(&z3, &tmp1)
|
||||
feSquare(&x3, &x3)
|
||||
feAdd(&tmp0, &tmp0, &z3)
|
||||
feMul(&z3, &x1, &z2)
|
||||
feMul(&z2, &tmp1, &tmp0)
|
||||
}
|
||||
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
|
||||
feInvert(&z2, &z2)
|
||||
feMul(&x2, &x2, &z2)
|
||||
feToBytes(out, &x2)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package curve25519
|
||||
|
||||
import "crypto/ecdh"
|
||||
|
||||
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||
curve := ecdh.X25519()
|
||||
pub, err := curve.NewPublicKey(point)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := curve.NewPrivateKey(scalar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := priv.ECDH(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(dst[:], out)
|
||||
return dst[:], nil
|
||||
}
|
||||
|
||||
func scalarMult(dst, scalar, point *[32]byte) {
|
||||
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
|
||||
// The only error condition for x25519 when the inputs are 32 bytes long
|
||||
// is if the output would have been the all-zero value.
|
||||
for i := range dst {
|
||||
dst[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scalarBaseMult(dst, scalar *[32]byte) {
|
||||
curve := ecdh.X25519()
|
||||
priv, err := curve.NewPrivateKey(scalar[:])
|
||||
if err != nil {
|
||||
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
|
||||
}
|
||||
copy(dst[:], priv.PublicKey().Bytes())
|
||||
}
|
||||
@@ -1,11 +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.
|
||||
|
||||
// +build !amd64 gccgo appengine purego
|
||||
|
||||
package curve25519
|
||||
|
||||
func scalarMult(out, in, base *[32]byte) {
|
||||
scalarMultGeneric(out, in, base)
|
||||
}
|
||||
@@ -2,13 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519
|
||||
package curve25519_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||
@@ -19,25 +21,25 @@ func TestX25519Basepoint(t *testing.T) {
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
var err error
|
||||
x, err = X25519(x, Basepoint)
|
||||
x, err = curve25519.X25519(x, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("%x", x)
|
||||
result := hex.EncodeToString(x)
|
||||
if result != expectedHex {
|
||||
t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowOrderPoints(t *testing.T) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, p := range lowOrderPoints {
|
||||
out, err := X25519(scalar, p)
|
||||
out, err := curve25519.X25519(scalar, p)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error, got nil", i)
|
||||
}
|
||||
@@ -48,11 +50,10 @@ func TestLowOrderPoints(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTestVectors(t *testing.T) {
|
||||
t.Run("Generic", func(t *testing.T) { testTestVectors(t, scalarMultGeneric) })
|
||||
t.Run("Native", func(t *testing.T) { testTestVectors(t, ScalarMult) })
|
||||
t.Run("Legacy", func(t *testing.T) { testTestVectors(t, curve25519.ScalarMult) })
|
||||
t.Run("X25519", func(t *testing.T) {
|
||||
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
||||
out, err := X25519(scalar[:], point[:])
|
||||
out, err := curve25519.X25519(scalar[:], point[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -89,22 +90,53 @@ func TestHighBitIgnored(t *testing.T) {
|
||||
var hi0, hi1 [32]byte
|
||||
|
||||
u[31] &= 0x7f
|
||||
ScalarMult(&hi0, &s, &u)
|
||||
curve25519.ScalarMult(&hi0, &s, &u)
|
||||
|
||||
u[31] |= 0x80
|
||||
ScalarMult(&hi1, &s, &u)
|
||||
curve25519.ScalarMult(&hi1, &s, &u)
|
||||
|
||||
if !bytes.Equal(hi0[:], hi1[:]) {
|
||||
t.Errorf("high bit of group point should not affect result")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||
var in, out [32]byte
|
||||
in[0] = 1
|
||||
var benchmarkSink byte
|
||||
|
||||
b.SetBytes(32)
|
||||
func BenchmarkX25519Basepoint(b *testing.B) {
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ScalarBaseMult(&out, &in)
|
||||
out, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkX25519(b *testing.B) {
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
point, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out, err := curve25519.X25519(scalar, point)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
This package is kept in sync with crypto/ed25519/internal/edwards25519/field in
|
||||
the standard library.
|
||||
|
||||
If there are any changes in the standard library that need to be synced to this
|
||||
package, run sync.sh. It will not overwrite any local changes made since the
|
||||
previous sync, so it's ok to land changes in this package first, and then sync
|
||||
to the standard library later.
|
||||
@@ -0,0 +1,298 @@
|
||||
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
. "github.com/mmcloughlin/avo/gotypes"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
. "github.com/mmcloughlin/avo/reg"
|
||||
|
||||
// Ensure "go mod tidy" doesn't remove the golang.org/x/crypto module
|
||||
// dependency, which is necessary to access the field.Element type.
|
||||
_ "golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
//go:generate go run . -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field
|
||||
|
||||
func main() {
|
||||
Package("golang.org/x/crypto/curve25519/internal/field")
|
||||
ConstraintExpr("amd64,gc,!purego")
|
||||
feMul()
|
||||
feSquare()
|
||||
Generate()
|
||||
}
|
||||
|
||||
type namedComponent struct {
|
||||
Component
|
||||
name string
|
||||
}
|
||||
|
||||
func (c namedComponent) String() string { return c.name }
|
||||
|
||||
type uint128 struct {
|
||||
name string
|
||||
hi, lo GPVirtual
|
||||
}
|
||||
|
||||
func (c uint128) String() string { return c.name }
|
||||
|
||||
func feSquare() {
|
||||
TEXT("feSquare", NOSPLIT, "func(out, a *Element)")
|
||||
Doc("feSquare sets out = a * a. It works like feSquareGeneric.")
|
||||
Pragma("noescape")
|
||||
|
||||
a := Dereference(Param("a"))
|
||||
l0 := namedComponent{a.Field("l0"), "l0"}
|
||||
l1 := namedComponent{a.Field("l1"), "l1"}
|
||||
l2 := namedComponent{a.Field("l2"), "l2"}
|
||||
l3 := namedComponent{a.Field("l3"), "l3"}
|
||||
l4 := namedComponent{a.Field("l4"), "l4"}
|
||||
|
||||
// r0 = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||
r0 := uint128{"r0", GP64(), GP64()}
|
||||
mul64(r0, 1, l0, l0)
|
||||
addMul64(r0, 38, l1, l4)
|
||||
addMul64(r0, 38, l2, l3)
|
||||
|
||||
// r1 = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||
r1 := uint128{"r1", GP64(), GP64()}
|
||||
mul64(r1, 2, l0, l1)
|
||||
addMul64(r1, 38, l2, l4)
|
||||
addMul64(r1, 19, l3, l3)
|
||||
|
||||
// r2 = = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||
r2 := uint128{"r2", GP64(), GP64()}
|
||||
mul64(r2, 2, l0, l2)
|
||||
addMul64(r2, 1, l1, l1)
|
||||
addMul64(r2, 38, l3, l4)
|
||||
|
||||
// r3 = = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||
r3 := uint128{"r3", GP64(), GP64()}
|
||||
mul64(r3, 2, l0, l3)
|
||||
addMul64(r3, 2, l1, l2)
|
||||
addMul64(r3, 19, l4, l4)
|
||||
|
||||
// r4 = = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||
r4 := uint128{"r4", GP64(), GP64()}
|
||||
mul64(r4, 2, l0, l4)
|
||||
addMul64(r4, 2, l1, l3)
|
||||
addMul64(r4, 1, l2, l2)
|
||||
|
||||
Comment("First reduction chain")
|
||||
maskLow51Bits := GP64()
|
||||
MOVQ(Imm((1<<51)-1), maskLow51Bits)
|
||||
c0, r0lo := shiftRightBy51(&r0)
|
||||
c1, r1lo := shiftRightBy51(&r1)
|
||||
c2, r2lo := shiftRightBy51(&r2)
|
||||
c3, r3lo := shiftRightBy51(&r3)
|
||||
c4, r4lo := shiftRightBy51(&r4)
|
||||
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
|
||||
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
|
||||
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
|
||||
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
|
||||
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
|
||||
|
||||
Comment("Second reduction chain (carryPropagate)")
|
||||
// c0 = r0 >> 51
|
||||
MOVQ(r0lo, c0)
|
||||
SHRQ(Imm(51), c0)
|
||||
// c1 = r1 >> 51
|
||||
MOVQ(r1lo, c1)
|
||||
SHRQ(Imm(51), c1)
|
||||
// c2 = r2 >> 51
|
||||
MOVQ(r2lo, c2)
|
||||
SHRQ(Imm(51), c2)
|
||||
// c3 = r3 >> 51
|
||||
MOVQ(r3lo, c3)
|
||||
SHRQ(Imm(51), c3)
|
||||
// c4 = r4 >> 51
|
||||
MOVQ(r4lo, c4)
|
||||
SHRQ(Imm(51), c4)
|
||||
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
|
||||
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
|
||||
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
|
||||
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
|
||||
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
|
||||
|
||||
Comment("Store output")
|
||||
out := Dereference(Param("out"))
|
||||
Store(r0lo, out.Field("l0"))
|
||||
Store(r1lo, out.Field("l1"))
|
||||
Store(r2lo, out.Field("l2"))
|
||||
Store(r3lo, out.Field("l3"))
|
||||
Store(r4lo, out.Field("l4"))
|
||||
|
||||
RET()
|
||||
}
|
||||
|
||||
func feMul() {
|
||||
TEXT("feMul", NOSPLIT, "func(out, a, b *Element)")
|
||||
Doc("feMul sets out = a * b. It works like feMulGeneric.")
|
||||
Pragma("noescape")
|
||||
|
||||
a := Dereference(Param("a"))
|
||||
a0 := namedComponent{a.Field("l0"), "a0"}
|
||||
a1 := namedComponent{a.Field("l1"), "a1"}
|
||||
a2 := namedComponent{a.Field("l2"), "a2"}
|
||||
a3 := namedComponent{a.Field("l3"), "a3"}
|
||||
a4 := namedComponent{a.Field("l4"), "a4"}
|
||||
|
||||
b := Dereference(Param("b"))
|
||||
b0 := namedComponent{b.Field("l0"), "b0"}
|
||||
b1 := namedComponent{b.Field("l1"), "b1"}
|
||||
b2 := namedComponent{b.Field("l2"), "b2"}
|
||||
b3 := namedComponent{b.Field("l3"), "b3"}
|
||||
b4 := namedComponent{b.Field("l4"), "b4"}
|
||||
|
||||
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||
r0 := uint128{"r0", GP64(), GP64()}
|
||||
mul64(r0, 1, a0, b0)
|
||||
addMul64(r0, 19, a1, b4)
|
||||
addMul64(r0, 19, a2, b3)
|
||||
addMul64(r0, 19, a3, b2)
|
||||
addMul64(r0, 19, a4, b1)
|
||||
|
||||
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||
r1 := uint128{"r1", GP64(), GP64()}
|
||||
mul64(r1, 1, a0, b1)
|
||||
addMul64(r1, 1, a1, b0)
|
||||
addMul64(r1, 19, a2, b4)
|
||||
addMul64(r1, 19, a3, b3)
|
||||
addMul64(r1, 19, a4, b2)
|
||||
|
||||
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||
r2 := uint128{"r2", GP64(), GP64()}
|
||||
mul64(r2, 1, a0, b2)
|
||||
addMul64(r2, 1, a1, b1)
|
||||
addMul64(r2, 1, a2, b0)
|
||||
addMul64(r2, 19, a3, b4)
|
||||
addMul64(r2, 19, a4, b3)
|
||||
|
||||
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||
r3 := uint128{"r3", GP64(), GP64()}
|
||||
mul64(r3, 1, a0, b3)
|
||||
addMul64(r3, 1, a1, b2)
|
||||
addMul64(r3, 1, a2, b1)
|
||||
addMul64(r3, 1, a3, b0)
|
||||
addMul64(r3, 19, a4, b4)
|
||||
|
||||
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||
r4 := uint128{"r4", GP64(), GP64()}
|
||||
mul64(r4, 1, a0, b4)
|
||||
addMul64(r4, 1, a1, b3)
|
||||
addMul64(r4, 1, a2, b2)
|
||||
addMul64(r4, 1, a3, b1)
|
||||
addMul64(r4, 1, a4, b0)
|
||||
|
||||
Comment("First reduction chain")
|
||||
maskLow51Bits := GP64()
|
||||
MOVQ(Imm((1<<51)-1), maskLow51Bits)
|
||||
c0, r0lo := shiftRightBy51(&r0)
|
||||
c1, r1lo := shiftRightBy51(&r1)
|
||||
c2, r2lo := shiftRightBy51(&r2)
|
||||
c3, r3lo := shiftRightBy51(&r3)
|
||||
c4, r4lo := shiftRightBy51(&r4)
|
||||
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
|
||||
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
|
||||
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
|
||||
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
|
||||
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
|
||||
|
||||
Comment("Second reduction chain (carryPropagate)")
|
||||
// c0 = r0 >> 51
|
||||
MOVQ(r0lo, c0)
|
||||
SHRQ(Imm(51), c0)
|
||||
// c1 = r1 >> 51
|
||||
MOVQ(r1lo, c1)
|
||||
SHRQ(Imm(51), c1)
|
||||
// c2 = r2 >> 51
|
||||
MOVQ(r2lo, c2)
|
||||
SHRQ(Imm(51), c2)
|
||||
// c3 = r3 >> 51
|
||||
MOVQ(r3lo, c3)
|
||||
SHRQ(Imm(51), c3)
|
||||
// c4 = r4 >> 51
|
||||
MOVQ(r4lo, c4)
|
||||
SHRQ(Imm(51), c4)
|
||||
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
|
||||
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
|
||||
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
|
||||
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
|
||||
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
|
||||
|
||||
Comment("Store output")
|
||||
out := Dereference(Param("out"))
|
||||
Store(r0lo, out.Field("l0"))
|
||||
Store(r1lo, out.Field("l1"))
|
||||
Store(r2lo, out.Field("l2"))
|
||||
Store(r3lo, out.Field("l3"))
|
||||
Store(r4lo, out.Field("l4"))
|
||||
|
||||
RET()
|
||||
}
|
||||
|
||||
// mul64 sets r to i * aX * bX.
|
||||
func mul64(r uint128, i int, aX, bX namedComponent) {
|
||||
switch i {
|
||||
case 1:
|
||||
Comment(fmt.Sprintf("%s = %s×%s", r, aX, bX))
|
||||
Load(aX, RAX)
|
||||
case 2:
|
||||
Comment(fmt.Sprintf("%s = 2×%s×%s", r, aX, bX))
|
||||
Load(aX, RAX)
|
||||
SHLQ(Imm(1), RAX)
|
||||
default:
|
||||
panic("unsupported i value")
|
||||
}
|
||||
MULQ(mustAddr(bX)) // RDX, RAX = RAX * bX
|
||||
MOVQ(RAX, r.lo)
|
||||
MOVQ(RDX, r.hi)
|
||||
}
|
||||
|
||||
// addMul64 sets r to r + i * aX * bX.
|
||||
func addMul64(r uint128, i uint64, aX, bX namedComponent) {
|
||||
switch i {
|
||||
case 1:
|
||||
Comment(fmt.Sprintf("%s += %s×%s", r, aX, bX))
|
||||
Load(aX, RAX)
|
||||
default:
|
||||
Comment(fmt.Sprintf("%s += %d×%s×%s", r, i, aX, bX))
|
||||
IMUL3Q(Imm(i), Load(aX, GP64()), RAX)
|
||||
}
|
||||
MULQ(mustAddr(bX)) // RDX, RAX = RAX * bX
|
||||
ADDQ(RAX, r.lo)
|
||||
ADCQ(RDX, r.hi)
|
||||
}
|
||||
|
||||
// shiftRightBy51 returns r >> 51 and r.lo.
|
||||
//
|
||||
// After this function is called, the uint128 may not be used anymore.
|
||||
func shiftRightBy51(r *uint128) (out, lo GPVirtual) {
|
||||
out = r.hi
|
||||
lo = r.lo
|
||||
SHLQ(Imm(64-51), r.lo, r.hi)
|
||||
r.lo, r.hi = nil, nil // make sure the uint128 is unusable
|
||||
return
|
||||
}
|
||||
|
||||
// maskAndAdd sets r = r&mask + c*i.
|
||||
func maskAndAdd(r, mask, c GPVirtual, i uint64) {
|
||||
ANDQ(mask, r)
|
||||
if i != 1 {
|
||||
IMUL3Q(Imm(i), c, c)
|
||||
}
|
||||
ADDQ(c, r)
|
||||
}
|
||||
|
||||
func mustAddr(c Component) Op {
|
||||
b, err := c.Resolve()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b.Addr
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
module asm
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.5.0
|
||||
golang.org/x/crypto v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
)
|
||||
|
||||
replace golang.org/x/crypto v0.1.0 => ../../../..
|
||||
@@ -0,0 +1,51 @@
|
||||
github.com/mmcloughlin/avo v0.5.0 h1:nAco9/aI9Lg2kiuROBY6BhCI/z0t5jEvJfjWbL8qXLU=
|
||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
@@ -0,0 +1,416 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
// Package field implements fast arithmetic modulo 2^255-19.
|
||||
package field
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Element represents an element of the field GF(2^255-19). Note that this
|
||||
// is not a cryptographically secure group, and should only be used to interact
|
||||
// with edwards25519.Point coordinates.
|
||||
//
|
||||
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||
// are allowed to alias.
|
||||
//
|
||||
// The zero value is a valid zero element.
|
||||
type Element struct {
|
||||
// An element t represents the integer
|
||||
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
|
||||
//
|
||||
// Between operations, all limbs are expected to be lower than 2^52.
|
||||
l0 uint64
|
||||
l1 uint64
|
||||
l2 uint64
|
||||
l3 uint64
|
||||
l4 uint64
|
||||
}
|
||||
|
||||
const maskLow51Bits uint64 = (1 << 51) - 1
|
||||
|
||||
var feZero = &Element{0, 0, 0, 0, 0}
|
||||
|
||||
// Zero sets v = 0, and returns v.
|
||||
func (v *Element) Zero() *Element {
|
||||
*v = *feZero
|
||||
return v
|
||||
}
|
||||
|
||||
var feOne = &Element{1, 0, 0, 0, 0}
|
||||
|
||||
// One sets v = 1, and returns v.
|
||||
func (v *Element) One() *Element {
|
||||
*v = *feOne
|
||||
return v
|
||||
}
|
||||
|
||||
// reduce reduces v modulo 2^255 - 19 and returns it.
|
||||
func (v *Element) reduce() *Element {
|
||||
v.carryPropagate()
|
||||
|
||||
// After the light reduction we now have a field element representation
|
||||
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
|
||||
|
||||
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
|
||||
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
|
||||
c := (v.l0 + 19) >> 51
|
||||
c = (v.l1 + c) >> 51
|
||||
c = (v.l2 + c) >> 51
|
||||
c = (v.l3 + c) >> 51
|
||||
c = (v.l4 + c) >> 51
|
||||
|
||||
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
|
||||
// effectively applying the reduction identity to the carry.
|
||||
v.l0 += 19 * c
|
||||
|
||||
v.l1 += v.l0 >> 51
|
||||
v.l0 = v.l0 & maskLow51Bits
|
||||
v.l2 += v.l1 >> 51
|
||||
v.l1 = v.l1 & maskLow51Bits
|
||||
v.l3 += v.l2 >> 51
|
||||
v.l2 = v.l2 & maskLow51Bits
|
||||
v.l4 += v.l3 >> 51
|
||||
v.l3 = v.l3 & maskLow51Bits
|
||||
// no additional carry
|
||||
v.l4 = v.l4 & maskLow51Bits
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Add sets v = a + b, and returns v.
|
||||
func (v *Element) Add(a, b *Element) *Element {
|
||||
v.l0 = a.l0 + b.l0
|
||||
v.l1 = a.l1 + b.l1
|
||||
v.l2 = a.l2 + b.l2
|
||||
v.l3 = a.l3 + b.l3
|
||||
v.l4 = a.l4 + b.l4
|
||||
// Using the generic implementation here is actually faster than the
|
||||
// assembly. Probably because the body of this function is so simple that
|
||||
// the compiler can figure out better optimizations by inlining the carry
|
||||
// propagation. TODO
|
||||
return v.carryPropagateGeneric()
|
||||
}
|
||||
|
||||
// Subtract sets v = a - b, and returns v.
|
||||
func (v *Element) Subtract(a, b *Element) *Element {
|
||||
// We first add 2 * p, to guarantee the subtraction won't underflow, and
|
||||
// then subtract b (which can be up to 2^255 + 2^13 * 19).
|
||||
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
|
||||
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
|
||||
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
|
||||
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
|
||||
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
|
||||
return v.carryPropagate()
|
||||
}
|
||||
|
||||
// Negate sets v = -a, and returns v.
|
||||
func (v *Element) Negate(a *Element) *Element {
|
||||
return v.Subtract(feZero, a)
|
||||
}
|
||||
|
||||
// Invert sets v = 1/z mod p, and returns v.
|
||||
//
|
||||
// If z == 0, Invert returns v = 0.
|
||||
func (v *Element) Invert(z *Element) *Element {
|
||||
// Inversion is implemented as exponentiation with exponent p − 2. It uses the
|
||||
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
|
||||
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
|
||||
|
||||
z2.Square(z) // 2
|
||||
t.Square(&z2) // 4
|
||||
t.Square(&t) // 8
|
||||
z9.Multiply(&t, z) // 9
|
||||
z11.Multiply(&z9, &z2) // 11
|
||||
t.Square(&z11) // 22
|
||||
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
|
||||
|
||||
t.Square(&z2_5_0) // 2^6 - 2^1
|
||||
for i := 0; i < 4; i++ {
|
||||
t.Square(&t) // 2^10 - 2^5
|
||||
}
|
||||
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
|
||||
|
||||
t.Square(&z2_10_0) // 2^11 - 2^1
|
||||
for i := 0; i < 9; i++ {
|
||||
t.Square(&t) // 2^20 - 2^10
|
||||
}
|
||||
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
|
||||
|
||||
t.Square(&z2_20_0) // 2^21 - 2^1
|
||||
for i := 0; i < 19; i++ {
|
||||
t.Square(&t) // 2^40 - 2^20
|
||||
}
|
||||
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
|
||||
|
||||
t.Square(&t) // 2^41 - 2^1
|
||||
for i := 0; i < 9; i++ {
|
||||
t.Square(&t) // 2^50 - 2^10
|
||||
}
|
||||
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
|
||||
|
||||
t.Square(&z2_50_0) // 2^51 - 2^1
|
||||
for i := 0; i < 49; i++ {
|
||||
t.Square(&t) // 2^100 - 2^50
|
||||
}
|
||||
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
|
||||
|
||||
t.Square(&z2_100_0) // 2^101 - 2^1
|
||||
for i := 0; i < 99; i++ {
|
||||
t.Square(&t) // 2^200 - 2^100
|
||||
}
|
||||
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
|
||||
|
||||
t.Square(&t) // 2^201 - 2^1
|
||||
for i := 0; i < 49; i++ {
|
||||
t.Square(&t) // 2^250 - 2^50
|
||||
}
|
||||
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
|
||||
|
||||
t.Square(&t) // 2^251 - 2^1
|
||||
t.Square(&t) // 2^252 - 2^2
|
||||
t.Square(&t) // 2^253 - 2^3
|
||||
t.Square(&t) // 2^254 - 2^4
|
||||
t.Square(&t) // 2^255 - 2^5
|
||||
|
||||
return v.Multiply(&t, &z11) // 2^255 - 21
|
||||
}
|
||||
|
||||
// Set sets v = a, and returns v.
|
||||
func (v *Element) Set(a *Element) *Element {
|
||||
*v = *a
|
||||
return v
|
||||
}
|
||||
|
||||
// SetBytes sets v to x, which must be a 32-byte little-endian encoding.
|
||||
//
|
||||
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||
// are accepted. Note that this is laxer than specified by RFC 8032.
|
||||
func (v *Element) SetBytes(x []byte) *Element {
|
||||
if len(x) != 32 {
|
||||
panic("edwards25519: invalid field element input size")
|
||||
}
|
||||
|
||||
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
|
||||
v.l0 = binary.LittleEndian.Uint64(x[0:8])
|
||||
v.l0 &= maskLow51Bits
|
||||
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
|
||||
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
|
||||
v.l1 &= maskLow51Bits
|
||||
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
|
||||
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
|
||||
v.l2 &= maskLow51Bits
|
||||
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
|
||||
v.l3 &= maskLow51Bits
|
||||
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
|
||||
v.l4 &= maskLow51Bits
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Bytes returns the canonical 32-byte little-endian encoding of v.
|
||||
func (v *Element) Bytes() []byte {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap.
|
||||
var out [32]byte
|
||||
return v.bytes(&out)
|
||||
}
|
||||
|
||||
func (v *Element) bytes(out *[32]byte) []byte {
|
||||
t := *v
|
||||
t.reduce()
|
||||
|
||||
var buf [8]byte
|
||||
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
|
||||
bitsOffset := i * 51
|
||||
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
|
||||
for i, bb := range buf {
|
||||
off := bitsOffset/8 + i
|
||||
if off >= len(out) {
|
||||
break
|
||||
}
|
||||
out[off] |= bb
|
||||
}
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// Equal returns 1 if v and u are equal, and 0 otherwise.
|
||||
func (v *Element) Equal(u *Element) int {
|
||||
sa, sv := u.Bytes(), v.Bytes()
|
||||
return subtle.ConstantTimeCompare(sa, sv)
|
||||
}
|
||||
|
||||
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
|
||||
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
|
||||
|
||||
// Select sets v to a if cond == 1, and to b if cond == 0.
|
||||
func (v *Element) Select(a, b *Element, cond int) *Element {
|
||||
m := mask64Bits(cond)
|
||||
v.l0 = (m & a.l0) | (^m & b.l0)
|
||||
v.l1 = (m & a.l1) | (^m & b.l1)
|
||||
v.l2 = (m & a.l2) | (^m & b.l2)
|
||||
v.l3 = (m & a.l3) | (^m & b.l3)
|
||||
v.l4 = (m & a.l4) | (^m & b.l4)
|
||||
return v
|
||||
}
|
||||
|
||||
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
|
||||
func (v *Element) Swap(u *Element, cond int) {
|
||||
m := mask64Bits(cond)
|
||||
t := m & (v.l0 ^ u.l0)
|
||||
v.l0 ^= t
|
||||
u.l0 ^= t
|
||||
t = m & (v.l1 ^ u.l1)
|
||||
v.l1 ^= t
|
||||
u.l1 ^= t
|
||||
t = m & (v.l2 ^ u.l2)
|
||||
v.l2 ^= t
|
||||
u.l2 ^= t
|
||||
t = m & (v.l3 ^ u.l3)
|
||||
v.l3 ^= t
|
||||
u.l3 ^= t
|
||||
t = m & (v.l4 ^ u.l4)
|
||||
v.l4 ^= t
|
||||
u.l4 ^= t
|
||||
}
|
||||
|
||||
// IsNegative returns 1 if v is negative, and 0 otherwise.
|
||||
func (v *Element) IsNegative() int {
|
||||
return int(v.Bytes()[0] & 1)
|
||||
}
|
||||
|
||||
// Absolute sets v to |u|, and returns v.
|
||||
func (v *Element) Absolute(u *Element) *Element {
|
||||
return v.Select(new(Element).Negate(u), u, u.IsNegative())
|
||||
}
|
||||
|
||||
// Multiply sets v = x * y, and returns v.
|
||||
func (v *Element) Multiply(x, y *Element) *Element {
|
||||
feMul(v, x, y)
|
||||
return v
|
||||
}
|
||||
|
||||
// Square sets v = x * x, and returns v.
|
||||
func (v *Element) Square(x *Element) *Element {
|
||||
feSquare(v, x)
|
||||
return v
|
||||
}
|
||||
|
||||
// Mult32 sets v = x * y, and returns v.
|
||||
func (v *Element) Mult32(x *Element, y uint32) *Element {
|
||||
x0lo, x0hi := mul51(x.l0, y)
|
||||
x1lo, x1hi := mul51(x.l1, y)
|
||||
x2lo, x2hi := mul51(x.l2, y)
|
||||
x3lo, x3hi := mul51(x.l3, y)
|
||||
x4lo, x4hi := mul51(x.l4, y)
|
||||
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
|
||||
v.l1 = x1lo + x0hi
|
||||
v.l2 = x2lo + x1hi
|
||||
v.l3 = x3lo + x2hi
|
||||
v.l4 = x4lo + x3hi
|
||||
// The hi portions are going to be only 32 bits, plus any previous excess,
|
||||
// so we can skip the carry propagation.
|
||||
return v
|
||||
}
|
||||
|
||||
// mul51 returns lo + hi * 2⁵¹ = a * b.
|
||||
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
|
||||
mh, ml := bits.Mul64(a, uint64(b))
|
||||
lo = ml & maskLow51Bits
|
||||
hi = (mh << 13) | (ml >> 51)
|
||||
return
|
||||
}
|
||||
|
||||
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
|
||||
func (v *Element) Pow22523(x *Element) *Element {
|
||||
var t0, t1, t2 Element
|
||||
|
||||
t0.Square(x) // x^2
|
||||
t1.Square(&t0) // x^4
|
||||
t1.Square(&t1) // x^8
|
||||
t1.Multiply(x, &t1) // x^9
|
||||
t0.Multiply(&t0, &t1) // x^11
|
||||
t0.Square(&t0) // x^22
|
||||
t0.Multiply(&t1, &t0) // x^31
|
||||
t1.Square(&t0) // x^62
|
||||
for i := 1; i < 5; i++ { // x^992
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
|
||||
t1.Square(&t0) // 2^11 - 2
|
||||
for i := 1; i < 10; i++ { // 2^20 - 2^10
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t1.Multiply(&t1, &t0) // 2^20 - 1
|
||||
t2.Square(&t1) // 2^21 - 2
|
||||
for i := 1; i < 20; i++ { // 2^40 - 2^20
|
||||
t2.Square(&t2)
|
||||
}
|
||||
t1.Multiply(&t2, &t1) // 2^40 - 1
|
||||
t1.Square(&t1) // 2^41 - 2
|
||||
for i := 1; i < 10; i++ { // 2^50 - 2^10
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // 2^50 - 1
|
||||
t1.Square(&t0) // 2^51 - 2
|
||||
for i := 1; i < 50; i++ { // 2^100 - 2^50
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t1.Multiply(&t1, &t0) // 2^100 - 1
|
||||
t2.Square(&t1) // 2^101 - 2
|
||||
for i := 1; i < 100; i++ { // 2^200 - 2^100
|
||||
t2.Square(&t2)
|
||||
}
|
||||
t1.Multiply(&t2, &t1) // 2^200 - 1
|
||||
t1.Square(&t1) // 2^201 - 2
|
||||
for i := 1; i < 50; i++ { // 2^250 - 2^50
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // 2^250 - 1
|
||||
t0.Square(&t0) // 2^251 - 2
|
||||
t0.Square(&t0) // 2^252 - 4
|
||||
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
|
||||
}
|
||||
|
||||
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
|
||||
var sqrtM1 = &Element{1718705420411056, 234908883556509,
|
||||
2233514472574048, 2117202627021982, 765476049583133}
|
||||
|
||||
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
|
||||
//
|
||||
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
|
||||
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||
// and returns r and 0.
|
||||
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
|
||||
var a, b Element
|
||||
|
||||
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||
v2 := a.Square(v)
|
||||
uv3 := b.Multiply(u, b.Multiply(v2, v))
|
||||
uv7 := a.Multiply(uv3, a.Square(v2))
|
||||
r.Multiply(uv3, r.Pow22523(uv7))
|
||||
|
||||
check := a.Multiply(v, a.Square(r)) // check = v * r^2
|
||||
|
||||
uNeg := b.Negate(u)
|
||||
correctSignSqrt := check.Equal(u)
|
||||
flippedSignSqrt := check.Equal(uNeg)
|
||||
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
|
||||
|
||||
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
|
||||
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
|
||||
|
||||
r.Absolute(r) // Choose the nonnegative square root.
|
||||
return r, correctSignSqrt | flippedSignSqrt
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package field
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func checkAliasingOneArg(f func(v, x *Element) *Element) func(v, x Element) bool {
|
||||
return func(v, x Element) bool {
|
||||
x1, v1 := x, x
|
||||
|
||||
// Calculate a reference f(x) without aliasing.
|
||||
if out := f(&v, &x); out != &v && isInBounds(out) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test aliasing the argument and the receiver.
|
||||
if out := f(&v1, &v1); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure the arguments was not modified.
|
||||
return x == x1
|
||||
}
|
||||
}
|
||||
|
||||
func checkAliasingTwoArgs(f func(v, x, y *Element) *Element) func(v, x, y Element) bool {
|
||||
return func(v, x, y Element) bool {
|
||||
x1, y1, v1 := x, y, Element{}
|
||||
|
||||
// Calculate a reference f(x, y) without aliasing.
|
||||
if out := f(&v, &x, &y); out != &v && isInBounds(out) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test aliasing the first argument and the receiver.
|
||||
v1 = x
|
||||
if out := f(&v1, &v1, &y); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
// Test aliasing the second argument and the receiver.
|
||||
v1 = y
|
||||
if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
|
||||
// Calculate a reference f(x, x) without aliasing.
|
||||
if out := f(&v, &x, &x); out != &v {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test aliasing the first argument and the receiver.
|
||||
v1 = x
|
||||
if out := f(&v1, &v1, &x); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
// Test aliasing the second argument and the receiver.
|
||||
v1 = x
|
||||
if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
// Test aliasing both arguments and the receiver.
|
||||
v1 = x
|
||||
if out := f(&v1, &v1, &v1); out != &v1 || v1 != v {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure the arguments were not modified.
|
||||
return x == x1 && y == y1
|
||||
}
|
||||
}
|
||||
|
||||
// TestAliasing checks that receivers and arguments can alias each other without
|
||||
// leading to incorrect results. That is, it ensures that it's safe to write
|
||||
//
|
||||
// v.Invert(v)
|
||||
//
|
||||
// or
|
||||
//
|
||||
// v.Add(v, v)
|
||||
//
|
||||
// without any of the inputs getting clobbered by the output being written.
|
||||
func TestAliasing(t *testing.T) {
|
||||
type target struct {
|
||||
name string
|
||||
oneArgF func(v, x *Element) *Element
|
||||
twoArgsF func(v, x, y *Element) *Element
|
||||
}
|
||||
for _, tt := range []target{
|
||||
{name: "Absolute", oneArgF: (*Element).Absolute},
|
||||
{name: "Invert", oneArgF: (*Element).Invert},
|
||||
{name: "Negate", oneArgF: (*Element).Negate},
|
||||
{name: "Set", oneArgF: (*Element).Set},
|
||||
{name: "Square", oneArgF: (*Element).Square},
|
||||
{name: "Multiply", twoArgsF: (*Element).Multiply},
|
||||
{name: "Add", twoArgsF: (*Element).Add},
|
||||
{name: "Subtract", twoArgsF: (*Element).Subtract},
|
||||
{
|
||||
name: "Select0",
|
||||
twoArgsF: func(v, x, y *Element) *Element {
|
||||
return (*Element).Select(v, x, y, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Select1",
|
||||
twoArgsF: func(v, x, y *Element) *Element {
|
||||
return (*Element).Select(v, x, y, 1)
|
||||
},
|
||||
},
|
||||
} {
|
||||
var err error
|
||||
switch {
|
||||
case tt.oneArgF != nil:
|
||||
err = quick.Check(checkAliasingOneArg(tt.oneArgF), &quick.Config{MaxCountScale: 1 << 8})
|
||||
case tt.twoArgsF != nil:
|
||||
err = quick.Check(checkAliasingTwoArgs(tt.twoArgsF), &quick.Config{MaxCountScale: 1 << 8})
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%v: %v", tt.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
package field
|
||||
|
||||
// feMul sets out = a * b. It works like feMulGeneric.
|
||||
//
|
||||
//go:noescape
|
||||
func feMul(out *Element, a *Element, b *Element)
|
||||
|
||||
// feSquare sets out = a * a. It works like feSquareGeneric.
|
||||
//
|
||||
//go:noescape
|
||||
func feSquare(out *Element, a *Element)
|
||||
@@ -0,0 +1,378 @@
|
||||
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func feMul(out *Element, a *Element, b *Element)
|
||||
TEXT ·feMul(SB), NOSPLIT, $0-24
|
||||
MOVQ a+8(FP), CX
|
||||
MOVQ b+16(FP), BX
|
||||
|
||||
// r0 = a0×b0
|
||||
MOVQ (CX), AX
|
||||
MULQ (BX)
|
||||
MOVQ AX, DI
|
||||
MOVQ DX, SI
|
||||
|
||||
// r0 += 19×a1×b4
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a2×b3
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a3×b2
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a4×b1
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r1 = a0×b1
|
||||
MOVQ (CX), AX
|
||||
MULQ 8(BX)
|
||||
MOVQ AX, R9
|
||||
MOVQ DX, R8
|
||||
|
||||
// r1 += a1×b0
|
||||
MOVQ 8(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a2×b4
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a3×b3
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a4×b2
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r2 = a0×b2
|
||||
MOVQ (CX), AX
|
||||
MULQ 16(BX)
|
||||
MOVQ AX, R11
|
||||
MOVQ DX, R10
|
||||
|
||||
// r2 += a1×b1
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += a2×b0
|
||||
MOVQ 16(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += 19×a3×b4
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += 19×a4×b3
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r3 = a0×b3
|
||||
MOVQ (CX), AX
|
||||
MULQ 24(BX)
|
||||
MOVQ AX, R13
|
||||
MOVQ DX, R12
|
||||
|
||||
// r3 += a1×b2
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += a2×b1
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += a3×b0
|
||||
MOVQ 24(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += 19×a4×b4
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r4 = a0×b4
|
||||
MOVQ (CX), AX
|
||||
MULQ 32(BX)
|
||||
MOVQ AX, R15
|
||||
MOVQ DX, R14
|
||||
|
||||
// r4 += a1×b3
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a2×b2
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a3×b1
|
||||
MOVQ 24(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a4×b0
|
||||
MOVQ 32(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// First reduction chain
|
||||
MOVQ $0x0007ffffffffffff, AX
|
||||
SHLQ $0x0d, DI, SI
|
||||
SHLQ $0x0d, R9, R8
|
||||
SHLQ $0x0d, R11, R10
|
||||
SHLQ $0x0d, R13, R12
|
||||
SHLQ $0x0d, R15, R14
|
||||
ANDQ AX, DI
|
||||
IMUL3Q $0x13, R14, R14
|
||||
ADDQ R14, DI
|
||||
ANDQ AX, R9
|
||||
ADDQ SI, R9
|
||||
ANDQ AX, R11
|
||||
ADDQ R8, R11
|
||||
ANDQ AX, R13
|
||||
ADDQ R10, R13
|
||||
ANDQ AX, R15
|
||||
ADDQ R12, R15
|
||||
|
||||
// Second reduction chain (carryPropagate)
|
||||
MOVQ DI, SI
|
||||
SHRQ $0x33, SI
|
||||
MOVQ R9, R8
|
||||
SHRQ $0x33, R8
|
||||
MOVQ R11, R10
|
||||
SHRQ $0x33, R10
|
||||
MOVQ R13, R12
|
||||
SHRQ $0x33, R12
|
||||
MOVQ R15, R14
|
||||
SHRQ $0x33, R14
|
||||
ANDQ AX, DI
|
||||
IMUL3Q $0x13, R14, R14
|
||||
ADDQ R14, DI
|
||||
ANDQ AX, R9
|
||||
ADDQ SI, R9
|
||||
ANDQ AX, R11
|
||||
ADDQ R8, R11
|
||||
ANDQ AX, R13
|
||||
ADDQ R10, R13
|
||||
ANDQ AX, R15
|
||||
ADDQ R12, R15
|
||||
|
||||
// Store output
|
||||
MOVQ out+0(FP), AX
|
||||
MOVQ DI, (AX)
|
||||
MOVQ R9, 8(AX)
|
||||
MOVQ R11, 16(AX)
|
||||
MOVQ R13, 24(AX)
|
||||
MOVQ R15, 32(AX)
|
||||
RET
|
||||
|
||||
// func feSquare(out *Element, a *Element)
|
||||
TEXT ·feSquare(SB), NOSPLIT, $0-16
|
||||
MOVQ a+8(FP), CX
|
||||
|
||||
// r0 = l0×l0
|
||||
MOVQ (CX), AX
|
||||
MULQ (CX)
|
||||
MOVQ AX, SI
|
||||
MOVQ DX, BX
|
||||
|
||||
// r0 += 38×l1×l4
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, SI
|
||||
ADCQ DX, BX
|
||||
|
||||
// r0 += 38×l2×l3
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, SI
|
||||
ADCQ DX, BX
|
||||
|
||||
// r1 = 2×l0×l1
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 8(CX)
|
||||
MOVQ AX, R8
|
||||
MOVQ DX, DI
|
||||
|
||||
// r1 += 38×l2×l4
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R8
|
||||
ADCQ DX, DI
|
||||
|
||||
// r1 += 19×l3×l3
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, R8
|
||||
ADCQ DX, DI
|
||||
|
||||
// r2 = 2×l0×l2
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 16(CX)
|
||||
MOVQ AX, R10
|
||||
MOVQ DX, R9
|
||||
|
||||
// r2 += l1×l1
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 8(CX)
|
||||
ADDQ AX, R10
|
||||
ADCQ DX, R9
|
||||
|
||||
// r2 += 38×l3×l4
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R10
|
||||
ADCQ DX, R9
|
||||
|
||||
// r3 = 2×l0×l3
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 24(CX)
|
||||
MOVQ AX, R12
|
||||
MOVQ DX, R11
|
||||
|
||||
// r3 += 2×l1×l2
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x02, AX, AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX, R12
|
||||
ADCQ DX, R11
|
||||
|
||||
// r3 += 19×l4×l4
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R12
|
||||
ADCQ DX, R11
|
||||
|
||||
// r4 = 2×l0×l4
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 32(CX)
|
||||
MOVQ AX, R14
|
||||
MOVQ DX, R13
|
||||
|
||||
// r4 += 2×l1×l3
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x02, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, R14
|
||||
ADCQ DX, R13
|
||||
|
||||
// r4 += l2×l2
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX, R14
|
||||
ADCQ DX, R13
|
||||
|
||||
// First reduction chain
|
||||
MOVQ $0x0007ffffffffffff, AX
|
||||
SHLQ $0x0d, SI, BX
|
||||
SHLQ $0x0d, R8, DI
|
||||
SHLQ $0x0d, R10, R9
|
||||
SHLQ $0x0d, R12, R11
|
||||
SHLQ $0x0d, R14, R13
|
||||
ANDQ AX, SI
|
||||
IMUL3Q $0x13, R13, R13
|
||||
ADDQ R13, SI
|
||||
ANDQ AX, R8
|
||||
ADDQ BX, R8
|
||||
ANDQ AX, R10
|
||||
ADDQ DI, R10
|
||||
ANDQ AX, R12
|
||||
ADDQ R9, R12
|
||||
ANDQ AX, R14
|
||||
ADDQ R11, R14
|
||||
|
||||
// Second reduction chain (carryPropagate)
|
||||
MOVQ SI, BX
|
||||
SHRQ $0x33, BX
|
||||
MOVQ R8, DI
|
||||
SHRQ $0x33, DI
|
||||
MOVQ R10, R9
|
||||
SHRQ $0x33, R9
|
||||
MOVQ R12, R11
|
||||
SHRQ $0x33, R11
|
||||
MOVQ R14, R13
|
||||
SHRQ $0x33, R13
|
||||
ANDQ AX, SI
|
||||
IMUL3Q $0x13, R13, R13
|
||||
ADDQ R13, SI
|
||||
ANDQ AX, R8
|
||||
ADDQ BX, R8
|
||||
ANDQ AX, R10
|
||||
ADDQ DI, R10
|
||||
ANDQ AX, R12
|
||||
ADDQ R9, R12
|
||||
ANDQ AX, R14
|
||||
ADDQ R11, R14
|
||||
|
||||
// Store output
|
||||
MOVQ out+0(FP), AX
|
||||
MOVQ SI, (AX)
|
||||
MOVQ R8, 8(AX)
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R12, 24(AX)
|
||||
MOVQ R14, 32(AX)
|
||||
RET
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 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 !amd64 || !gc || purego
|
||||
|
||||
package field
|
||||
|
||||
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
|
||||
|
||||
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2020 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 arm64 && gc && !purego
|
||||
|
||||
package field
|
||||
|
||||
//go:noescape
|
||||
func carryPropagate(v *Element)
|
||||
|
||||
func (v *Element) carryPropagate() *Element {
|
||||
carryPropagate(v)
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2020 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 arm64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// carryPropagate works exactly like carryPropagateGeneric and uses the
|
||||
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
|
||||
// avoids loading R0-R4 twice and uses LDP and STP.
|
||||
//
|
||||
// See https://golang.org/issues/43145 for the main compiler issue.
|
||||
//
|
||||
// func carryPropagate(v *Element)
|
||||
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
|
||||
MOVD v+0(FP), R20
|
||||
|
||||
LDP 0(R20), (R0, R1)
|
||||
LDP 16(R20), (R2, R3)
|
||||
MOVD 32(R20), R4
|
||||
|
||||
AND $0x7ffffffffffff, R0, R10
|
||||
AND $0x7ffffffffffff, R1, R11
|
||||
AND $0x7ffffffffffff, R2, R12
|
||||
AND $0x7ffffffffffff, R3, R13
|
||||
AND $0x7ffffffffffff, R4, R14
|
||||
|
||||
ADD R0>>51, R11, R11
|
||||
ADD R1>>51, R12, R12
|
||||
ADD R2>>51, R13, R13
|
||||
ADD R3>>51, R14, R14
|
||||
// R4>>51 * 19 + R10 -> R10
|
||||
LSR $51, R4, R21
|
||||
MOVD $19, R22
|
||||
MADD R22, R10, R21, R10
|
||||
|
||||
STP (R10, R11), 0(R20)
|
||||
STP (R12, R13), 16(R20)
|
||||
MOVD R14, 32(R20)
|
||||
|
||||
RET
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !arm64 || !gc || purego
|
||||
|
||||
package field
|
||||
|
||||
func (v *Element) carryPropagate() *Element {
|
||||
return v.carryPropagateGeneric()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package field
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
var x, y Element
|
||||
x.One()
|
||||
y.Add(feOne, feOne)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
x.Add(&x, &y)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMultiply(b *testing.B) {
|
||||
var x, y Element
|
||||
x.One()
|
||||
y.Add(feOne, feOne)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
x.Multiply(&x, &y)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMult32(b *testing.B) {
|
||||
var x Element
|
||||
x.One()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
x.Mult32(&x, 0xaa42aa42)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package field
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
|
||||
// bits.Mul64 and bits.Add64 intrinsics.
|
||||
type uint128 struct {
|
||||
lo, hi uint64
|
||||
}
|
||||
|
||||
// mul64 returns a * b.
|
||||
func mul64(a, b uint64) uint128 {
|
||||
hi, lo := bits.Mul64(a, b)
|
||||
return uint128{lo, hi}
|
||||
}
|
||||
|
||||
// addMul64 returns v + a * b.
|
||||
func addMul64(v uint128, a, b uint64) uint128 {
|
||||
hi, lo := bits.Mul64(a, b)
|
||||
lo, c := bits.Add64(lo, v.lo, 0)
|
||||
hi, _ = bits.Add64(hi, v.hi, c)
|
||||
return uint128{lo, hi}
|
||||
}
|
||||
|
||||
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
|
||||
func shiftRightBy51(a uint128) uint64 {
|
||||
return (a.hi << (64 - 51)) | (a.lo >> 51)
|
||||
}
|
||||
|
||||
func feMulGeneric(v, a, b *Element) {
|
||||
a0 := a.l0
|
||||
a1 := a.l1
|
||||
a2 := a.l2
|
||||
a3 := a.l3
|
||||
a4 := a.l4
|
||||
|
||||
b0 := b.l0
|
||||
b1 := b.l1
|
||||
b2 := b.l2
|
||||
b3 := b.l3
|
||||
b4 := b.l4
|
||||
|
||||
// Limb multiplication works like pen-and-paper columnar multiplication, but
|
||||
// with 51-bit limbs instead of digits.
|
||||
//
|
||||
// a4 a3 a2 a1 a0 x
|
||||
// b4 b3 b2 b1 b0 =
|
||||
// ------------------------
|
||||
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||
// a4b1 a3b1 a2b1 a1b1 a0b1 +
|
||||
// a4b2 a3b2 a2b2 a1b2 a0b2 +
|
||||
// a4b3 a3b3 a2b3 a1b3 a0b3 +
|
||||
// a4b4 a3b4 a2b4 a1b4 a0b4 =
|
||||
// ----------------------------------------------
|
||||
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||
//
|
||||
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
|
||||
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
|
||||
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
|
||||
//
|
||||
// Reduction can be carried out simultaneously to multiplication. For
|
||||
// example, we do not compute r5: whenever the result of a multiplication
|
||||
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
|
||||
//
|
||||
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
|
||||
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
|
||||
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
|
||||
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
|
||||
// --------------------------------------
|
||||
// r4 r3 r2 r1 r0
|
||||
//
|
||||
// Finally we add up the columns into wide, overlapping limbs.
|
||||
|
||||
a1_19 := a1 * 19
|
||||
a2_19 := a2 * 19
|
||||
a3_19 := a3 * 19
|
||||
a4_19 := a4 * 19
|
||||
|
||||
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||
r0 := mul64(a0, b0)
|
||||
r0 = addMul64(r0, a1_19, b4)
|
||||
r0 = addMul64(r0, a2_19, b3)
|
||||
r0 = addMul64(r0, a3_19, b2)
|
||||
r0 = addMul64(r0, a4_19, b1)
|
||||
|
||||
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||
r1 := mul64(a0, b1)
|
||||
r1 = addMul64(r1, a1, b0)
|
||||
r1 = addMul64(r1, a2_19, b4)
|
||||
r1 = addMul64(r1, a3_19, b3)
|
||||
r1 = addMul64(r1, a4_19, b2)
|
||||
|
||||
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||
r2 := mul64(a0, b2)
|
||||
r2 = addMul64(r2, a1, b1)
|
||||
r2 = addMul64(r2, a2, b0)
|
||||
r2 = addMul64(r2, a3_19, b4)
|
||||
r2 = addMul64(r2, a4_19, b3)
|
||||
|
||||
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||
r3 := mul64(a0, b3)
|
||||
r3 = addMul64(r3, a1, b2)
|
||||
r3 = addMul64(r3, a2, b1)
|
||||
r3 = addMul64(r3, a3, b0)
|
||||
r3 = addMul64(r3, a4_19, b4)
|
||||
|
||||
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||
r4 := mul64(a0, b4)
|
||||
r4 = addMul64(r4, a1, b3)
|
||||
r4 = addMul64(r4, a2, b2)
|
||||
r4 = addMul64(r4, a3, b1)
|
||||
r4 = addMul64(r4, a4, b0)
|
||||
|
||||
// After the multiplication, we need to reduce (carry) the five coefficients
|
||||
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
|
||||
// to respect the Element invariant.
|
||||
//
|
||||
// Overall, the reduction works the same as carryPropagate, except with
|
||||
// wider inputs: we take the carry for each coefficient by shifting it right
|
||||
// by 51, and add it to the limb above it. The top carry is multiplied by 19
|
||||
// according to the reduction identity and added to the lowest limb.
|
||||
//
|
||||
// The largest coefficient (r0) will be at most 111 bits, which guarantees
|
||||
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
|
||||
//
|
||||
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
|
||||
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
|
||||
// r0 < 2⁷ × 2⁵² × 2⁵²
|
||||
// r0 < 2¹¹¹
|
||||
//
|
||||
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
|
||||
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
|
||||
// allows us to easily apply the reduction identity.
|
||||
//
|
||||
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||
// r4 < 5 × 2⁵² × 2⁵²
|
||||
// r4 < 2¹⁰⁷
|
||||
//
|
||||
|
||||
c0 := shiftRightBy51(r0)
|
||||
c1 := shiftRightBy51(r1)
|
||||
c2 := shiftRightBy51(r2)
|
||||
c3 := shiftRightBy51(r3)
|
||||
c4 := shiftRightBy51(r4)
|
||||
|
||||
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||
rr1 := r1.lo&maskLow51Bits + c0
|
||||
rr2 := r2.lo&maskLow51Bits + c1
|
||||
rr3 := r3.lo&maskLow51Bits + c2
|
||||
rr4 := r4.lo&maskLow51Bits + c3
|
||||
|
||||
// Now all coefficients fit into 64-bit registers but are still too large to
|
||||
// be passed around as a Element. We therefore do one last carry chain,
|
||||
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
|
||||
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||
v.carryPropagate()
|
||||
}
|
||||
|
||||
func feSquareGeneric(v, a *Element) {
|
||||
l0 := a.l0
|
||||
l1 := a.l1
|
||||
l2 := a.l2
|
||||
l3 := a.l3
|
||||
l4 := a.l4
|
||||
|
||||
// Squaring works precisely like multiplication above, but thanks to its
|
||||
// symmetry we get to group a few terms together.
|
||||
//
|
||||
// l4 l3 l2 l1 l0 x
|
||||
// l4 l3 l2 l1 l0 =
|
||||
// ------------------------
|
||||
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||
// l4l1 l3l1 l2l1 l1l1 l0l1 +
|
||||
// l4l2 l3l2 l2l2 l1l2 l0l2 +
|
||||
// l4l3 l3l3 l2l3 l1l3 l0l3 +
|
||||
// l4l4 l3l4 l2l4 l1l4 l0l4 =
|
||||
// ----------------------------------------------
|
||||
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||
//
|
||||
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
|
||||
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
|
||||
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
|
||||
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
|
||||
// --------------------------------------
|
||||
// r4 r3 r2 r1 r0
|
||||
//
|
||||
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
|
||||
// only three Mul64 and four Add64, instead of five and eight.
|
||||
|
||||
l0_2 := l0 * 2
|
||||
l1_2 := l1 * 2
|
||||
|
||||
l1_38 := l1 * 38
|
||||
l2_38 := l2 * 38
|
||||
l3_38 := l3 * 38
|
||||
|
||||
l3_19 := l3 * 19
|
||||
l4_19 := l4 * 19
|
||||
|
||||
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||
r0 := mul64(l0, l0)
|
||||
r0 = addMul64(r0, l1_38, l4)
|
||||
r0 = addMul64(r0, l2_38, l3)
|
||||
|
||||
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||
r1 := mul64(l0_2, l1)
|
||||
r1 = addMul64(r1, l2_38, l4)
|
||||
r1 = addMul64(r1, l3_19, l3)
|
||||
|
||||
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||
r2 := mul64(l0_2, l2)
|
||||
r2 = addMul64(r2, l1, l1)
|
||||
r2 = addMul64(r2, l3_38, l4)
|
||||
|
||||
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||
r3 := mul64(l0_2, l3)
|
||||
r3 = addMul64(r3, l1_2, l2)
|
||||
r3 = addMul64(r3, l4_19, l4)
|
||||
|
||||
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||
r4 := mul64(l0_2, l4)
|
||||
r4 = addMul64(r4, l1_2, l3)
|
||||
r4 = addMul64(r4, l2, l2)
|
||||
|
||||
c0 := shiftRightBy51(r0)
|
||||
c1 := shiftRightBy51(r1)
|
||||
c2 := shiftRightBy51(r2)
|
||||
c3 := shiftRightBy51(r3)
|
||||
c4 := shiftRightBy51(r4)
|
||||
|
||||
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||
rr1 := r1.lo&maskLow51Bits + c0
|
||||
rr2 := r2.lo&maskLow51Bits + c1
|
||||
rr3 := r3.lo&maskLow51Bits + c2
|
||||
rr4 := r4.lo&maskLow51Bits + c3
|
||||
|
||||
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||
v.carryPropagate()
|
||||
}
|
||||
|
||||
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
|
||||
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. TODO inline
|
||||
func (v *Element) carryPropagateGeneric() *Element {
|
||||
c0 := v.l0 >> 51
|
||||
c1 := v.l1 >> 51
|
||||
c2 := v.l2 >> 51
|
||||
c3 := v.l3 >> 51
|
||||
c4 := v.l4 >> 51
|
||||
|
||||
v.l0 = v.l0&maskLow51Bits + c4*19
|
||||
v.l1 = v.l1&maskLow51Bits + c0
|
||||
v.l2 = v.l2&maskLow51Bits + c1
|
||||
v.l3 = v.l3&maskLow51Bits + c2
|
||||
v.l4 = v.l4&maskLow51Bits + c3
|
||||
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package field
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/big"
|
||||
"math/bits"
|
||||
mathrand "math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func (v Element) String() string {
|
||||
return hex.EncodeToString(v.Bytes())
|
||||
}
|
||||
|
||||
// quickCheckConfig1024 will make each quickcheck test run (1024 * -quickchecks)
|
||||
// times. The default value of -quickchecks is 100.
|
||||
var quickCheckConfig1024 = &quick.Config{MaxCountScale: 1 << 10}
|
||||
|
||||
func generateFieldElement(rand *mathrand.Rand) Element {
|
||||
const maskLow52Bits = (1 << 52) - 1
|
||||
return Element{
|
||||
rand.Uint64() & maskLow52Bits,
|
||||
rand.Uint64() & maskLow52Bits,
|
||||
rand.Uint64() & maskLow52Bits,
|
||||
rand.Uint64() & maskLow52Bits,
|
||||
rand.Uint64() & maskLow52Bits,
|
||||
}
|
||||
}
|
||||
|
||||
// weirdLimbs can be combined to generate a range of edge-case field elements.
|
||||
// 0 and -1 are intentionally more weighted, as they combine well.
|
||||
var (
|
||||
weirdLimbs51 = []uint64{
|
||||
0, 0, 0, 0,
|
||||
1,
|
||||
19 - 1,
|
||||
19,
|
||||
0x2aaaaaaaaaaaa,
|
||||
0x5555555555555,
|
||||
(1 << 51) - 20,
|
||||
(1 << 51) - 19,
|
||||
(1 << 51) - 1, (1 << 51) - 1,
|
||||
(1 << 51) - 1, (1 << 51) - 1,
|
||||
}
|
||||
weirdLimbs52 = []uint64{
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1,
|
||||
19 - 1,
|
||||
19,
|
||||
0x2aaaaaaaaaaaa,
|
||||
0x5555555555555,
|
||||
(1 << 51) - 20,
|
||||
(1 << 51) - 19,
|
||||
(1 << 51) - 1, (1 << 51) - 1,
|
||||
(1 << 51) - 1, (1 << 51) - 1,
|
||||
(1 << 51) - 1, (1 << 51) - 1,
|
||||
1 << 51,
|
||||
(1 << 51) + 1,
|
||||
(1 << 52) - 19,
|
||||
(1 << 52) - 1,
|
||||
}
|
||||
)
|
||||
|
||||
func generateWeirdFieldElement(rand *mathrand.Rand) Element {
|
||||
return Element{
|
||||
weirdLimbs52[rand.Intn(len(weirdLimbs52))],
|
||||
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
||||
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
||||
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
||||
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
||||
}
|
||||
}
|
||||
|
||||
func (Element) Generate(rand *mathrand.Rand, size int) reflect.Value {
|
||||
if rand.Intn(2) == 0 {
|
||||
return reflect.ValueOf(generateWeirdFieldElement(rand))
|
||||
}
|
||||
return reflect.ValueOf(generateFieldElement(rand))
|
||||
}
|
||||
|
||||
// isInBounds returns whether the element is within the expected bit size bounds
|
||||
// after a light reduction.
|
||||
func isInBounds(x *Element) bool {
|
||||
return bits.Len64(x.l0) <= 52 &&
|
||||
bits.Len64(x.l1) <= 52 &&
|
||||
bits.Len64(x.l2) <= 52 &&
|
||||
bits.Len64(x.l3) <= 52 &&
|
||||
bits.Len64(x.l4) <= 52
|
||||
}
|
||||
|
||||
func TestMultiplyDistributesOverAdd(t *testing.T) {
|
||||
multiplyDistributesOverAdd := func(x, y, z Element) bool {
|
||||
// Compute t1 = (x+y)*z
|
||||
t1 := new(Element)
|
||||
t1.Add(&x, &y)
|
||||
t1.Multiply(t1, &z)
|
||||
|
||||
// Compute t2 = x*z + y*z
|
||||
t2 := new(Element)
|
||||
t3 := new(Element)
|
||||
t2.Multiply(&x, &z)
|
||||
t3.Multiply(&y, &z)
|
||||
t2.Add(t2, t3)
|
||||
|
||||
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
|
||||
}
|
||||
|
||||
if err := quick.Check(multiplyDistributesOverAdd, quickCheckConfig1024); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMul64to128(t *testing.T) {
|
||||
a := uint64(5)
|
||||
b := uint64(5)
|
||||
r := mul64(a, b)
|
||||
if r.lo != 0x19 || r.hi != 0 {
|
||||
t.Errorf("lo-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
|
||||
}
|
||||
|
||||
a = uint64(18014398509481983) // 2^54 - 1
|
||||
b = uint64(18014398509481983) // 2^54 - 1
|
||||
r = mul64(a, b)
|
||||
if r.lo != 0xff80000000000001 || r.hi != 0xfffffffffff {
|
||||
t.Errorf("hi-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
|
||||
}
|
||||
|
||||
a = uint64(1125899906842661)
|
||||
b = uint64(2097155)
|
||||
r = mul64(a, b)
|
||||
r = addMul64(r, a, b)
|
||||
r = addMul64(r, a, b)
|
||||
r = addMul64(r, a, b)
|
||||
r = addMul64(r, a, b)
|
||||
if r.lo != 16888498990613035 || r.hi != 640 {
|
||||
t.Errorf("wrong answer: %d + %d*(2**64)", r.lo, r.hi)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetBytesRoundTrip(t *testing.T) {
|
||||
f1 := func(in [32]byte, fe Element) bool {
|
||||
fe.SetBytes(in[:])
|
||||
|
||||
// Mask the most significant bit as it's ignored by SetBytes. (Now
|
||||
// instead of earlier so we check the masking in SetBytes is working.)
|
||||
in[len(in)-1] &= (1 << 7) - 1
|
||||
|
||||
return bytes.Equal(in[:], fe.Bytes()) && isInBounds(&fe)
|
||||
}
|
||||
if err := quick.Check(f1, nil); err != nil {
|
||||
t.Errorf("failed bytes->FE->bytes round-trip: %v", err)
|
||||
}
|
||||
|
||||
f2 := func(fe, r Element) bool {
|
||||
r.SetBytes(fe.Bytes())
|
||||
|
||||
// Intentionally not using Equal not to go through Bytes again.
|
||||
// Calling reduce because both Generate and SetBytes can produce
|
||||
// non-canonical representations.
|
||||
fe.reduce()
|
||||
r.reduce()
|
||||
return fe == r
|
||||
}
|
||||
if err := quick.Check(f2, nil); err != nil {
|
||||
t.Errorf("failed FE->bytes->FE round-trip: %v", err)
|
||||
}
|
||||
|
||||
// Check some fixed vectors from dalek
|
||||
type feRTTest struct {
|
||||
fe Element
|
||||
b []byte
|
||||
}
|
||||
var tests = []feRTTest{
|
||||
{
|
||||
fe: Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676},
|
||||
b: []byte{74, 209, 69, 197, 70, 70, 161, 222, 56, 226, 229, 19, 112, 60, 25, 92, 187, 74, 222, 56, 50, 153, 51, 233, 40, 74, 57, 6, 160, 185, 213, 31},
|
||||
},
|
||||
{
|
||||
fe: Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972},
|
||||
b: []byte{199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b := tt.fe.Bytes()
|
||||
if !bytes.Equal(b, tt.b) || new(Element).SetBytes(tt.b).Equal(&tt.fe) != 1 {
|
||||
t.Errorf("Failed fixed roundtrip: %v", tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func swapEndianness(buf []byte) []byte {
|
||||
for i := 0; i < len(buf)/2; i++ {
|
||||
buf[i], buf[len(buf)-i-1] = buf[len(buf)-i-1], buf[i]
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func TestBytesBigEquivalence(t *testing.T) {
|
||||
f1 := func(in [32]byte, fe, fe1 Element) bool {
|
||||
fe.SetBytes(in[:])
|
||||
|
||||
in[len(in)-1] &= (1 << 7) - 1 // mask the most significant bit
|
||||
b := new(big.Int).SetBytes(swapEndianness(in[:]))
|
||||
fe1.fromBig(b)
|
||||
|
||||
if fe != fe1 {
|
||||
return false
|
||||
}
|
||||
|
||||
buf := make([]byte, 32) // pad with zeroes
|
||||
copy(buf, swapEndianness(fe1.toBig().Bytes()))
|
||||
|
||||
return bytes.Equal(fe.Bytes(), buf) && isInBounds(&fe) && isInBounds(&fe1)
|
||||
}
|
||||
if err := quick.Check(f1, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// fromBig sets v = n, and returns v. The bit length of n must not exceed 256.
|
||||
func (v *Element) fromBig(n *big.Int) *Element {
|
||||
if n.BitLen() > 32*8 {
|
||||
panic("edwards25519: invalid field element input size")
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 32)
|
||||
for _, word := range n.Bits() {
|
||||
for i := 0; i < bits.UintSize; i += 8 {
|
||||
if len(buf) >= cap(buf) {
|
||||
break
|
||||
}
|
||||
buf = append(buf, byte(word))
|
||||
word >>= 8
|
||||
}
|
||||
}
|
||||
|
||||
return v.SetBytes(buf[:32])
|
||||
}
|
||||
|
||||
func (v *Element) fromDecimal(s string) *Element {
|
||||
n, ok := new(big.Int).SetString(s, 10)
|
||||
if !ok {
|
||||
panic("not a valid decimal: " + s)
|
||||
}
|
||||
return v.fromBig(n)
|
||||
}
|
||||
|
||||
// toBig returns v as a big.Int.
|
||||
func (v *Element) toBig() *big.Int {
|
||||
buf := v.Bytes()
|
||||
|
||||
words := make([]big.Word, 32*8/bits.UintSize)
|
||||
for n := range words {
|
||||
for i := 0; i < bits.UintSize; i += 8 {
|
||||
if len(buf) == 0 {
|
||||
break
|
||||
}
|
||||
words[n] |= big.Word(buf[0]) << big.Word(i)
|
||||
buf = buf[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return new(big.Int).SetBits(words)
|
||||
}
|
||||
|
||||
func TestDecimalConstants(t *testing.T) {
|
||||
sqrtM1String := "19681161376707505956807079304988542015446066515923890162744021073123829784752"
|
||||
if exp := new(Element).fromDecimal(sqrtM1String); sqrtM1.Equal(exp) != 1 {
|
||||
t.Errorf("sqrtM1 is %v, expected %v", sqrtM1, exp)
|
||||
}
|
||||
// d is in the parent package, and we don't want to expose d or fromDecimal.
|
||||
// dString := "37095705934669439343138083508754565189542113879843219016388785533085940283555"
|
||||
// if exp := new(Element).fromDecimal(dString); d.Equal(exp) != 1 {
|
||||
// t.Errorf("d is %v, expected %v", d, exp)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestSetBytesRoundTripEdgeCases(t *testing.T) {
|
||||
// TODO: values close to 0, close to 2^255-19, between 2^255-19 and 2^255-1,
|
||||
// and between 2^255 and 2^256-1. Test both the documented SetBytes
|
||||
// behavior, and that Bytes reduces them.
|
||||
}
|
||||
|
||||
// Tests self-consistency between Multiply and Square.
|
||||
func TestConsistency(t *testing.T) {
|
||||
var x Element
|
||||
var x2, x2sq Element
|
||||
|
||||
x = Element{1, 1, 1, 1, 1}
|
||||
x2.Multiply(&x, &x)
|
||||
x2sq.Square(&x)
|
||||
|
||||
if x2 != x2sq {
|
||||
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
|
||||
}
|
||||
|
||||
var bytes [32]byte
|
||||
|
||||
_, err := io.ReadFull(rand.Reader, bytes[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
x.SetBytes(bytes[:])
|
||||
|
||||
x2.Multiply(&x, &x)
|
||||
x2sq.Square(&x)
|
||||
|
||||
if x2 != x2sq {
|
||||
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
x := Element{1, 1, 1, 1, 1}
|
||||
y := Element{5, 4, 3, 2, 1}
|
||||
|
||||
eq := x.Equal(&x)
|
||||
if eq != 1 {
|
||||
t.Errorf("wrong about equality")
|
||||
}
|
||||
|
||||
eq = x.Equal(&y)
|
||||
if eq != 0 {
|
||||
t.Errorf("wrong about inequality")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvert(t *testing.T) {
|
||||
x := Element{1, 1, 1, 1, 1}
|
||||
one := Element{1, 0, 0, 0, 0}
|
||||
var xinv, r Element
|
||||
|
||||
xinv.Invert(&x)
|
||||
r.Multiply(&x, &xinv)
|
||||
r.reduce()
|
||||
|
||||
if one != r {
|
||||
t.Errorf("inversion identity failed, got: %x", r)
|
||||
}
|
||||
|
||||
var bytes [32]byte
|
||||
|
||||
_, err := io.ReadFull(rand.Reader, bytes[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
x.SetBytes(bytes[:])
|
||||
|
||||
xinv.Invert(&x)
|
||||
r.Multiply(&x, &xinv)
|
||||
r.reduce()
|
||||
|
||||
if one != r {
|
||||
t.Errorf("random inversion identity failed, got: %x for field element %x", r, x)
|
||||
}
|
||||
|
||||
zero := Element{}
|
||||
x.Set(&zero)
|
||||
if xx := xinv.Invert(&x); xx != &xinv {
|
||||
t.Errorf("inverting zero did not return the receiver")
|
||||
} else if xinv.Equal(&zero) != 1 {
|
||||
t.Errorf("inverting zero did not return zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectSwap(t *testing.T) {
|
||||
a := Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676}
|
||||
b := Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972}
|
||||
|
||||
var c, d Element
|
||||
|
||||
c.Select(&a, &b, 1)
|
||||
d.Select(&a, &b, 0)
|
||||
|
||||
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
|
||||
t.Errorf("Select failed")
|
||||
}
|
||||
|
||||
c.Swap(&d, 0)
|
||||
|
||||
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
|
||||
t.Errorf("Swap failed")
|
||||
}
|
||||
|
||||
c.Swap(&d, 1)
|
||||
|
||||
if c.Equal(&b) != 1 || d.Equal(&a) != 1 {
|
||||
t.Errorf("Swap failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMult32(t *testing.T) {
|
||||
mult32EquivalentToMul := func(x Element, y uint32) bool {
|
||||
t1 := new(Element)
|
||||
for i := 0; i < 100; i++ {
|
||||
t1.Mult32(&x, y)
|
||||
}
|
||||
|
||||
ty := new(Element)
|
||||
ty.l0 = uint64(y)
|
||||
|
||||
t2 := new(Element)
|
||||
for i := 0; i < 100; i++ {
|
||||
t2.Multiply(&x, ty)
|
||||
}
|
||||
|
||||
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
|
||||
}
|
||||
|
||||
if err := quick.Check(mult32EquivalentToMul, quickCheckConfig1024); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqrtRatio(t *testing.T) {
|
||||
// From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4.
|
||||
type test struct {
|
||||
u, v string
|
||||
wasSquare int
|
||||
r string
|
||||
}
|
||||
var tests = []test{
|
||||
// If u is 0, the function is defined to return (0, TRUE), even if v
|
||||
// is zero. Note that where used in this package, the denominator v
|
||||
// is never zero.
|
||||
{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
1, "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
// 0/1 == 0²
|
||||
{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
1, "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
// If u is non-zero and v is zero, defined to return (0, FALSE).
|
||||
{
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
0, "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
// 2/1 is not square in this field.
|
||||
{
|
||||
"0200000000000000000000000000000000000000000000000000000000000000",
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
0, "3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54",
|
||||
},
|
||||
// 4/1 == 2²
|
||||
{
|
||||
"0400000000000000000000000000000000000000000000000000000000000000",
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
1, "0200000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
// 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem
|
||||
{
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
"0400000000000000000000000000000000000000000000000000000000000000",
|
||||
1, "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
u := new(Element).SetBytes(decodeHex(tt.u))
|
||||
v := new(Element).SetBytes(decodeHex(tt.v))
|
||||
want := new(Element).SetBytes(decodeHex(tt.r))
|
||||
got, wasSquare := new(Element).SqrtRatio(u, v)
|
||||
if got.Equal(want) == 0 || wasSquare != tt.wasSquare {
|
||||
t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCarryPropagate(t *testing.T) {
|
||||
asmLikeGeneric := func(a [5]uint64) bool {
|
||||
t1 := &Element{a[0], a[1], a[2], a[3], a[4]}
|
||||
t2 := &Element{a[0], a[1], a[2], a[3], a[4]}
|
||||
|
||||
t1.carryPropagate()
|
||||
t2.carryPropagateGeneric()
|
||||
|
||||
if *t1 != *t2 {
|
||||
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
|
||||
}
|
||||
|
||||
return *t1 == *t2 && isInBounds(t2)
|
||||
}
|
||||
|
||||
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !asmLikeGeneric([5]uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}) {
|
||||
t.Errorf("failed for {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeSquare(t *testing.T) {
|
||||
asmLikeGeneric := func(a Element) bool {
|
||||
t1 := a
|
||||
t2 := a
|
||||
|
||||
feSquareGeneric(&t1, &t1)
|
||||
feSquare(&t2, &t2)
|
||||
|
||||
if t1 != t2 {
|
||||
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
|
||||
}
|
||||
|
||||
return t1 == t2 && isInBounds(&t2)
|
||||
}
|
||||
|
||||
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeMul(t *testing.T) {
|
||||
asmLikeGeneric := func(a, b Element) bool {
|
||||
a1 := a
|
||||
a2 := a
|
||||
b1 := b
|
||||
b2 := b
|
||||
|
||||
feMulGeneric(&a1, &a1, &b1)
|
||||
feMul(&a2, &a2, &b2)
|
||||
|
||||
if a1 != a2 || b1 != b2 {
|
||||
t.Logf("got: %#v,\nexpected: %#v", a1, a2)
|
||||
t.Logf("got: %#v,\nexpected: %#v", b1, b2)
|
||||
}
|
||||
|
||||
return a1 == a2 && isInBounds(&a2) &&
|
||||
b1 == b2 && isInBounds(&b2)
|
||||
}
|
||||
|
||||
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
b0c49ae9f59d233526f8934262c5bbbe14d4358d
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#! /bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
STD_PATH=src/crypto/ed25519/internal/edwards25519/field
|
||||
LOCAL_PATH=curve25519/internal/field
|
||||
LAST_SYNC_REF=$(cat $LOCAL_PATH/sync.checkpoint)
|
||||
|
||||
git fetch https://go.googlesource.com/go master
|
||||
|
||||
if git diff --quiet $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH; then
|
||||
echo "No changes."
|
||||
else
|
||||
NEW_REF=$(git rev-parse FETCH_HEAD | tee $LOCAL_PATH/sync.checkpoint)
|
||||
echo "Applying changes from $LAST_SYNC_REF to $NEW_REF..."
|
||||
git diff $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH | \
|
||||
git apply -3 --directory=$LOCAL_PATH
|
||||
fi
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519
|
||||
package curve25519_test
|
||||
|
||||
// lowOrderPoints from libsodium.
|
||||
// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
|
||||
|
||||
+18
-169
@@ -1,12 +1,7 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// 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.
|
||||
|
||||
// In Go 1.13, the ed25519 package was promoted to the standard library as
|
||||
// crypto/ed25519, and this package became a wrapper for the standard library one.
|
||||
//
|
||||
// +build !go1.13
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// https://ed25519.cr.yp.to/.
|
||||
//
|
||||
@@ -15,21 +10,15 @@
|
||||
// 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”.
|
||||
//
|
||||
// Beginning with Go 1.13, the functionality of this package was moved to the
|
||||
// standard library as crypto/ed25519. This package only acts as a compatibility
|
||||
// wrapper.
|
||||
package ed25519
|
||||
|
||||
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||
// from SUPERCOP.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"crypto/ed25519"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,57 +33,21 @@ const (
|
||||
)
|
||||
|
||||
// PublicKey is the type of Ed25519 public keys.
|
||||
type PublicKey []byte
|
||||
//
|
||||
// This type is an alias for crypto/ed25519's PublicKey type.
|
||||
// See the crypto/ed25519 package for the methods on this type.
|
||||
type PublicKey = ed25519.PublicKey
|
||||
|
||||
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||
type PrivateKey []byte
|
||||
|
||||
// Public returns the PublicKey corresponding to priv.
|
||||
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||
publicKey := make([]byte, PublicKeySize)
|
||||
copy(publicKey, priv[32:])
|
||||
return PublicKey(publicKey)
|
||||
}
|
||||
|
||||
// Seed returns the private key seed corresponding to priv. It is provided for
|
||||
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
|
||||
// in this package.
|
||||
func (priv PrivateKey) Seed() []byte {
|
||||
seed := make([]byte, SeedSize)
|
||||
copy(seed, priv[:32])
|
||||
return seed
|
||||
}
|
||||
|
||||
// Sign signs the given message with priv.
|
||||
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||
// crypto.Hash(0) as the value for opts.
|
||||
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
if opts.HashFunc() != crypto.Hash(0) {
|
||||
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||
}
|
||||
|
||||
return Sign(priv, message), nil
|
||||
}
|
||||
//
|
||||
// This type is an alias for crypto/ed25519's PrivateKey type.
|
||||
// See the crypto/ed25519 package for the methods on this type.
|
||||
type PrivateKey = ed25519.PrivateKey
|
||||
|
||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||
// If rand is nil, crypto/rand.Reader will be used.
|
||||
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||
if rand == nil {
|
||||
rand = cryptorand.Reader
|
||||
}
|
||||
|
||||
seed := make([]byte, SeedSize)
|
||||
if _, err := io.ReadFull(rand, seed); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
privateKey := NewKeyFromSeed(seed)
|
||||
publicKey := make([]byte, PublicKeySize)
|
||||
copy(publicKey, privateKey[32:])
|
||||
|
||||
return publicKey, privateKey, nil
|
||||
return ed25519.GenerateKey(rand)
|
||||
}
|
||||
|
||||
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||
@@ -102,121 +55,17 @@ func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||
// package.
|
||||
func NewKeyFromSeed(seed []byte) PrivateKey {
|
||||
if l := len(seed); l != SeedSize {
|
||||
panic("ed25519: bad seed length: " + strconv.Itoa(l))
|
||||
}
|
||||
|
||||
digest := sha512.Sum512(seed)
|
||||
digest[0] &= 248
|
||||
digest[31] &= 127
|
||||
digest[31] |= 64
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
var hBytes [32]byte
|
||||
copy(hBytes[:], digest[:])
|
||||
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||
var publicKeyBytes [32]byte
|
||||
A.ToBytes(&publicKeyBytes)
|
||||
|
||||
privateKey := make([]byte, PrivateKeySize)
|
||||
copy(privateKey, seed)
|
||||
copy(privateKey[32:], publicKeyBytes[:])
|
||||
|
||||
return privateKey
|
||||
return ed25519.NewKeyFromSeed(seed)
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature. It will
|
||||
// panic if len(privateKey) is not PrivateKeySize.
|
||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||
if l := len(privateKey); l != PrivateKeySize {
|
||||
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||
}
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
|
||||
var digest1, messageDigest, hramDigest [64]byte
|
||||
var expandedSecretKey [32]byte
|
||||
h.Sum(digest1[:0])
|
||||
copy(expandedSecretKey[:], digest1[:])
|
||||
expandedSecretKey[0] &= 248
|
||||
expandedSecretKey[31] &= 63
|
||||
expandedSecretKey[31] |= 64
|
||||
|
||||
h.Reset()
|
||||
h.Write(digest1[32:])
|
||||
h.Write(message)
|
||||
h.Sum(messageDigest[:0])
|
||||
|
||||
var messageDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||
var R edwards25519.ExtendedGroupElement
|
||||
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||
|
||||
var encodedR [32]byte
|
||||
R.ToBytes(&encodedR)
|
||||
|
||||
h.Reset()
|
||||
h.Write(encodedR[:])
|
||||
h.Write(privateKey[32:])
|
||||
h.Write(message)
|
||||
h.Sum(hramDigest[:0])
|
||||
var hramDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||
|
||||
var s [32]byte
|
||||
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||
|
||||
signature := make([]byte, SignatureSize)
|
||||
copy(signature[:], encodedR[:])
|
||||
copy(signature[32:], s[:])
|
||||
|
||||
return signature
|
||||
return ed25519.Sign(privateKey, message)
|
||||
}
|
||||
|
||||
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||
// will panic if len(publicKey) is not PublicKeySize.
|
||||
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||
if l := len(publicKey); l != PublicKeySize {
|
||||
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
||||
}
|
||||
|
||||
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
var publicKeyBytes [32]byte
|
||||
copy(publicKeyBytes[:], publicKey)
|
||||
if !A.FromBytes(&publicKeyBytes) {
|
||||
return false
|
||||
}
|
||||
edwards25519.FeNeg(&A.X, &A.X)
|
||||
edwards25519.FeNeg(&A.T, &A.T)
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(sig[:32])
|
||||
h.Write(publicKey[:])
|
||||
h.Write(message)
|
||||
var digest [64]byte
|
||||
h.Sum(digest[:0])
|
||||
|
||||
var hReduced [32]byte
|
||||
edwards25519.ScReduce(&hReduced, &digest)
|
||||
|
||||
var R edwards25519.ProjectiveGroupElement
|
||||
var s [32]byte
|
||||
copy(s[:], sig[32:])
|
||||
|
||||
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
|
||||
// the range [0, order) in order to prevent signature malleability.
|
||||
if !edwards25519.ScMinimal(&s) {
|
||||
return false
|
||||
}
|
||||
|
||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
|
||||
|
||||
var checkR [32]byte
|
||||
R.ToBytes(&checkR)
|
||||
return bytes.Equal(sig[:32], checkR[:])
|
||||
return ed25519.Verify(publicKey, message, sig)
|
||||
}
|
||||
|
||||
@@ -1,73 +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.
|
||||
|
||||
// +build go1.13
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// https://ed25519.cr.yp.to/.
|
||||
//
|
||||
// These functions are also compatible with the “Ed25519” function defined in
|
||||
// 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”.
|
||||
//
|
||||
// Beginning with Go 1.13, the functionality of this package was moved to the
|
||||
// standard library as crypto/ed25519. This package only acts as a compatibility
|
||||
// wrapper.
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = 32
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = 64
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = 64
|
||||
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
|
||||
SeedSize = 32
|
||||
)
|
||||
|
||||
// PublicKey is the type of Ed25519 public keys.
|
||||
//
|
||||
// This type is an alias for crypto/ed25519's PublicKey type.
|
||||
// See the crypto/ed25519 package for the methods on this type.
|
||||
type PublicKey = ed25519.PublicKey
|
||||
|
||||
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||
//
|
||||
// This type is an alias for crypto/ed25519's PrivateKey type.
|
||||
// See the crypto/ed25519 package for the methods on this type.
|
||||
type PrivateKey = ed25519.PrivateKey
|
||||
|
||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||
// If rand is nil, crypto/rand.Reader will be used.
|
||||
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||
return ed25519.GenerateKey(rand)
|
||||
}
|
||||
|
||||
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||
// len(seed) is not SeedSize. This function is provided for interoperability
|
||||
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||
// package.
|
||||
func NewKeyFromSeed(seed []byte) PrivateKey {
|
||||
return ed25519.NewKeyFromSeed(seed)
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature. It will
|
||||
// panic if len(privateKey) is not PrivateKeySize.
|
||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||
return ed25519.Sign(privateKey, message)
|
||||
}
|
||||
|
||||
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||
// will panic if len(publicKey) is not PublicKeySize.
|
||||
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||
return ed25519.Verify(publicKey, message, sig)
|
||||
}
|
||||
+4
-203
@@ -1,221 +1,22 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// 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.
|
||||
|
||||
package ed25519_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strings"
|
||||
ed25519std "crypto/ed25519"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||
)
|
||||
|
||||
type zeroReader struct{}
|
||||
|
||||
func (zeroReader) Read(buf []byte) (int, error) {
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestUnmarshalMarshal(t *testing.T) {
|
||||
pub, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
var pubBytes [32]byte
|
||||
copy(pubBytes[:], pub)
|
||||
if !A.FromBytes(&pubBytes) {
|
||||
t.Fatalf("ExtendedGroupElement.FromBytes failed")
|
||||
}
|
||||
|
||||
var pub2 [32]byte
|
||||
A.ToBytes(&pub2)
|
||||
|
||||
if pubBytes != pub2 {
|
||||
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignVerify(t *testing.T) {
|
||||
var zero zeroReader
|
||||
public, private, _ := ed25519.GenerateKey(zero)
|
||||
func TestTypeAlias(t *testing.T) {
|
||||
public, private, _ := ed25519std.GenerateKey(nil)
|
||||
|
||||
message := []byte("test message")
|
||||
sig := ed25519.Sign(private, message)
|
||||
if !ed25519.Verify(public, message, sig) {
|
||||
t.Errorf("valid signature rejected")
|
||||
}
|
||||
|
||||
wrongMessage := []byte("wrong message")
|
||||
if ed25519.Verify(public, wrongMessage, sig) {
|
||||
t.Errorf("signature of different message accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoSigner(t *testing.T) {
|
||||
var zero zeroReader
|
||||
public, private, _ := ed25519.GenerateKey(zero)
|
||||
|
||||
signer := crypto.Signer(private)
|
||||
|
||||
publicInterface := signer.Public()
|
||||
public2, ok := publicInterface.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("expected PublicKey from Public() but got %T", publicInterface)
|
||||
}
|
||||
|
||||
if !bytes.Equal(public, public2) {
|
||||
t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2)
|
||||
}
|
||||
|
||||
message := []byte("message")
|
||||
var noHash crypto.Hash
|
||||
signature, err := signer.Sign(zero, message, noHash)
|
||||
if err != nil {
|
||||
t.Fatalf("error from Sign(): %s", err)
|
||||
}
|
||||
|
||||
if !ed25519.Verify(public, message, signature) {
|
||||
t.Errorf("Verify failed on signature from Sign()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGolden(t *testing.T) {
|
||||
// sign.input.gz is a selection of test cases from
|
||||
// https://ed25519.cr.yp.to/python/sign.input
|
||||
testDataZ, err := os.Open("testdata/sign.input.gz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testDataZ.Close()
|
||||
testData, err := gzip.NewReader(testDataZ)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testData.Close()
|
||||
|
||||
scanner := bufio.NewScanner(testData)
|
||||
lineNo := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) != 5 {
|
||||
t.Fatalf("bad number of parts on line %d", lineNo)
|
||||
}
|
||||
|
||||
privBytes, _ := hex.DecodeString(parts[0])
|
||||
pubKey, _ := hex.DecodeString(parts[1])
|
||||
msg, _ := hex.DecodeString(parts[2])
|
||||
sig, _ := hex.DecodeString(parts[3])
|
||||
// The signatures in the test vectors also include the message
|
||||
// at the end, but we just want R and S.
|
||||
sig = sig[:ed25519.SignatureSize]
|
||||
|
||||
if l := len(pubKey); l != ed25519.PublicKeySize {
|
||||
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
|
||||
}
|
||||
|
||||
var priv [ed25519.PrivateKeySize]byte
|
||||
copy(priv[:], privBytes)
|
||||
copy(priv[32:], pubKey)
|
||||
|
||||
sig2 := ed25519.Sign(priv[:], msg)
|
||||
if !bytes.Equal(sig, sig2[:]) {
|
||||
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
|
||||
}
|
||||
|
||||
if !ed25519.Verify(pubKey, msg, sig2) {
|
||||
t.Errorf("signature failed to verify on line %d", lineNo)
|
||||
}
|
||||
|
||||
priv2 := ed25519.NewKeyFromSeed(priv[:32])
|
||||
if !bytes.Equal(priv[:], priv2) {
|
||||
t.Errorf("recreating key pair gave different private key on line %d: %x vs %x", lineNo, priv[:], priv2)
|
||||
}
|
||||
|
||||
if pubKey2 := priv2.Public().(ed25519.PublicKey); !bytes.Equal(pubKey, pubKey2) {
|
||||
t.Errorf("recreating key pair gave different public key on line %d: %x vs %x", lineNo, pubKey, pubKey2)
|
||||
}
|
||||
|
||||
if seed := priv2.Seed(); !bytes.Equal(priv[:32], seed) {
|
||||
t.Errorf("recreating key pair gave different seed on line %d: %x vs %x", lineNo, priv[:32], seed)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
t.Fatalf("error reading test data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalleability(t *testing.T) {
|
||||
// https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test
|
||||
// that s be in [0, order). This prevents someone from adding a multiple of
|
||||
// order to s and obtaining a second valid signature for the same message.
|
||||
msg := []byte{0x54, 0x65, 0x73, 0x74}
|
||||
sig := []byte{
|
||||
0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a,
|
||||
0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b,
|
||||
0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67,
|
||||
0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d,
|
||||
0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33,
|
||||
0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d,
|
||||
}
|
||||
publicKey := []byte{
|
||||
0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5,
|
||||
0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34,
|
||||
0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa,
|
||||
}
|
||||
|
||||
if ed25519.Verify(publicKey, msg, sig) {
|
||||
t.Fatal("non-canonical signature accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyGeneration(b *testing.B) {
|
||||
var zero zeroReader
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, err := ed25519.GenerateKey(zero); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSigning(b *testing.B) {
|
||||
var zero zeroReader
|
||||
_, priv, err := ed25519.GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ed25519.Sign(priv, message)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVerification(b *testing.B) {
|
||||
var zero zeroReader
|
||||
pub, priv, err := ed25519.GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
signature := ed25519.Sign(priv, message)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ed25519.Verify(pub, message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +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.
|
||||
|
||||
// +build go1.13
|
||||
|
||||
package ed25519_test
|
||||
|
||||
import (
|
||||
ed25519std "crypto/ed25519"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypeAlias(t *testing.T) {
|
||||
var zero zeroReader
|
||||
public, private, _ := ed25519std.GenerateKey(zero)
|
||||
|
||||
message := []byte("test message")
|
||||
sig := ed25519.Sign(private, message)
|
||||
if !ed25519.Verify(public, message, sig) {
|
||||
t.Errorf("valid signature rejected")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user