Compare commits

..
Author SHA1 Message Date
umlublin cf75a17258 [internal-branch.go1.17-vendor] cryptobyte: fix parsing of large ASN.1 OIDs
Updates golang/go#49678
For golang/go#50165

Change-Id: If8a40e25edd810a66165ab78dd68d9b7fc2699f8
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/365674
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Trust: Alex Rakoczy <alex@golang.org>
Trust: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
(cherry picked from commit e495a2d5b3)
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/372274
Trust: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2021-12-15 16:50:25 +00:00
243 changed files with 10820 additions and 15014 deletions
+3
View File
@@ -0,0 +1,3 @@
# 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.
+3
View File
@@ -0,0 +1,3 @@
# 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.
+375 -91
View File
@@ -3,20 +3,17 @@
// license that can be found in the LICENSE file.
// Package acme provides an implementation of the
// 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.
// 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.
//
// 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 (
@@ -36,6 +33,8 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"net/http"
"strings"
@@ -73,22 +72,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 RFC 7518 for more details about the algorithms.
// See RFC7518 for more details about the algorithms.
Key crypto.Signer
// HTTPClient optionally specifies an HTTP client to use
@@ -126,9 +125,7 @@ type Client struct {
cacheMu sync.Mutex
dir *Directory // cached result of Client's Discover method
// 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
kid keyID // cached Account.URI obtained from registerRFC or getAccountRFC
noncesMu sync.Mutex
nonces map[string]struct{} // nonces collected from previous responses
@@ -143,22 +140,23 @@ 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.KID != noKeyID {
return c.KID
if !c.dir.rfcCompliant() {
return noKeyID
}
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
@@ -179,36 +177,53 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
c.addNonce(res.Header)
var v struct {
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"`
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"`
}
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return Directory{}, err
}
if v.Order == "" {
return Directory{}, errPreRFC
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
}
// RFC compliant ACME CA.
c.dir = &Directory{
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,
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,
}
return *c.dir, nil
}
@@ -220,11 +235,55 @@ func (c *Client) directoryURL() string {
return LetsEncryptURL
}
// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
// 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.
//
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
// 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.
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
return nil, "", errPreRFC
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
}
// FetchCert retrieves already issued certificate from the given url, in DER format.
@@ -238,10 +297,20 @@ 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) {
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
return c.fetchCertRFC(ctx, url, bundle)
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)
}
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
@@ -251,10 +320,30 @@ 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 {
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return err
}
return c.revokeCertRFC(ctx, key, cert, reason)
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
}
// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
@@ -277,47 +366,75 @@ func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL
if c.Key == nil {
return nil, errors.New("acme: client.Key must be set to Register")
}
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
return c.registerRFC(ctx, acct, prompt)
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
}
// GetReg retrieves an existing account associated with c.Key.
//
// The url argument is a legacy artifact of the pre-RFC 8555 API
// and is ignored.
// The url argument is an Account URI used with pre-RFC 8555 CAs.
// It is ignored when interfacing with an RFC-compliant CA.
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
return c.getRegRFC(ctx)
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
}
// UpdateReg updates an existing registration.
// It returns an updated account copy. The provided account is not modified.
//
// The account's URI is ignored and the account URL associated with
// c.Key is used instead.
// When interfacing with RFC-compliant CAs, a.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) {
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
return c.updateRegRFC(ctx, acct)
}
if dir.rfcCompliant() {
return c.updateRegRFC(ctx, acct)
}
// 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)
// 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
}
// Authorize performs the initial step in the pre-authorization flow,
@@ -386,11 +503,17 @@ 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) {
if _, err := c.Discover(ctx); err != nil {
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
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))
}
if err != nil {
return nil, err
}
@@ -412,6 +535,7 @@ 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
}
@@ -441,11 +565,18 @@ 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) {
if _, err := c.Discover(ctx); err != nil {
// Required for c.accountKID() when in RFC mode.
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
getfn := c.postAsGet
if !dir.rfcCompliant() {
getfn = c.get
}
for {
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
if err != nil {
return nil, err
}
@@ -488,11 +619,17 @@ 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) {
if _, err := c.Discover(ctx); err != nil {
// Required for c.accountKID() when in RFC mode.
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
getfn := c.postAsGet
if !dir.rfcCompliant() {
getfn = c.get
}
res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
if err != nil {
return nil, err
}
@@ -510,11 +647,29 @@ 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) {
if _, err := c.Discover(ctx); err != nil {
// Required for c.accountKID() when in RFC mode.
dir, err := c.Discover(ctx)
if err != nil {
return nil, err
}
res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus(
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(
http.StatusOK, // according to the spec
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
))
@@ -565,7 +720,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 the ACME spec.
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of 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 {
@@ -583,7 +738,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 the ACME spec.
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of 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[:])
@@ -650,6 +805,63 @@ 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,
@@ -724,6 +936,78 @@ 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.
@@ -814,5 +1098,5 @@ func encodePEM(typ string, b []byte) []byte {
return pem.EncodeToMemory(pb)
}
// timeNow is time.Now, except in tests which can mess with it.
// timeNow is useful for testing for fixed current time.
var timeNow = time.Now
+656 -15
View File
@@ -79,6 +79,115 @@ 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"}
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)
}))
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
}
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 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 TestRegisterWithoutKey(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "HEAD" {
@@ -104,6 +213,134 @@ func TestRegisterWithoutKey(t *testing.T) {
}
}
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)
}
}
func TestAuthorize(t *testing.T) {
tt := []struct{ typ, value string }{
{"dns", "example.com"},
@@ -254,6 +491,82 @@ 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
@@ -354,34 +667,38 @@ func TestWaitAuthorization(t *testing.T) {
})
}
t.Run("context cancel", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
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(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)
}))
ts := httptest.NewServer(h)
defer ts.Close()
client := &Client{
Key: testKey,
DirectoryURL: ts.URL,
dir: &Directory{},
KID: "some-key-id", // set to avoid lookup attempt
type res struct {
authz *Authorization
err error
}
return client.WaitAuthorization(ctx, ts.URL)
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")
}
func TestRevokeAuthorization(t *testing.T) {
@@ -426,9 +743,238 @@ 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)
}))
@@ -497,6 +1043,42 @@ 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"}})
@@ -617,6 +1199,65 @@ 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"`,
+92 -41
View File
@@ -47,8 +47,6 @@ 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)}
@@ -170,11 +168,6 @@ 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
@@ -463,7 +456,7 @@ func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error
leaf: cert.Leaf,
}
m.state[ck] = s
m.startRenew(ck, s.key, s.leaf.NotAfter)
go m.renew(ck, s.key, s.leaf.NotAfter)
return cert, nil
}
@@ -589,9 +582,8 @@ 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 didRemove(ck)
defer testDidRemoveState(ck)
m.stateMu.Lock()
defer m.stateMu.Unlock()
// Verify the state hasn't changed and it's still invalid
@@ -609,7 +601,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
}
state.cert = der
state.leaf = leaf
m.startRenew(ck, state.key, state.leaf.NotAfter)
go m.renew(ck, state.key, state.leaf.NotAfter)
return state.tlscert()
}
@@ -666,19 +658,31 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck cert
if err != nil {
return nil, nil, err
}
if dir.OrderURL == "" {
return nil, nil, errPreRFC
}
o, err := m.verifyRFC(ctx, client, ck.domain)
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
}
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
@@ -686,6 +690,69 @@ 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) {
@@ -899,7 +966,7 @@ func httpTokenCacheKey(tokenPath string) string {
return path.Base(tokenPath) + "+http-01"
}
// startRenew starts a cert renewal timer loop, one per domain.
// renew 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)
@@ -907,7 +974,7 @@ func httpTokenCacheKey(tokenPath string) string {
//
// The key argument is a certificate private key.
// The exp argument is the cert expiration time (NotAfter).
func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
func (m *Manager) renew(ck certKey, key crypto.Signer, exp time.Time) {
m.renewalMu.Lock()
defer m.renewalMu.Unlock()
if m.renewal[ck] != nil {
@@ -1001,7 +1068,7 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
if m.Email != "" {
contact = []string{"mailto:" + m.Email}
}
a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding}
a := &acme.Account{Contact: contact}
_, err := client.Register(ctx, a, m.Prompt)
if err == nil || isAccountAlreadyExist(err) {
m.client = client
@@ -1133,10 +1200,6 @@ 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:
@@ -1167,18 +1230,6 @@ 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
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -7,6 +7,7 @@ package autocert
import (
"context"
"errors"
"io/ioutil"
"os"
"path/filepath"
)
@@ -40,14 +41,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), filepath.Clean("/"+name))
name = filepath.Join(string(d), name)
var (
data []byte
err error
done = make(chan struct{})
)
go func() {
data, err = os.ReadFile(name)
data, err = ioutil.ReadFile(name)
close(done)
}()
select {
@@ -81,7 +82,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), filepath.Clean("/"+name))
newName := filepath.Join(string(d), name)
err = os.Rename(tmp, newName)
}
}()
@@ -95,7 +96,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), filepath.Clean("/"+name))
name = filepath.Join(string(d), name)
var (
err error
done = make(chan struct{})
@@ -118,7 +119,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 := os.CreateTemp(string(d), prefix)
f, err := ioutil.TempFile(string(d), prefix)
if err != nil {
return "", err
}
+2 -1
View File
@@ -6,6 +6,7 @@ package autocert
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@@ -16,7 +17,7 @@ import (
var _ Cache = DirCache("/")
func TestDirCache(t *testing.T) {
dir, err := os.MkdirTemp("", "autocert")
dir, err := ioutil.TempDir("", "autocert")
if err != nil {
t.Fatal(err)
}
-1
View File
@@ -24,7 +24,6 @@ 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{
+180 -424
View File
@@ -8,30 +8,27 @@
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"
@@ -39,65 +36,56 @@ 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
t *testing.T
server *httptest.Server
issuer pkix.Name
challengeTypes []string
url string
roots *x509.CertPool
eabRequired bool
server *httptest.Server
challengeTypes []string // supported challenge types
domainsWhitelist []string // only these domains are valid for issuing, unless empty
mu sync.Mutex
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
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
}
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),
// 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),
}
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: ca.issuer,
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Acme Co"},
CommonName: "Root CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign,
@@ -112,96 +100,40 @@ func (ca *CAServer) generateRoot() {
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
}
// 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
ca.server = httptest.NewServer(http.HandlerFunc(ca.handle))
ca.URL = ca.server.URL
return ca
}
// 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
// Close shuts down the server and blocks until all outstanding
// requests on this server have completed.
func (ca *CAServer) Close() {
ca.server.Close()
}
func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
return ca.server.URL + fmt.Sprintf(format, arg...)
}
func (ca *CAServer) addr(domain string) (string, bool) {
func (ca *CAServer) addr(domain string) (string, error) {
ca.mu.Lock()
defer ca.mu.Unlock()
addr, ok := ca.domainAddr[domain]
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
if !ok {
return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
}
return addr, nil
}
func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
s := fmt.Sprintf(format, a...)
ca.t.Errorf(format, a...)
log.Println(s)
http.Error(w, s, code)
}
@@ -213,33 +145,11 @@ 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"`
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"`
NewNonce string `json:"newNonce"`
NewReg string `json:"newAccount"`
NewOrder string `json:"newOrder"`
NewAuthz string `json:"newAuthz"`
}
type challenge struct {
@@ -253,7 +163,6 @@ type authorization struct {
Challenges []challenge `json:"challenges"`
domain string
id int
}
type order struct {
@@ -266,7 +175,7 @@ type order struct {
}
func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
ca.t.Logf("%s %s", r.Method, r.URL)
log.Printf("%s %s", r.Method, r.URL)
w.Header().Set("Replay-Nonce", "nonce")
// TODO: Verify nonce header for all POST requests.
@@ -277,12 +186,10 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
// Discovery request.
case r.URL.Path == "/":
resp := &discovery{
NewNonce: ca.serverURL("/new-nonce"),
NewAccount: ca.serverURL("/new-account"),
NewOrder: ca.serverURL("/new-order"),
Meta: discoveryMeta{
ExternalAccountRequired: ca.eabRequired,
},
NewNonce: ca.serverURL("/new-nonce"),
NewReg: ca.serverURL("/new-reg"),
NewOrder: ca.serverURL("/new-order"),
NewAuthz: ca.serverURL("/new-authz"),
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
panic(fmt.Sprintf("discovery response: %v", err))
@@ -294,29 +201,7 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
return
// Client key registration request.
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
}
case r.URL.Path == "/new-reg":
// TODO: Check the user account key against a ca.accountKeys?
w.Header().Set("Location", ca.serverURL("/accounts/1"))
w.WriteHeader(http.StatusCreated)
@@ -336,7 +221,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/%d", z.id))
o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%s", z.domain))
}
orderID := len(ca.orders)
ca.orders = append(ca.orders, o)
@@ -359,50 +244,49 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
panic(err)
}
// 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]
// 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
}
ca.mu.Lock()
supported := false
for _, suppTyp := range ca.challengeTypes {
if suppTyp == typ {
supported = true
}
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))
}
a, err := ca.storedAuthz(id)
// 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]
ca.mu.Unlock()
if !supported {
ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ)
if !exist {
ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: no authz for %q", domain)
return
}
if err != nil {
ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err)
return
}
ca.validateChallenge(a, typ)
go ca.validateChallenge("tls-alpn-01", domain)
w.Write([]byte("{}"))
// Get authorization status requests.
case strings.HasPrefix(r.URL.Path, "/authz/"):
var req struct{ Status string }
decodePayload(&req, r.Body)
deactivate := req.Status == "deactivated"
domain := strings.TrimPrefix(r.URL.Path, "/authz/")
ca.mu.Lock()
defer ca.mu.Unlock()
authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/"))
if err != nil {
ca.httpErrorf(w, http.StatusNotFound, "%v", err)
authz, ok := ca.authorizations[domain]
if !ok {
ca.httpErrorf(w, http.StatusNotFound, "no authz for %q", domain)
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("encoding authz %d: %v", authz.id, err))
panic(fmt.Sprintf("get authz for %q response: %v", domain, err))
}
// Certificate issuance request.
@@ -430,6 +314,15 @@ 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 {
@@ -462,6 +355,25 @@ 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) {
@@ -475,50 +387,46 @@ 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
}
// storedAuthz retrieves a previously created authz at index i.
// authz returns an existing authorization for the identifier or creates a new one.
// 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.validAuthz[identifier]
authz, ok := ca.authorizations[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/%d", typ, authzId),
Token: challengeToken(authz.domain, typ, authzId),
URI: ca.serverURL("/challenge/%s/%s", typ, authz.domain),
Token: challengeToken(authz.domain, typ),
})
}
ca.authorizations = append(ca.authorizations, authz)
ca.authorizations[authz.domain] = 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) {
@@ -539,231 +447,79 @@ func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err erro
return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
}
// 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) {
// TODO: Only tls-alpn-01 is currently supported: implement http-01 and dns-01.
func (ca *CAServer) validateChallenge(typ, identifier string) {
var err error
switch typ {
case "tls-alpn-01":
err = ca.verifyALPNChallenge(authz)
case "http-01":
err = ca.verifyHTTPChallenge(authz)
err = ca.verifyALPNChallenge(identifier)
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
}
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() {
log.Printf("validated %q for %q; authz status is now: %s", typ, identifier, authz.Status)
// 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
}
countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i)
if countInvalid > 0 {
o.Status = acme.StatusInvalid
ca.t.Logf("order %d is now invalid", i)
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++
}
}
if countValid == len(o.AuthzURLs) {
o.Status = acme.StatusReady
o.FinalizeURL = ca.serverURL("/new-cert/%d", i)
ca.t.Logf("order %d is now ready", i)
log.Printf("order %d is now ready", i)
}
}
}
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 {
func (ca *CAServer) verifyALPNChallenge(domain string) error {
const acmeALPNProto = "acme-tls/1"
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)
addr, err := ca.addr(domain)
if err != nil {
return err
}
if haveAddr && haveGetCert {
return fmt.Errorf("overlapping 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
}
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 v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
}
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)
if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
}
// TODO: verify conn.ConnectionState().PeerCertificates[0]
return nil
}
@@ -779,8 +535,8 @@ func decodePayload(v interface{}, r io.Reader) error {
return json.Unmarshal(payload, v)
}
func challengeToken(domain, challType string, authzID int) string {
return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
func challengeToken(domain, challType string) string {
return fmt.Sprintf("token-%s-%s", domain, challType)
}
func unique(a []string) []string {
+1 -1
View File
@@ -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
+10 -25
View File
@@ -21,9 +21,8 @@ type domainRenewal struct {
ck certKey
key crypto.Signer
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
timerMu sync.Mutex
timer *time.Timer
}
// start starts a cert renewal timer at the time
@@ -39,28 +38,16 @@ func (dr *domainRenewal) start(exp time.Time) {
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
}
// 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.
// stop stops the cert renewal timer.
// If the timer is already stopped, calling stop is a noop.
func (dr *domainRenewal) stop() {
dr.timerMu.Lock()
defer dr.timerMu.Unlock()
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()
}
if dr.timer == nil {
return
}
dr.timer.Stop()
dr.timer = nil
}
// renew is called periodically by a timer.
@@ -68,9 +55,7 @@ func (dr *domainRenewal) stop() {
func (dr *domainRenewal) renew() {
dr.timerMu.Lock()
defer dr.timerMu.Unlock()
if dr.timerClose != nil {
close(dr.timerClose)
dr.timer, dr.timerClose = nil, nil
if dr.timer == nil {
return
}
@@ -82,8 +67,8 @@ func (dr *domainRenewal) renew() {
next = renewJitter / 2
next += time.Duration(pseudoRand.int63n(int64(next)))
}
testDidRenewLoop(next, err)
dr.timer = time.AfterFunc(next, dr.renew)
testDidRenewLoop(next, err)
}
// updateState locks and replaces the relevant Manager.state item with the given
+156 -93
View File
@@ -6,13 +6,19 @@ 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) {
@@ -42,47 +48,101 @@ func TestRenewalNext(t *testing.T) {
}
func TestRenewFromCache(t *testing.T) {
man := testManager(t)
man.RenewBefore = 24 * time.Hour
// 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
}
ca := acmetest.NewCAServer(t).Start()
ca.ResolveGetCertificate(exampleDomain, man.GetCertificate)
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()
man.Client = &acme.Client{
DirectoryURL: ca.URL(),
man := &Manager{
Prompt: AcceptTOS,
Cache: newMemCache(t),
RenewBefore: 24 * time.Hour,
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()
c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
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 {
t.Fatal(err)
}
// verify the renewal happened
// veriy 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) {}
}()
renewed := make(chan bool, 1)
done := make(chan struct{})
testDidRenewLoop = func(next time.Duration, err error) {
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.
}
}()
defer close(done)
if err != nil {
t.Errorf("testDidRenewLoop: %v", err)
}
// Next should be about 90 days:
// CaServer creates 90days expiry + account for man.RenewBefore.
// dummyCert creates 90days expiry + account for man.RenewBefore.
// Previous expiration was within 1 min.
future := 88 * 24 * time.Hour
if next < future {
@@ -93,8 +153,7 @@ func TestRenewFromCache(t *testing.T) {
after := time.Now().Add(future)
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
if err != nil {
t.Errorf("man.cacheGet: %v", err)
return
t.Fatalf("man.cacheGet: %v", err)
}
if !tlscert.Leaf.NotAfter.After(after) {
t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
@@ -105,13 +164,11 @@ func TestRenewFromCache(t *testing.T) {
defer man.stateMu.Unlock()
s := man.state[exampleCertKey]
if s == nil {
t.Errorf("m.state[%q] is nil", exampleCertKey)
return
t.Fatalf("m.state[%q] is nil", exampleCertKey)
}
tlscert, err = s.tlscert()
if err != nil {
t.Errorf("s.tlscert: %v", err)
return
t.Fatalf("s.tlscert: %v", err)
}
if !tlscert.Leaf.NotAfter.After(after) {
t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
@@ -123,34 +180,55 @@ func TestRenewFromCache(t *testing.T) {
if _, err := man.GetCertificate(hello); err != nil {
t.Fatal(err)
}
<-renewed
// wait for renew loop
select {
case <-time.After(10 * time.Second):
t.Fatal("renew took too long to occur")
case <-done:
}
}
func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
ca := acmetest.NewCAServer(t).Start()
man := testManager(t)
man.RenewBefore = 24 * time.Hour
man.Client = &acme.Client{
DirectoryURL: "invalid",
man := &Manager{
Prompt: AcceptTOS,
Cache: newMemCache(t),
RenewBefore: 24 * time.Hour,
Client: &acme.Client{
DirectoryURL: "invalid",
},
}
defer man.stopRenew()
// cache a recently renewed cert with a different private key
now := time.Now()
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 {
newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now)
now := time.Now()
newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
if err != nil {
t.Fatal(err)
}
newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey, 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
oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
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)
if err != nil {
t.Fatal(err)
}
@@ -159,32 +237,20 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
man.state = make(map[certKey]*certState)
}
s := &certState{
key: oldCert.PrivateKey.(crypto.Signer),
cert: oldCert.Certificate,
key: key,
cert: [][]byte{oldCert},
leaf: oldLeaf,
}
man.state[exampleCertKey] = s
man.stateMu.Unlock()
// verify the renewal accepted the newer cached cert
// veriy 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) {}
}()
renewed := make(chan bool, 1)
done := make(chan struct{})
testDidRenewLoop = func(next time.Duration, err error) {
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.
}
}()
defer close(done)
if err != nil {
t.Errorf("testDidRenewLoop: %v", err)
}
@@ -198,8 +264,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
// ensure the cached cert was not modified
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
if err != nil {
t.Errorf("man.cacheGet: %v", err)
return
t.Fatalf("man.cacheGet: %v", err)
}
if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
@@ -210,22 +275,30 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
defer man.stateMu.Unlock()
s := man.state[exampleCertKey]
if s == nil {
t.Errorf("m.state[%q] is nil", exampleCertKey)
return
t.Fatalf("m.state[%q] is nil", exampleCertKey)
}
stateKey := s.key.Public().(*ecdsa.PublicKey)
if !stateKey.Equal(newLeaf.PublicKey) {
t.Error("state key was not updated from cache")
return
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)
}
tlscert, err = s.tlscert()
if err != nil {
t.Errorf("s.tlscert: %v", err)
return
t.Fatalf("s.tlscert: %v", err)
}
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
@@ -239,31 +312,21 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
}
// trigger renew
man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter)
<-renewed
func() {
man.renewalMu.Lock()
defer man.renewalMu.Unlock()
go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
// 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
// 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)
}
renewalKey := r.key.Public().(*ecdsa.PublicKey)
if !renewalKey.Equal(newLeaf.PublicKey) {
t.Error("renewal private key was not updated from cache")
if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
}
}()
// 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)
}
}
+3 -7
View File
@@ -10,9 +10,8 @@ import (
"crypto"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"net/http"
"strconv"
@@ -156,7 +155,7 @@ func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Respons
}
}
// postAsGet is POST-as-GET, a replacement for GET in RFC 8555
// postAsGet is POST-as-GET, a replacement for GET in RFC8555
// 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.
@@ -216,9 +215,6 @@ 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)
}
@@ -310,7 +306,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, _ := io.ReadAll(resp.Body)
b, _ := ioutil.ReadAll(resp.Body)
e := &wireError{Status: resp.StatusCode}
if err := json.Unmarshal(b, e); err != nil {
// this is not a regular error response:
+5 -20
View File
@@ -7,7 +7,7 @@ package acme
import (
"context"
"fmt"
"io"
"io/ioutil"
"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: io.NopCloser(strings.NewReader(s)),
Body: ioutil.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 != 3 {
t.Errorf("total requests count: %d; want 3", count)
if count != 4 {
t.Errorf("total requests count: %d; want 4", count)
}
}
@@ -224,7 +224,7 @@ func TestUserAgent(t *testing.T) {
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"newOrder": "sure"}`))
w.Write([]byte(`{}`))
}))
defer ts.Close()
@@ -238,18 +238,3 @@ 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())
}
}
+54 -7
View File
@@ -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,13 +50,14 @@ 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" or "preauthz" (RFC8555).`)
flow = flag.String("f", "", "Flow to run: order, preauthz (RFC8555) or preauthz02 (draft-02).")
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.")
@@ -126,6 +127,8 @@ 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)
}
@@ -273,6 +276,50 @@ 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 {
+11 -39
View File
@@ -20,12 +20,12 @@ import (
"math/big"
)
// KeyID is the account key identity provided by a CA during registration.
type KeyID string
// keyID is the account 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
@@ -33,10 +33,6 @@ 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 {
@@ -47,56 +43,32 @@ type jsonWebSignature struct {
// 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.
//
// 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
// If kid is non-empty, its quoted value is inserted in the protected head
// 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) {
if key == nil {
return nil, errors.New("nil key")
}
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
alg, sha := jwsHasher(key.Public())
if alg == "" || !sha.Available() {
return nil, ErrUnsupportedKey
}
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,
}
var phead string
switch kid {
case noKeyID:
jwk, err := jwkEncode(key.Public())
if err != nil {
return nil, err
}
headers.JWK = json.RawMessage(jwk)
phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
default:
headers.KID = string(kid)
phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
}
phJSON, err := json.Marshal(headers)
if err != nil {
return nil, err
}
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
var payload string
if val, ok := claimset.(string); ok {
payload = val
} else {
if claimset != noPayload {
cs, err := json.Marshal(claimset)
if err != nil {
return nil, err
+1 -39
View File
@@ -195,46 +195,8 @@ 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 (
+4 -68
View File
@@ -13,6 +13,7 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)
@@ -23,9 +24,6 @@ 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
@@ -80,7 +78,7 @@ 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
}
@@ -117,7 +115,7 @@ func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error)
return responseAccount(res)
}
// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
// getGegRFC 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}`)
@@ -150,42 +148,6 @@ 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.
@@ -389,7 +351,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 := io.ReadAll(io.LimitReader(res.Body, max+1))
b, err := ioutil.ReadAll(io.LimitReader(res.Body, max+1))
if err != nil {
return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
}
@@ -448,29 +410,3 @@ 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
}
+60 -193
View File
@@ -15,14 +15,12 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"sync"
"testing"
"time"
@@ -64,7 +62,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)
@@ -148,7 +146,7 @@ func TestRFC_postKID(t *testing.T) {
w.Header().Set("Location", "/account-1")
w.Write([]byte(`{"status":"valid"}`))
case "/post":
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
head, err := decodeJWSHead(bytes.NewReader(b))
if err != nil {
t.Errorf("decodeJWSHead: %v", err)
@@ -177,7 +175,8 @@ func TestRFC_postKID(t *testing.T) {
}))
defer ts.Close()
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cl := &Client{
Key: testKey,
DirectoryURL: ts.URL,
@@ -193,13 +192,13 @@ func TestRFC_postKID(t *testing.T) {
t.Fatal(err)
}
defer res.Body.Close()
b, _ := io.ReadAll(res.Body) // don't care about err - just checking b
b, _ := ioutil.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 RFC 8555 compliant CA.
// acmeServer simulates a subset of RFC8555 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.
@@ -233,7 +232,6 @@ func (s *acmeServer) start() {
"newOrder": %q,
"newAuthz": %q,
"revokeCert": %q,
"keyChange": %q,
"meta": {"termsOfService": %q}
}`,
s.url("/acme/new-nonce"),
@@ -241,7 +239,6 @@ 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
@@ -296,7 +293,7 @@ func TestRFC_Register(t *testing.T) {
"orders": %q
}`, email, s.url("/accounts/1/orders"))
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
head, err := decodeJWSHead(bytes.NewReader(b))
if err != nil {
t.Errorf("decodeJWSHead: %v", err)
@@ -315,7 +312,8 @@ func TestRFC_Register(t *testing.T) {
s.start()
defer s.close()
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cl := &Client{
Key: testKeyEC,
DirectoryURL: s.url("/"),
@@ -346,7 +344,7 @@ 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)
}
}
@@ -452,7 +450,8 @@ func TestRFC_RegisterExternalAccountBinding(t *testing.T) {
s.start()
defer s.close()
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cl := &Client{
Key: testKeyEC,
DirectoryURL: s.url("/"),
@@ -483,7 +482,7 @@ func TestRFC_RegisterExternalAccountBinding(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)
}
}
@@ -503,7 +502,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)
}
@@ -525,7 +524,7 @@ func TestRFC_UpdateReg(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "valid"}`))
b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
head, err := decodeJWSHead(bytes.NewReader(b))
if err != nil {
t.Errorf("decodeJWSHead: %v", err)
@@ -622,128 +621,6 @@ 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) {
@@ -864,13 +741,24 @@ func testWaitOrderStatus(t *testing.T, okStatus string) {
s.start()
defer s.close()
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)
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)
}
}
}
@@ -895,20 +783,30 @@ func TestRFC_WaitOrderError(t *testing.T) {
s.start()
defer s.close()
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)
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)
}
}
}
@@ -948,7 +846,8 @@ func TestRFC_CreateOrderCert(t *testing.T) {
})
s.start()
defer s.close()
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
@@ -983,35 +882,3 @@ 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)
}
}
+10 -2
View File
@@ -297,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 RFC 6844.
// as defined in RFC6844.
CAA []string
// ExternalAccountRequired indicates that the CA requires for all account-related
@@ -305,6 +305,14 @@ 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 {
@@ -440,7 +448,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 RFC 1123 Section 2.1 for IPv4 and in RFC 5952 Section 4 for IPv6.
// in RFC1123 Section 2.1 for IPv4 and in RFC5952 Section 4 for IPv6.
func IPIDs(addr ...string) []AuthzID {
a := make([]AuthzID, len(addr))
for i, v := range addr {
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build go1.12
// +build go1.12
package acme
+6 -4
View File
@@ -11,7 +11,8 @@
// 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
@@ -20,7 +21,8 @@
// 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
@@ -57,7 +59,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
@@ -81,7 +83,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
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
package argon2
+7 -7
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +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 c+24(FP), CX
MOVQ $128, DI
MOVQ a+24(FP), CX
MOVQ $128, BP
loop:
MOVOU 0(AX), X0
@@ -213,7 +213,7 @@ loop:
ADDQ $16, BX
ADDQ $16, CX
ADDQ $16, DX
SUBQ $2, DI
SUBQ $2, BP
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 c+24(FP), CX
MOVQ $128, DI
MOVQ a+24(FP), CX
MOVQ $128, BP
loop:
MOVOU 0(AX), X0
@@ -238,6 +238,6 @@ loop:
ADDQ $16, BX
ADDQ $16, CX
ADDQ $16, DX
SUBQ $2, DI
SUBQ $2, BP
JA loop
RET
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build !amd64 || purego || !gc
// +build !amd64 purego !gc
package argon2
+1 -10
View File
@@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
type InvalidCostError int
func (ic InvalidCostError) Error() string {
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
}
const (
@@ -82,20 +82,11 @@ 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
-7
View File
@@ -241,10 +241,3 @@ 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)
}
}
+2 -1
View File
@@ -2,7 +2,8 @@
// 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
//go:build go1.7 && amd64 && gc && !purego
// +build go1.7,amd64,gc,!purego
package blake2b
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +build go1.7,amd64,gc,!purego
#include "textflag.h"
+25
View File
@@ -0,0 +1,25 @@
// 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.
//go:build !go1.7 && amd64 && gc && !purego
// +build !go1.7,amd64,gc,!purego
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)
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
#include "textflag.h"
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build !amd64 || purego || !gc
// +build !amd64 purego !gc
package blake2b
+3
View File
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.9
// +build go1.9
package blake2b
import (
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build 386 && gc && !purego
// +build 386,gc,!purego
package blake2s
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build 386 && gc && !purego
// +build 386,gc,!purego
#include "textflag.h"
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
package blake2s
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
#include "textflag.h"
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build (!amd64 && !386) || !gc || purego
// +build !amd64,!386 !gc purego
package blake2s
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build go1.9
// +build go1.9
package blake2s
+1 -1
View File
@@ -162,7 +162,7 @@ type G2 struct {
p *twistPoint
}
// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r.
// RandomG1 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
+4 -7
View File
@@ -13,10 +13,7 @@
// golang.org/x/crypto/chacha20poly1305).
package cast5 // import "golang.org/x/crypto/cast5"
import (
"errors"
"math/bits"
)
import "errors"
const BlockSize = 8
const KeySize = 16
@@ -244,19 +241,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 := bits.RotateLeft32(t, int(r))
I := (t << r) | (t >> (32 - 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 := bits.RotateLeft32(t, int(r))
I := (t << r) | (t >> (32 - 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 := bits.RotateLeft32(t, int(r))
I := (t << r) | (t >> (32 - r))
return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
}
+2 -1
View File
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
//go:build go1.11 && gc && !purego
// +build go1.11,gc,!purego
package chacha20
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
// +build go1.11,gc,!purego
#include "textflag.h"
+2 -2
View File
@@ -12,7 +12,7 @@ import (
"errors"
"math/bits"
"golang.org/x/crypto/internal/alias"
"golang.org/x/crypto/internal/subtle"
)
const (
@@ -189,7 +189,7 @@ func (s *Cipher) XORKeyStream(dst, src []byte) {
panic("chacha20: output smaller than input")
}
dst = dst[:len(src)]
if alias.InexactOverlap(dst, src) {
if subtle.InexactOverlap(dst, src) {
panic("chacha20: invalid buffer overlap")
}
+2 -1
View File
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!arm64 && !s390x && !ppc64le) || !gc || purego
//go:build (!arm64 && !s390x && !ppc64le) || (arm64 && !go1.11) || !gc || purego
// +build !arm64,!s390x,!ppc64le arm64,!go1.11 !gc purego
package chacha20
+1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build gc && !purego
// +build gc,!purego
package chacha20
+1 -1
View File
@@ -19,7 +19,7 @@
// The differences in this and the original implementation are
// due to the calling conventions and initialization of constants.
//go:build gc && !purego
// +build gc,!purego
#include "textflag.h"
+1 -1
View File
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build gc && !purego
// +build gc,!purego
package chacha20
@@ -14,7 +15,6 @@ 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)
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
// +build gc,!purego
#include "go_asm.h"
#include "textflag.h"
+1 -5
View File
@@ -26,10 +26,6 @@ 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 {
@@ -51,7 +47,7 @@ func (c *chacha20poly1305) NonceSize() int {
}
func (c *chacha20poly1305) Overhead() int {
return Overhead
return 16
}
func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+4 -3
View File
@@ -3,13 +3,14 @@
// license that can be found in the LICENSE file.
//go:build gc && !purego
// +build gc,!purego
package chacha20poly1305
import (
"encoding/binary"
"golang.org/x/crypto/internal/alias"
"golang.org/x/crypto/internal/subtle"
"golang.org/x/sys/cpu"
)
@@ -55,7 +56,7 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
setupState(&state, &c.key, nonce)
ret, out := sliceForAppend(dst, len(plaintext)+16)
if alias.InexactOverlap(out, plaintext) {
if subtle.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap")
}
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
@@ -72,7 +73,7 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
ciphertext = ciphertext[:len(ciphertext)-16]
ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
if subtle.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
}
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
+3 -23
View File
@@ -4,7 +4,7 @@
// This file was originally from https://golang.org/cl/24717 by Vlad Krasnov of CloudFlare.
//go:build gc && !purego
// +build gc,!purego
#include "textflag.h"
// General register allocation
@@ -183,31 +183,11 @@ 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; ROL16(D, T) \
PADDD B, A; PXOR A, D; PSHUFB ·rol16<>(SB), D \
PADDD D, C; PXOR C, B; MOVO B, T; PSLLL $12, T; PSRLL $20, B; PXOR T, B \
PADDD B, A; PXOR A, D; ROL8(D, T) \
PADDD B, A; PXOR A, D; PSHUFB ·rol8<>(SB), D \
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) \
+4 -4
View File
@@ -8,8 +8,8 @@ import (
"encoding/binary"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/alias"
"golang.org/x/crypto/internal/poly1305"
"golang.org/x/crypto/internal/subtle"
"golang.org/x/crypto/poly1305"
)
func writeWithPadding(p *poly1305.MAC, b []byte) {
@@ -30,7 +30,7 @@ func writeUint64(p *poly1305.MAC, n int) {
func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte {
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
if alias.InexactOverlap(out, plaintext) {
if subtle.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap")
}
@@ -66,7 +66,7 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
writeUint64(p, len(ciphertext))
ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
if subtle.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
}
if !p.Verify(tag) {
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
// +build !amd64 !gc purego
package chacha20poly1305
+1 -1
View File
@@ -35,7 +35,7 @@ func (*xchacha20poly1305) NonceSize() int {
}
func (*xchacha20poly1305) Overhead() int {
return Overhead
return 16
}
func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+42 -58
View File
@@ -264,35 +264,36 @@ 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, 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.
// not point to an integer or to a big.Int, it panics. It reports whether the
// read was successful.
func (s *String) ReadASN1Integer(out interface{}) bool {
switch out := out.(type) {
case *int, *int8, *int16, *int32, *int64:
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:
var i int64
if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
return false
}
reflect.ValueOf(out).Elem().SetInt(i)
return true
case *uint, *uint8, *uint16, *uint32, *uint64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.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 *big.Int:
return s.readASN1BigInt(out)
case *[]byte:
return s.readASN1Bytes(out)
default:
panic("out does not point to an integer type")
case reflect.Struct:
if reflect.TypeOf(out).Elem() == bigIntType {
return s.readASN1BigInt(out.(*big.Int))
}
}
panic("out does not point to an integer type")
}
func checkASN1Integer(bytes []byte) bool {
@@ -332,21 +333,6 @@ 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) {
@@ -431,14 +417,6 @@ func (s *String) readBase128Int(out *int) bool {
}
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
@@ -554,7 +532,7 @@ func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
return false
}
paddingBits := bytes[0]
paddingBits := uint8(bytes[0])
bytes = bytes[1:]
if paddingBits > 7 ||
len(bytes) == 0 && paddingBits != 0 ||
@@ -567,7 +545,7 @@ func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
return true
}
// ReadASN1BitStringAsBytes decodes an ASN.1 BIT STRING into out and advances. It is
// ReadASN1BitString 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 {
@@ -576,7 +554,7 @@ func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
return false
}
paddingBits := bytes[0]
paddingBits := uint8(bytes[0])
if paddingBits != 0 {
return false
}
@@ -676,27 +654,34 @@ 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. Otherwise, it behaves like
// ReadASN1Integer.
// 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.
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 out.(type) {
case *int, *int8, *int16, *int32, *int64,
*uint, *uint8, *uint16, *uint32, *uint64, *[]byte:
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:
reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue))
case *big.Int:
if defaultValue, ok := defaultValue.(*big.Int); ok {
out.(*big.Int).Set(defaultValue)
} else {
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 {
panic("out points to big.Int, but defaultValue does not")
}
out.(*big.Int).Set(defaultValue.(*big.Int))
default:
panic("invalid integer type")
}
@@ -733,14 +718,13 @@ func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag
return true
}
// 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 {
// 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 {
var present bool
var child String
if !s.ReadOptionalASN1(&child, &present, tag) {
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
return false
}
@@ -749,7 +733,7 @@ func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue b
return true
}
return child.ReadASN1Boolean(out)
return s.ReadASN1Boolean(out)
}
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
-49
View File
@@ -115,28 +115,6 @@ 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
@@ -176,32 +154,6 @@ 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 {
@@ -298,7 +250,6 @@ func TestASN1ObjectIdentifier(t *testing.T) {
{[]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 {
+10 -23
View File
@@ -95,16 +95,6 @@ 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...)
@@ -116,13 +106,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
@@ -308,9 +298,9 @@ func (b *Builder) add(bytes ...byte) {
b.result = append(b.result, bytes...)
}
// 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.
// 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.
func (b *Builder) Unwrite(n int) {
if b.err != nil {
return
@@ -322,9 +312,6 @@ 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")
}
+2 -43
View File
@@ -141,7 +141,7 @@ func TestUint24(t *testing.T) {
var s String = b.BytesOrPanic()
var v uint32
if !s.ReadUint24(&v) {
t.Error("ReadUint24() = false, want true")
t.Error("ReadUint8() = 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("ReadUint32() = false, want true")
t.Error("ReadUint8() = false, want true")
}
if v != 0xfffefdfc {
t.Errorf("v = %x, want fffefdfc", v)
@@ -179,47 +179,6 @@ 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)
-22
View File
@@ -81,28 +81,6 @@ 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 {
+41 -5
View File
@@ -5,11 +5,13 @@
// 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
@@ -25,7 +27,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) {
scalarBaseMult(dst, scalar)
ScalarMult(dst, scalar, &basePoint)
}
const (
@@ -38,10 +40,21 @@ const (
// Basepoint is the canonical Curve25519 generator.
var Basepoint []byte
var basePoint = [32]byte{9}
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}
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.
@@ -57,3 +70,26 @@ 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
}
+241
View File
@@ -0,0 +1,241 @@
// 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.
//go:build amd64 && gc && !purego
// +build amd64,gc,!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
-105
View File
@@ -1,105 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !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")
}
}
+828
View File
@@ -0,0 +1,828 @@
// 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)
}
-46
View File
@@ -1,46 +0,0 @@
// 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())
}
+12
View File
@@ -0,0 +1,12 @@
// 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 !amd64 || !gc || purego
// +build !amd64 !gc purego
package curve25519
func scalarMult(out, in, base *[32]byte) {
scalarMultGeneric(out, in, base)
}
+16 -48
View File
@@ -2,15 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package curve25519_test
package curve25519
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"testing"
"golang.org/x/crypto/curve25519"
)
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
@@ -21,25 +19,25 @@ func TestX25519Basepoint(t *testing.T) {
for i := 0; i < 200; i++ {
var err error
x, err = curve25519.X25519(x, curve25519.Basepoint)
x, err = X25519(x, Basepoint)
if err != nil {
t.Fatal(err)
}
}
result := hex.EncodeToString(x)
result := fmt.Sprintf("%x", x)
if result != expectedHex {
t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
}
}
func TestLowOrderPoints(t *testing.T) {
scalar := make([]byte, curve25519.ScalarSize)
scalar := make([]byte, ScalarSize)
if _, err := rand.Read(scalar); err != nil {
t.Fatal(err)
}
for i, p := range lowOrderPoints {
out, err := curve25519.X25519(scalar, p)
out, err := X25519(scalar, p)
if err == nil {
t.Errorf("%d: expected error, got nil", i)
}
@@ -50,10 +48,11 @@ func TestLowOrderPoints(t *testing.T) {
}
func TestTestVectors(t *testing.T) {
t.Run("Legacy", func(t *testing.T) { testTestVectors(t, curve25519.ScalarMult) })
t.Run("Generic", func(t *testing.T) { testTestVectors(t, scalarMultGeneric) })
t.Run("Native", func(t *testing.T) { testTestVectors(t, ScalarMult) })
t.Run("X25519", func(t *testing.T) {
testTestVectors(t, func(dst, scalar, point *[32]byte) {
out, err := curve25519.X25519(scalar[:], point[:])
out, err := X25519(scalar[:], point[:])
if err != nil {
t.Fatal(err)
}
@@ -90,53 +89,22 @@ func TestHighBitIgnored(t *testing.T) {
var hi0, hi1 [32]byte
u[31] &= 0x7f
curve25519.ScalarMult(&hi0, &s, &u)
ScalarMult(&hi0, &s, &u)
u[31] |= 0x80
curve25519.ScalarMult(&hi1, &s, &u)
ScalarMult(&hi1, &s, &u)
if !bytes.Equal(hi0[:], hi1[:]) {
t.Errorf("high bit of group point should not affect result")
}
}
var benchmarkSink byte
func BenchmarkScalarBaseMult(b *testing.B) {
var in, out [32]byte
in[0] = 1
func BenchmarkX25519Basepoint(b *testing.B) {
scalar := make([]byte, curve25519.ScalarSize)
if _, err := rand.Read(scalar); err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.SetBytes(32)
for i := 0; i < b.N; i++ {
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]
ScalarBaseMult(&out, &in)
}
}
-7
View File
@@ -1,7 +0,0 @@
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.
@@ -1,298 +0,0 @@
// 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
}
-16
View File
@@ -1,16 +0,0 @@
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 => ../../../..
-51
View File
@@ -1,51 +0,0 @@
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=
-416
View File
@@ -1,416 +0,0 @@
// 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
}
-126
View File
@@ -1,126 +0,0 @@
// 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)
}
}
}
-15
View File
@@ -1,15 +0,0 @@
// 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)
-378
View File
@@ -1,378 +0,0 @@
// 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
@@ -1,11 +0,0 @@
// 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) }
-15
View File
@@ -1,15 +0,0 @@
// 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
}
-42
View File
@@ -1,42 +0,0 @@
// 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
@@ -1,11 +0,0 @@
// 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()
}
@@ -1,36 +0,0 @@
// 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)
}
}
-264
View File
@@ -1,264 +0,0 @@
// 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
}
-558
View File
@@ -1,558 +0,0 @@
// 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
}
@@ -1 +0,0 @@
b0c49ae9f59d233526f8934262c5bbbe14d4358d
-19
View File
@@ -1,19 +0,0 @@
#! /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
+1 -1
View File
@@ -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_test
package curve25519
// lowOrderPoints from libsodium.
// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
+170 -18
View File
@@ -1,7 +1,13 @@
// Copyright 2019 The Go Authors. All rights reserved.
// 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.
// 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.
//
//go:build !go1.13
// +build !go1.13
// Package ed25519 implements the Ed25519 signature algorithm. See
// https://ed25519.cr.yp.to/.
//
@@ -10,15 +16,21 @@
// 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 (
"crypto/ed25519"
"bytes"
"crypto"
cryptorand "crypto/rand"
"crypto/sha512"
"errors"
"io"
"strconv"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
const (
@@ -33,21 +45,57 @@ const (
)
// 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
type PublicKey []byte
// 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
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
}
// 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)
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
}
// NewKeyFromSeed calculates a private key from a seed. It will panic if
@@ -55,17 +103,121 @@ 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 {
return ed25519.NewKeyFromSeed(seed)
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
}
// 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)
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
}
// 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)
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[:])
}
+74
View File
@@ -0,0 +1,74 @@
// 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.13
// +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)
}
+203 -4
View File
@@ -1,22 +1,221 @@
// Copyright 2019 The Go Authors. All rights reserved.
// 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.
package ed25519_test
import (
ed25519std "crypto/ed25519"
"bufio"
"bytes"
"compress/gzip"
"crypto"
"crypto/rand"
"encoding/hex"
"os"
"strings"
"testing"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
func TestTypeAlias(t *testing.T) {
public, private, _ := ed25519std.GenerateKey(nil)
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)
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)
}
}
+25
View File
@@ -0,0 +1,25 @@
// 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.13
// +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
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+5 -6
View File
@@ -1,11 +1,10 @@
module golang.org/x/crypto
go 1.18
go 1.17
require (
golang.org/x/net v0.21.0 // tagx:ignore
golang.org/x/sys v0.17.0
golang.org/x/term v0.17.0
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
golang.org/x/text v0.3.3 // indirect
)
require golang.org/x/text v0.14.0 // indirect
+9 -8
View File
@@ -1,8 +1,9 @@
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+1 -3
View File
@@ -56,9 +56,7 @@ func (f *hkdf) Read(p []byte) (int, error) {
// Fill the rest of the buffer
for len(p) > 0 {
if f.counter > 1 {
f.expander.Reset()
}
f.expander.Reset()
f.expander.Write(f.prev)
f.expander.Write(f.info)
f.expander.Write([]byte{f.counter})
@@ -3,9 +3,11 @@
// license that can be found in the LICENSE file.
//go:build !purego
// +build !purego
// Package alias implements memory aliasing tests.
package alias
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
package subtle // import "golang.org/x/crypto/internal/subtle"
import "unsafe"
@@ -3,9 +3,11 @@
// license that can be found in the LICENSE file.
//go:build purego
// +build purego
// Package alias implements memory aliasing tests.
package alias
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
package subtle // import "golang.org/x/crypto/internal/subtle"
// This is the Google App Engine standard variant based on reflect
// because the unsafe package and cgo are disallowed.
@@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package alias
package subtle_test
import "testing"
import (
"testing"
"golang.org/x/crypto/internal/subtle"
)
var a, b [100]byte
@@ -28,11 +32,11 @@ var aliasingTests = []struct {
}
func testAliasing(t *testing.T, i int, x, y []byte, anyOverlap, inexactOverlap bool) {
any := AnyOverlap(x, y)
any := subtle.AnyOverlap(x, y)
if any != anyOverlap {
t.Errorf("%d: wrong AnyOverlap result, expected %v, got %v", i, anyOverlap, any)
}
inexact := InexactOverlap(x, y)
inexact := subtle.InexactOverlap(x, y)
if inexact != inexactOverlap {
t.Errorf("%d: wrong InexactOverlap result, expected %v, got %v", i, inexactOverlap, any)
}

Some files were not shown because too many files have changed in this diff Show More