Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b5121be2f |
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Add no patterns to .gitignore except for files generated by the build.
|
||||
# Add no patterns to .hgignore except for files generated by the build.
|
||||
last-change
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -1,7 +1,5 @@
|
||||
# Go Cryptography
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/crypto)
|
||||
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
## Download/Install
|
||||
|
||||
+311
-180
@@ -3,20 +3,14 @@
|
||||
// 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.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02 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 +30,8 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -45,7 +41,7 @@ import (
|
||||
|
||||
const (
|
||||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
LetsEncryptURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
// ALPNProto is the ALPN protocol name used by a CA server when validating
|
||||
// tls-alpn-01 challenges.
|
||||
@@ -56,16 +52,12 @@ const (
|
||||
ALPNProto = "acme-tls/1"
|
||||
)
|
||||
|
||||
// idPeACMEIdentifier is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
||||
var idPeACMEIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
|
||||
const (
|
||||
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||
maxCertSize = 1 << 20 // max size of a certificate, in DER bytes
|
||||
// Used for decoding certs from application/pem-certificate-chain response,
|
||||
// the default when in RFC mode.
|
||||
maxCertChainSize = maxCertSize * maxChainLen
|
||||
maxCertSize = 1 << 20 // max size of a certificate, in bytes
|
||||
|
||||
// Max number of collected nonces kept in memory.
|
||||
// Expect usual peak of 1 or 2.
|
||||
@@ -73,22 +65,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
|
||||
@@ -117,56 +109,21 @@ type Client struct {
|
||||
// The jitter is a random value up to 1 second.
|
||||
RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
|
||||
|
||||
// UserAgent is prepended to the User-Agent header sent to the ACME server,
|
||||
// which by default is this package's name and version.
|
||||
//
|
||||
// Reusable libraries and tools in particular should set this value to be
|
||||
// identifiable by the server, in case they are causing issues.
|
||||
UserAgent string
|
||||
|
||||
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
|
||||
dirMu sync.Mutex // guards writes to dir
|
||||
dir *Directory // cached result of Client's Discover method
|
||||
|
||||
noncesMu sync.Mutex
|
||||
nonces map[string]struct{} // nonces collected from previous responses
|
||||
}
|
||||
|
||||
// accountKID returns a key ID associated with c.Key, the account identity
|
||||
// provided by the CA during RFC based registration.
|
||||
// It assumes c.Discover has already been called.
|
||||
//
|
||||
// accountKID requires at most one network roundtrip.
|
||||
// It caches only successful result.
|
||||
//
|
||||
// When in pre-RFC mode or when c.getRegRFC responds with an error, accountKID
|
||||
// returns noKeyID.
|
||||
func (c *Client) accountKID(ctx context.Context) KeyID {
|
||||
c.cacheMu.Lock()
|
||||
defer c.cacheMu.Unlock()
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
// a network round-trip. This also means mutating c.DirectoryURL after successful call
|
||||
// of this method will have no effect.
|
||||
func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
c.cacheMu.Lock()
|
||||
defer c.cacheMu.Unlock()
|
||||
c.dirMu.Lock()
|
||||
defer c.dirMu.Unlock()
|
||||
if c.dir != nil {
|
||||
return *c.dir, nil
|
||||
}
|
||||
@@ -179,36 +136,27 @@ 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"`
|
||||
Authz string `json:"new-authz"`
|
||||
Cert string `json:"new-cert"`
|
||||
Revoke string `json:"revoke-cert"`
|
||||
Meta struct {
|
||||
Terms string `json:"terms-of-service"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caa-identities"`
|
||||
}
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
if v.Order == "" {
|
||||
return Directory{}, errPreRFC
|
||||
}
|
||||
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.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
CertURL: v.Cert,
|
||||
RevokeURL: v.Revoke,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
@@ -220,28 +168,69 @@ 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.
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
|
||||
// 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, c.Key, 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.
|
||||
// It retries the request until the certificate is successfully retrieved,
|
||||
// context is cancelled by the caller or an error response is received.
|
||||
//
|
||||
// If the bundle argument is true, the returned value also contains the CA (issuer)
|
||||
// certificate chain.
|
||||
// The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
|
||||
//
|
||||
// FetchCert 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 expected features.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.fetchCertRFC(ctx, url, bundle)
|
||||
return c.responseCert(ctx, res, bundle)
|
||||
}
|
||||
|
||||
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
|
||||
@@ -254,87 +243,88 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte,
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.revokeCertRFC(ctx, key, cert, reason)
|
||||
|
||||
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),
|
||||
}
|
||||
if key == nil {
|
||||
key = c.Key
|
||||
}
|
||||
res, err := c.post(ctx, key, c.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
|
||||
// during account registration. See Register method of Client for more details.
|
||||
func AcceptTOS(tosURL string) bool { return true }
|
||||
|
||||
// Register creates a new account with the CA using c.Key.
|
||||
// It returns the registered account. The account acct is not modified.
|
||||
// Register creates a new account registration by following the "new-reg" flow.
|
||||
// It returns the registered account. The account is not modified.
|
||||
//
|
||||
// The registration may require the caller to agree to the CA's Terms of Service (TOS).
|
||||
// If so, and the account has not indicated the acceptance of the terms (see Account for details),
|
||||
// Register calls prompt with a TOS URL provided by the CA. Prompt should report
|
||||
// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
|
||||
//
|
||||
// When interfacing with an RFC-compliant CA, non-RFC 8555 fields of acct are ignored
|
||||
// and prompt is called if Directory's Terms field is non-zero.
|
||||
// Also see Error's Instance field for when a CA requires already registered accounts to agree
|
||||
// to an updated Terms of Service.
|
||||
func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
if c.Key == nil {
|
||||
return nil, errors.New("acme: client.Key must be set to Register")
|
||||
}
|
||||
func (c *Client) Register(ctx context.Context, a *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.registerRFC(ctx, acct, prompt)
|
||||
|
||||
var err error
|
||||
if a, err = c.doReg(ctx, c.dir.RegURL, "new-reg", a); 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.
|
||||
// GetReg retrieves an existing registration.
|
||||
// The url argument is an Account URI.
|
||||
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
a, err := c.doReg(ctx, url, "reg", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.getRegRFC(ctx)
|
||||
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.
|
||||
func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) {
|
||||
uri := a.URI
|
||||
a, err := c.doReg(ctx, uri, "reg", a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.updateRegRFC(ctx, acct)
|
||||
a.URI = uri
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// AccountKeyRollover attempts to transition a client's account key to a new key.
|
||||
// On success client's Key is updated which is not concurrency safe.
|
||||
// On failure an error will be returned.
|
||||
// The new key is already registered with the ACME provider if the following is true:
|
||||
// - error is of type acme.Error
|
||||
// - StatusCode should be 409 (Conflict)
|
||||
// - Location header will have the KID of the associated account
|
||||
//
|
||||
// More about account key rollover can be found at
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.3.5.
|
||||
func (c *Client) AccountKeyRollover(ctx context.Context, newKey crypto.Signer) error {
|
||||
return c.accountKeyRollover(ctx, newKey)
|
||||
}
|
||||
|
||||
// Authorize performs the initial step in the pre-authorization flow,
|
||||
// as opposed to order-based flow.
|
||||
// Authorize performs the initial step in an authorization flow.
|
||||
// The caller will then need to choose from and perform a set of returned
|
||||
// challenges using c.Accept in order to successfully complete authorization.
|
||||
//
|
||||
// Once complete, the caller can use AuthorizeOrder which the CA
|
||||
// should provision with the already satisfied authorization.
|
||||
// For pre-RFC CAs, the caller can proceed directly to requesting a certificate
|
||||
// using CreateCert method.
|
||||
//
|
||||
// If an authorization has been previously granted, the CA may return
|
||||
// a valid authorization which has its Status field set to StatusValid.
|
||||
//
|
||||
// More about pre-authorization can be found at
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.4.1.
|
||||
// a valid authorization (Authorization.Status is StatusValid). If so, the caller
|
||||
// need not fulfill any challenge and can proceed to requesting a certificate.
|
||||
func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
|
||||
return c.authorize(ctx, "dns", domain)
|
||||
}
|
||||
@@ -365,7 +355,7 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
|
||||
Resource: "new-authz",
|
||||
Identifier: authzID{Type: typ, Value: val},
|
||||
}
|
||||
res, err := c.post(ctx, nil, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
|
||||
res, err := c.post(ctx, c.Key, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -386,11 +376,7 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -407,15 +393,11 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati
|
||||
// The url argument is an Authorization.URI value.
|
||||
//
|
||||
// If successful, the caller will be required to obtain a new authorization
|
||||
// using the Authorize or AuthorizeOrder methods before being able to request
|
||||
// a new certificate for the domain associated with the authorization.
|
||||
// using the Authorize method before being able to request a new certificate
|
||||
// for the domain associated with the authorization.
|
||||
//
|
||||
// It does not revoke existing certificates.
|
||||
func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Status string `json:"status"`
|
||||
@@ -425,7 +407,7 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
Status: "deactivated",
|
||||
Delete: true,
|
||||
}
|
||||
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
||||
res, err := c.post(ctx, c.Key, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -441,11 +423,8 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -488,15 +467,10 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
v := wireChallenge{URI: url}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
@@ -510,11 +484,21 @@ 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 {
|
||||
auth, err := keyAuth(c.Key.Public(), chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus(
|
||||
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, c.Key, chal.URI, req, wantStatus(
|
||||
http.StatusOK, // according to the spec
|
||||
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||
))
|
||||
@@ -564,8 +548,21 @@ func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
// The implementation is incomplete in that the returned value is a single certificate,
|
||||
// computed only for Z0 of the key authorization. ACME CAs are expected to update
|
||||
// their implementations to use the newer version, TLS-SNI-02.
|
||||
// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name of the TLS ClientHello matches exactly the returned name value.
|
||||
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 {
|
||||
@@ -582,8 +579,17 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-SNI-02 see
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
@@ -628,7 +634,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: idPeACMEIdentifier,
|
||||
Id: idPeACMEIdentifierV1,
|
||||
Critical: true,
|
||||
Value: extValue,
|
||||
}
|
||||
@@ -650,17 +656,70 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests.
|
||||
// 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, c.Key, 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,
|
||||
// the provided url.
|
||||
// or fetches a fresh one from a URL by issuing a HEAD request.
|
||||
// It first tries c.directoryURL() and then the provided url if the former fails.
|
||||
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) == 0 {
|
||||
if c.dir != nil && c.dir.NonceURL != "" {
|
||||
return c.fetchNonce(ctx, c.dir.NonceURL)
|
||||
}
|
||||
dirURL := c.directoryURL()
|
||||
v, err := c.fetchNonce(ctx, dirURL)
|
||||
if err != nil && url != dirURL {
|
||||
@@ -724,6 +783,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 +945,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
|
||||
|
||||
+650
-104
@@ -16,7 +16,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -27,22 +26,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// newTestClient creates a client with a non-nil Directory so that it skips
|
||||
// the discovery which is otherwise done on the first call of almost every
|
||||
// exported method.
|
||||
func newTestClient() *Client {
|
||||
return &Client{
|
||||
Key: testKeyEC,
|
||||
dir: &Directory{}, // skip discovery
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
|
||||
// interface.
|
||||
func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) {
|
||||
func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) {
|
||||
// Decode request
|
||||
var req struct{ Payload string }
|
||||
if err := json.NewDecoder(r).Decode(&req); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
|
||||
@@ -58,14 +47,12 @@ func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) {
|
||||
type jwsHead struct {
|
||||
Alg string
|
||||
Nonce string
|
||||
URL string `json:"url"`
|
||||
KID string `json:"kid"`
|
||||
JWK map[string]string `json:"jwk"`
|
||||
}
|
||||
|
||||
func decodeJWSHead(r io.Reader) (*jwsHead, error) {
|
||||
func decodeJWSHead(r *http.Request) (*jwsHead, error) {
|
||||
var req struct{ Protected string }
|
||||
if err := json.NewDecoder(r).Decode(&req); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(req.Protected)
|
||||
@@ -79,28 +66,232 @@ func decodeJWSHead(r io.Reader) (*jwsHead, error) {
|
||||
return &head, nil
|
||||
}
|
||||
|
||||
func TestRegisterWithoutKey(t *testing.T) {
|
||||
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)
|
||||
|
||||
// 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)
|
||||
fmt.Fprint(w, `{}`)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{"contact": %s}`, b)
|
||||
}))
|
||||
defer ts.Close()
|
||||
// First verify that using a complete client results in success.
|
||||
|
||||
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},
|
||||
}
|
||||
if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err != nil {
|
||||
t.Fatalf("c.Register() = %v; want success with a complete test client", err)
|
||||
a := &Account{Contact: contacts}
|
||||
var err error
|
||||
if a, err = c.Register(context.Background(), a, prompt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Key = nil
|
||||
if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err == nil {
|
||||
t.Error("c.Register() from client without key succeeded, wanted error")
|
||||
if a.URI != "https://ca.tld/acme/reg/1" {
|
||||
t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.CurrentTerms != "https://ca.tld/acme/terms" {
|
||||
t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Contact, contacts) {
|
||||
t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReg(t *testing.T) {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// 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}
|
||||
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)
|
||||
|
||||
// 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}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +318,7 @@ func TestAuthorize(t *testing.T) {
|
||||
Value string
|
||||
}
|
||||
}
|
||||
decodeJWSRequest(t, &j, r.Body)
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-authz" {
|
||||
@@ -254,6 +445,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}
|
||||
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
|
||||
@@ -281,45 +548,6 @@ func TestWaitAuthorization(t *testing.T) {
|
||||
t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
|
||||
}
|
||||
})
|
||||
t.Run("invalid status with error returns the authorization error", func(t *testing.T) {
|
||||
_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, `{
|
||||
"type": "dns-01",
|
||||
"status": "invalid",
|
||||
"error": {
|
||||
"type": "urn:ietf:params:acme:error:caa",
|
||||
"detail": "CAA record for <domain> prevents issuance",
|
||||
"status": 403
|
||||
},
|
||||
"url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx",
|
||||
"token": "xxx",
|
||||
"validationRecord": [
|
||||
{
|
||||
"hostname": "<domain>"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
})
|
||||
|
||||
want := &AuthorizationError{
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for <domain> prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
}
|
||||
|
||||
_, ok := err.(*AuthorizationError)
|
||||
if !ok {
|
||||
t.Errorf("err is %T; want non-nil *AuthorizationError", err)
|
||||
}
|
||||
|
||||
if err.Error() != want.Error() {
|
||||
t.Errorf("err is %v; want %v", err, want)
|
||||
}
|
||||
})
|
||||
t.Run("non-retriable error", func(t *testing.T) {
|
||||
const code = http.StatusBadRequest
|
||||
_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -354,34 +582,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
|
||||
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) {
|
||||
@@ -397,7 +629,7 @@ func TestRevokeAuthorization(t *testing.T) {
|
||||
Status string
|
||||
Delete bool
|
||||
}
|
||||
decodeJWSRequest(t, &req, r.Body)
|
||||
decodeJWSRequest(t, &req, r)
|
||||
if req.Resource != "authz" {
|
||||
t.Errorf("req.Resource = %q; want authz", req.Resource)
|
||||
}
|
||||
@@ -412,11 +644,7 @@ func TestRevokeAuthorization(t *testing.T) {
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
client := &Client{
|
||||
Key: testKey,
|
||||
DirectoryURL: ts.URL, // don't dial outside of localhost
|
||||
dir: &Directory{}, // don't do discovery
|
||||
}
|
||||
client := &Client{Key: testKey}
|
||||
ctx := context.Background()
|
||||
if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil {
|
||||
t.Errorf("err = %v", err)
|
||||
@@ -426,19 +654,241 @@ 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}
|
||||
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)
|
||||
|
||||
// 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}
|
||||
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)
|
||||
|
||||
// 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()
|
||||
res, err := (&Client{}).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()
|
||||
res, err := (&Client{}).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)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
defer ts.Close()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
cl := newTestClient()
|
||||
_, err = cl.FetchCert(ctx, ts.URL, false)
|
||||
_, err = (&Client{}).FetchCert(ctx, ts.URL, false)
|
||||
close(done)
|
||||
}()
|
||||
cancel()
|
||||
@@ -461,8 +911,7 @@ func TestFetchCertDepth(t *testing.T) {
|
||||
w.Write([]byte{count})
|
||||
}))
|
||||
defer ts.Close()
|
||||
cl := newTestClient()
|
||||
_, err := cl.FetchCert(context.Background(), ts.URL, true)
|
||||
_, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
|
||||
if err == nil {
|
||||
t.Errorf("err is nil")
|
||||
}
|
||||
@@ -477,8 +926,7 @@ func TestFetchCertBreadth(t *testing.T) {
|
||||
w.Write([]byte{1})
|
||||
}))
|
||||
defer ts.Close()
|
||||
cl := newTestClient()
|
||||
_, err := cl.FetchCert(context.Background(), ts.URL, true)
|
||||
_, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
|
||||
if err == nil {
|
||||
t.Errorf("err is nil")
|
||||
}
|
||||
@@ -490,13 +938,48 @@ func TestFetchCertSize(t *testing.T) {
|
||||
w.Write(b)
|
||||
}))
|
||||
defer ts.Close()
|
||||
cl := newTestClient()
|
||||
_, err := cl.FetchCert(context.Background(), ts.URL, false)
|
||||
_, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
|
||||
if err == nil {
|
||||
t.Errorf("err is nil")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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"}})
|
||||
@@ -540,7 +1023,7 @@ func TestNonce_fetch(t *testing.T) {
|
||||
defer ts.Close()
|
||||
for ; i < len(tests); i++ {
|
||||
test := tests[i]
|
||||
c := newTestClient()
|
||||
c := &Client{}
|
||||
n, err := c.fetchNonce(context.Background(), ts.URL)
|
||||
if n != test.nonce {
|
||||
t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
|
||||
@@ -559,7 +1042,7 @@ func TestNonce_fetchError(t *testing.T) {
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
}))
|
||||
defer ts.Close()
|
||||
c := newTestClient()
|
||||
c := &Client{}
|
||||
_, err := c.fetchNonce(context.Background(), ts.URL)
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
@@ -617,6 +1100,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)
|
||||
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"`,
|
||||
@@ -647,7 +1189,8 @@ func TestTLSSNI01ChallengeCert(t *testing.T) {
|
||||
san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
|
||||
)
|
||||
|
||||
tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token)
|
||||
client := &Client{Key: testKeyEC}
|
||||
tlscert, name, err := client.TLSSNI01ChallengeCert(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -679,7 +1222,8 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
|
||||
sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
|
||||
)
|
||||
|
||||
tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token)
|
||||
client := &Client{Key: testKeyEC}
|
||||
tlscert, name, err := client.TLSSNI02ChallengeCert(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -719,7 +1263,8 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain)
|
||||
client := &Client{Key: testKeyEC}
|
||||
tlscert, err := client.TLSALPN01ChallengeCert(token, domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -740,7 +1285,7 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
}
|
||||
acmeExts := []pkix.Extension{}
|
||||
for _, ext := range cert.Extensions {
|
||||
if idPeACMEIdentifier.Equal(ext.Id) {
|
||||
if idPeACMEIdentifierV1.Equal(ext.Id) {
|
||||
acmeExts = append(acmeExts, ext)
|
||||
}
|
||||
}
|
||||
@@ -768,7 +1313,7 @@ func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
}
|
||||
opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
|
||||
|
||||
client := newTestClient()
|
||||
client := &Client{Key: testKeyEC}
|
||||
cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -826,7 +1371,7 @@ func TestHTTP01Challenge(t *testing.T) {
|
||||
value = token + "." + testKeyECThumbprint
|
||||
urlpath = "/.well-known/acme-challenge/" + token
|
||||
)
|
||||
client := newTestClient()
|
||||
client := &Client{Key: testKeyEC}
|
||||
val, err := client.HTTP01ChallengeResponse(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -845,7 +1390,8 @@ func TestDNS01ChallengeRecord(t *testing.T) {
|
||||
// base64 | tr -d '=' | tr '/+' '_-'
|
||||
const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo"
|
||||
|
||||
val, err := newTestClient().DNS01ChallengeRecord("xxx")
|
||||
client := &Client{Key: testKeyEC}
|
||||
val, err := client.DNS01ChallengeRecord("xxx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+164
-206
@@ -35,9 +35,6 @@ import (
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// DefaultACMEDirectory is the default ACME Directory URL used when the Manager's Client is nil.
|
||||
const DefaultACMEDirectory = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
// createCertRetryAfter is how much time to wait before removing a failed state
|
||||
// entry due to an unsuccessful createCert call.
|
||||
// This is a variable instead of a const for testing.
|
||||
@@ -47,8 +44,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)}
|
||||
@@ -93,9 +88,9 @@ func defaultHostPolicy(context.Context, string) error {
|
||||
}
|
||||
|
||||
// Manager is a stateful certificate manager built on top of acme.Client.
|
||||
// It obtains and refreshes certificates automatically using "tls-alpn-01"
|
||||
// or "http-01" challenge types, as well as providing them to a TLS server
|
||||
// via tls.Config.
|
||||
// It obtains and refreshes certificates automatically using "tls-alpn-01",
|
||||
// "tls-sni-01", "tls-sni-02" and "http-01" challenge types,
|
||||
// as well as providing them to a TLS server via tls.Config.
|
||||
//
|
||||
// You must specify a cache implementation, such as DirCache,
|
||||
// to reuse obtained certificates across program restarts.
|
||||
@@ -140,10 +135,9 @@ type Manager struct {
|
||||
// Client is used to perform low-level operations, such as account registration
|
||||
// and requesting new certificates.
|
||||
//
|
||||
// If Client is nil, a zero-value acme.Client is used with DefaultACMEDirectory
|
||||
// as the directory endpoint.
|
||||
// If the Client.Key is nil, a new ECDSA P-256 key is generated and,
|
||||
// if Cache is not nil, stored in cache.
|
||||
// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL
|
||||
// as directory endpoint. If the Client.Key is nil, a new ECDSA P-256 key is
|
||||
// generated and, if Cache is not nil, stored in cache.
|
||||
//
|
||||
// Mutating the field after the first call of GetCertificate method will have no effect.
|
||||
Client *acme.Client
|
||||
@@ -170,11 +164,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
|
||||
|
||||
@@ -185,8 +174,8 @@ type Manager struct {
|
||||
renewalMu sync.Mutex
|
||||
renewal map[certKey]*domainRenewal
|
||||
|
||||
// challengeMu guards tryHTTP01, certTokens and httpTokens.
|
||||
challengeMu sync.RWMutex
|
||||
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
|
||||
tokensMu sync.RWMutex
|
||||
// tryHTTP01 indicates whether the Manager should try "http-01" challenge type
|
||||
// during the authorization flow.
|
||||
tryHTTP01 bool
|
||||
@@ -195,11 +184,12 @@ type Manager struct {
|
||||
// to be provisioned.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
httpTokens map[string][]byte
|
||||
// certTokens contains temporary certificates for tls-alpn-01 challenges
|
||||
// and is keyed by the domain name which matches the ClientHello server name.
|
||||
// certTokens contains temporary certificates for tls-sni and tls-alpn challenges
|
||||
// and is keyed by token domain name, which matches server name of ClientHello.
|
||||
// Keys always have ".acme.invalid" suffix for tls-sni. Otherwise, they are domain names
|
||||
// for tls-alpn.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
certTokens map[string]*tls.Certificate
|
||||
|
||||
// nowFunc, if not nil, returns the current time. This may be set for
|
||||
// testing purposes.
|
||||
nowFunc func() time.Time
|
||||
@@ -236,7 +226,7 @@ func (m *Manager) TLSConfig() *tls.Config {
|
||||
|
||||
// GetCertificate implements the tls.Config.GetCertificate hook.
|
||||
// It provides a TLS certificate for hello.ServerName host, including answering
|
||||
// tls-alpn-01 challenges.
|
||||
// tls-alpn-01 and *.acme.invalid (tls-sni-01 and tls-sni-02) challenges.
|
||||
// All other fields of hello are ignored.
|
||||
//
|
||||
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
|
||||
@@ -245,7 +235,9 @@ func (m *Manager) TLSConfig() *tls.Config {
|
||||
// This does not affect cached certs. See HostPolicy field description for more details.
|
||||
//
|
||||
// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
|
||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
|
||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler
|
||||
// for http-01. (The tls-sni-* challenges have been deprecated by popular ACME providers
|
||||
// due to security issues in the ecosystem.)
|
||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if m.Prompt == nil {
|
||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
||||
@@ -277,10 +269,13 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Check whether this is a token cert requested for TLS-ALPN challenge.
|
||||
// Check whether this is a token cert requested for TLS-SNI or TLS-ALPN challenge.
|
||||
if wantsTokenCert(hello) {
|
||||
m.challengeMu.RLock()
|
||||
defer m.challengeMu.RUnlock()
|
||||
m.tokensMu.RLock()
|
||||
defer m.tokensMu.RUnlock()
|
||||
// It's ok to use the same token cert key for both tls-sni and tls-alpn
|
||||
// because there's always at most 1 token cert per on-going domain authorization.
|
||||
// See m.verify for details.
|
||||
if cert := m.certTokens[name]; cert != nil {
|
||||
return cert, nil
|
||||
}
|
||||
@@ -323,7 +318,8 @@ func wantsTokenCert(hello *tls.ClientHelloInfo) bool {
|
||||
if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// tls-sni-xx
|
||||
return strings.HasSuffix(hello.ServerName, ".acme.invalid")
|
||||
}
|
||||
|
||||
func supportsECDSA(hello *tls.ClientHelloInfo) bool {
|
||||
@@ -388,8 +384,8 @@ func supportsECDSA(hello *tls.ClientHelloInfo) bool {
|
||||
// If HTTPHandler is never called, the Manager will only use the "tls-alpn-01"
|
||||
// challenge for domain verification.
|
||||
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||
m.challengeMu.Lock()
|
||||
defer m.challengeMu.Unlock()
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
m.tryHTTP01 = true
|
||||
|
||||
if fallback == nil {
|
||||
@@ -463,7 +459,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 +585,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 +604,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()
|
||||
}
|
||||
|
||||
@@ -653,152 +648,120 @@ func (m *Manager) certState(ck certKey) (*certState, error) {
|
||||
// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
|
||||
// The key argument is the certificate private key.
|
||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) {
|
||||
csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dir, err := client.Discover(ctx)
|
||||
|
||||
if err := m.verify(ctx, client, ck.domain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dir.OrderURL == "" {
|
||||
return nil, nil, errPreRFC
|
||||
}
|
||||
|
||||
o, err := m.verifyRFC(ctx, client, ck.domain)
|
||||
der, _, err = client.CreateCert(ctx, csr, 0, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
|
||||
leaf, err = validCert(ck, der, key, m.now())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
leaf, err = validCert(ck, chain, key, m.now())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return chain, leaf, nil
|
||||
return der, leaf, 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) {
|
||||
// Try each supported challenge type starting with a new order each time.
|
||||
// The nextTyp index of the next challenge type to try is shared across
|
||||
// all order authorizations: if we've tried a challenge type once and it didn't work,
|
||||
// it will most likely not work on another order's authorization either.
|
||||
challengeTypes := m.supportedChallengeTypes()
|
||||
nextTyp := 0 // challengeTypes index
|
||||
AuthorizeOrderLoop:
|
||||
for {
|
||||
o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove all hanging authorizations to reduce rate limit quotas
|
||||
// after we're done.
|
||||
defer func(urls []string) {
|
||||
go m.deactivatePendingAuthz(urls)
|
||||
}(o.AuthzURLs)
|
||||
|
||||
// Check if there's actually anything we need to do.
|
||||
switch o.Status {
|
||||
case acme.StatusReady:
|
||||
// Already authorized.
|
||||
return o, nil
|
||||
case acme.StatusPending:
|
||||
// Continue normal Order-based flow.
|
||||
default:
|
||||
return nil, fmt.Errorf("acme/autocert: invalid new order status %q; order URL: %q", o.Status, o.URI)
|
||||
}
|
||||
|
||||
// Satisfy all pending authorizations.
|
||||
for _, zurl := range o.AuthzURLs {
|
||||
z, err := client.GetAuthorization(ctx, zurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if z.Status != acme.StatusPending {
|
||||
// We are interested only in pending authorizations.
|
||||
continue
|
||||
}
|
||||
// Pick the next preferred challenge.
|
||||
var chal *acme.Challenge
|
||||
for chal == nil && nextTyp < len(challengeTypes) {
|
||||
chal = pickChallenge(challengeTypes[nextTyp], z.Challenges)
|
||||
nextTyp++
|
||||
}
|
||||
if chal == nil {
|
||||
return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found", z.URI, domain)
|
||||
}
|
||||
// Respond to the challenge and wait for validation result.
|
||||
cleanup, err := m.fulfill(ctx, client, chal, domain)
|
||||
if err != nil {
|
||||
continue AuthorizeOrderLoop
|
||||
}
|
||||
defer cleanup()
|
||||
if _, err := client.Accept(ctx, chal); err != nil {
|
||||
continue AuthorizeOrderLoop
|
||||
}
|
||||
if _, err := client.WaitAuthorization(ctx, z.URI); err != nil {
|
||||
continue AuthorizeOrderLoop
|
||||
}
|
||||
}
|
||||
|
||||
// All authorizations are satisfied.
|
||||
// Wait for the CA to update the order status.
|
||||
o, err = client.WaitOrder(ctx, o.URI)
|
||||
if err != nil {
|
||||
continue AuthorizeOrderLoop
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
}
|
||||
|
||||
func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
|
||||
for _, c := range chal {
|
||||
if c.Type == typ {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) supportedChallengeTypes() []string {
|
||||
m.challengeMu.RLock()
|
||||
defer m.challengeMu.RUnlock()
|
||||
typ := []string{"tls-alpn-01"}
|
||||
if m.tryHTTP01 {
|
||||
typ = append(typ, "http-01")
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// deactivatePendingAuthz relinquishes all authorizations identified by the elements
|
||||
// of the provided uri slice which are in "pending" state.
|
||||
// revokePendingAuthz revokes all authorizations idenfied by the elements of uri slice.
|
||||
// It ignores revocation errors.
|
||||
//
|
||||
// deactivatePendingAuthz takes no context argument and instead runs with its own
|
||||
// "detached" context because deactivations are done in a goroutine separate from
|
||||
// that of the main issuance or renewal flow.
|
||||
func (m *Manager) deactivatePendingAuthz(uri []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
func (m *Manager) revokePendingAuthz(ctx context.Context, uri []string) {
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, u := range uri {
|
||||
z, err := client.GetAuthorization(ctx, u)
|
||||
if err == nil && z.Status == acme.StatusPending {
|
||||
client.RevokeAuthorization(ctx, u)
|
||||
client.RevokeAuthorization(ctx, u)
|
||||
}
|
||||
}
|
||||
|
||||
// verify runs the identifier (domain) authorization flow
|
||||
// using each applicable ACME challenge type.
|
||||
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
|
||||
// The list of challenge types we'll try to fulfill
|
||||
// in this specific order.
|
||||
challengeTypes := []string{"tls-alpn-01", "tls-sni-02", "tls-sni-01"}
|
||||
m.tokensMu.RLock()
|
||||
if m.tryHTTP01 {
|
||||
challengeTypes = append(challengeTypes, "http-01")
|
||||
}
|
||||
m.tokensMu.RUnlock()
|
||||
|
||||
// Keep track of pending authzs and revoke the ones that did not validate.
|
||||
pendingAuthzs := make(map[string]bool)
|
||||
defer func() {
|
||||
var uri []string
|
||||
for k, pending := range pendingAuthzs {
|
||||
if pending {
|
||||
uri = append(uri, k)
|
||||
}
|
||||
}
|
||||
if len(uri) > 0 {
|
||||
// Use "detached" background context.
|
||||
// The revocations need not happen in the current verification flow.
|
||||
go m.revokePendingAuthz(context.Background(), uri)
|
||||
}
|
||||
}()
|
||||
|
||||
// errs accumulates challenge failure errors, printed if all fail
|
||||
errs := make(map[*acme.Challenge]error)
|
||||
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
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
pendingAuthzs[authz.URI] = true
|
||||
|
||||
// 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
|
||||
}
|
||||
delete(pendingAuthzs, authz.URI)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +776,20 @@ func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.C
|
||||
}
|
||||
m.putCertToken(ctx, domain, &cert)
|
||||
return func() { go m.deleteCertToken(domain) }, nil
|
||||
case "tls-sni-01":
|
||||
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.putCertToken(ctx, name, &cert)
|
||||
return func() { go m.deleteCertToken(name) }, nil
|
||||
case "tls-sni-02":
|
||||
cert, name, err := client.TLSSNI02ChallengeCert(chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.putCertToken(ctx, name, &cert)
|
||||
return func() { go m.deleteCertToken(name) }, nil
|
||||
case "http-01":
|
||||
resp, err := client.HTTP01ChallengeResponse(chal.Token)
|
||||
if err != nil {
|
||||
@@ -825,11 +802,20 @@ func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.C
|
||||
return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
|
||||
}
|
||||
|
||||
func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
|
||||
for _, c := range chal {
|
||||
if c.Type == typ {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// putCertToken stores the token certificate with the specified name
|
||||
// in both m.certTokens map and m.Cache.
|
||||
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
|
||||
m.challengeMu.Lock()
|
||||
defer m.challengeMu.Unlock()
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
if m.certTokens == nil {
|
||||
m.certTokens = make(map[string]*tls.Certificate)
|
||||
}
|
||||
@@ -840,8 +826,8 @@ func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certi
|
||||
// deleteCertToken removes the token certificate with the specified name
|
||||
// from both m.certTokens map and m.Cache.
|
||||
func (m *Manager) deleteCertToken(name string) {
|
||||
m.challengeMu.Lock()
|
||||
defer m.challengeMu.Unlock()
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
delete(m.certTokens, name)
|
||||
if m.Cache != nil {
|
||||
ck := certKey{domain: name, isToken: true}
|
||||
@@ -852,8 +838,8 @@ func (m *Manager) deleteCertToken(name string) {
|
||||
// httpToken retrieves an existing http-01 token value from an in-memory map
|
||||
// or the optional cache.
|
||||
func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) {
|
||||
m.challengeMu.RLock()
|
||||
defer m.challengeMu.RUnlock()
|
||||
m.tokensMu.RLock()
|
||||
defer m.tokensMu.RUnlock()
|
||||
if v, ok := m.httpTokens[tokenPath]; ok {
|
||||
return v, nil
|
||||
}
|
||||
@@ -868,8 +854,8 @@ func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, erro
|
||||
//
|
||||
// It ignores any error returned from Cache.Put.
|
||||
func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
|
||||
m.challengeMu.Lock()
|
||||
defer m.challengeMu.Unlock()
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
if m.httpTokens == nil {
|
||||
m.httpTokens = make(map[string][]byte)
|
||||
}
|
||||
@@ -885,8 +871,8 @@ func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
|
||||
//
|
||||
// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
|
||||
func (m *Manager) deleteHTTPToken(tokenPath string) {
|
||||
m.challengeMu.Lock()
|
||||
defer m.challengeMu.Unlock()
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
delete(m.httpTokens, tokenPath)
|
||||
if m.Cache != nil {
|
||||
m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
|
||||
@@ -899,7 +885,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 +893,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 {
|
||||
@@ -985,7 +971,7 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
||||
|
||||
client := m.Client
|
||||
if client == nil {
|
||||
client = &acme.Client{DirectoryURL: DefaultACMEDirectory}
|
||||
client = &acme.Client{DirectoryURL: acme.LetsEncryptURL}
|
||||
}
|
||||
if client.Key == nil {
|
||||
var err error
|
||||
@@ -994,32 +980,20 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if client.UserAgent == "" {
|
||||
client.UserAgent = "autocert"
|
||||
}
|
||||
var contact []string
|
||||
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) {
|
||||
if ae, ok := err.(*acme.Error); err == nil || ok && ae.StatusCode == http.StatusConflict {
|
||||
// conflict indicates the key is already registered
|
||||
m.client = client
|
||||
err = nil
|
||||
}
|
||||
return m.client, err
|
||||
}
|
||||
|
||||
// isAccountAlreadyExist reports whether the err, as returned from acme.Client.Register,
|
||||
// indicates the account has already been registered.
|
||||
func isAccountAlreadyExist(err error) bool {
|
||||
if err == acme.ErrAccountAlreadyExists {
|
||||
return true
|
||||
}
|
||||
ae, ok := err.(*acme.Error)
|
||||
return ok && ae.StatusCode == http.StatusConflict
|
||||
}
|
||||
|
||||
func (m *Manager) hostPolicy() HostPolicy {
|
||||
if m.HostPolicy != nil {
|
||||
return m.HostPolicy
|
||||
@@ -1066,11 +1040,11 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// certRequest generates a CSR for the given common name.
|
||||
func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) {
|
||||
// certRequest generates a CSR for the given common name cn and optional SANs.
|
||||
func certRequest(key crypto.Signer, cn string, ext []pkix.Extension, san ...string) ([]byte, error) {
|
||||
req := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: name},
|
||||
DNSNames: []string{name},
|
||||
Subject: pkix.Name{CommonName: cn},
|
||||
DNSNames: san,
|
||||
ExtraExtensions: ext,
|
||||
}
|
||||
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
||||
@@ -1133,10 +1107,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 +1137,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
|
||||
|
||||
+628
-412
File diff suppressed because it is too large
Load Diff
+7
-12
@@ -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 {
|
||||
@@ -76,12 +77,11 @@ func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
if tmp, err = d.writeTempFile(name, data); err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmp)
|
||||
select {
|
||||
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 +95,7 @@ func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
|
||||
// Delete removes the specified file name.
|
||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||
name = filepath.Join(string(d), filepath.Clean("/"+name))
|
||||
name = filepath.Join(string(d), name)
|
||||
var (
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
@@ -116,17 +116,12 @@ 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) {
|
||||
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
||||
// TempFile uses 0600 permissions
|
||||
f, err := os.CreateTemp(string(d), prefix)
|
||||
f, err := ioutil.TempFile(string(d), prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
if reterr != nil {
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
}()
|
||||
if _, err := f.Write(b); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -47,15 +48,6 @@ func TestDirCache(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// test put deletes temp file
|
||||
tmp, err := filepath.Glob(name + "?*")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if tmp != nil {
|
||||
t.Errorf("temp file exists: %s", tmp)
|
||||
}
|
||||
|
||||
// test delete
|
||||
if err := cache.Delete(ctx, "dummy"); err != nil {
|
||||
t.Fatalf("delete: %v", err)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -8,101 +8,82 @@
|
||||
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"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"strconv"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
// 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
|
||||
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,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
IsCA: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
@@ -112,97 +93,28 @@ 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
|
||||
// Close shuts down the server and blocks until all outstanding
|
||||
// requests on this server have completed.
|
||||
func (ca *CAServer) Close() {
|
||||
ca.server.Close()
|
||||
}
|
||||
|
||||
// URL returns the server address, after Start has been called.
|
||||
func (ca *CAServer) URL() string {
|
||||
if ca.url == "" {
|
||||
panic("URL called before Start")
|
||||
}
|
||||
return ca.url
|
||||
}
|
||||
|
||||
// Roots returns a pool cointaining the CA root.
|
||||
func (ca *CAServer) Roots() *x509.CertPool {
|
||||
if ca.url == "" {
|
||||
panic("Roots called before Start")
|
||||
}
|
||||
return ca.roots
|
||||
}
|
||||
|
||||
// ExternalAccountRequired makes an EAB JWS required for account registration.
|
||||
func (ca *CAServer) ExternalAccountRequired() *CAServer {
|
||||
if ca.url != "" {
|
||||
panic("ExternalAccountRequired must be called before Start")
|
||||
}
|
||||
ca.eabRequired = true
|
||||
return ca
|
||||
}
|
||||
|
||||
// Start starts serving requests. The server address becomes available in the
|
||||
// URL field.
|
||||
func (ca *CAServer) Start() *CAServer {
|
||||
if ca.url == "" {
|
||||
ca.generateRoot()
|
||||
ca.server.Start()
|
||||
ca.t.Cleanup(ca.server.Close)
|
||||
ca.url = ca.server.URL
|
||||
}
|
||||
return ca
|
||||
}
|
||||
|
||||
func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
|
||||
return ca.server.URL + fmt.Sprintf(format, arg...)
|
||||
}
|
||||
|
||||
func (ca *CAServer) addr(domain string) (string, bool) {
|
||||
// Errors returns all client errors.
|
||||
func (ca *CAServer) Errors() []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
|
||||
}
|
||||
|
||||
func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
|
||||
s := fmt.Sprintf(format, a...)
|
||||
ca.t.Errorf(format, a...)
|
||||
http.Error(w, s, code)
|
||||
return ca.errors
|
||||
}
|
||||
|
||||
// Resolve adds a domain to address resolution for the ca to dial to
|
||||
@@ -213,33 +125,10 @@ 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"`
|
||||
NewReg string `json:"new-reg"`
|
||||
NewAuthz string `json:"new-authz"`
|
||||
NewCert string `json:"new-cert"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
@@ -252,174 +141,116 @@ type authorization struct {
|
||||
Status string `json:"status"`
|
||||
Challenges []challenge `json:"challenges"`
|
||||
|
||||
domain string
|
||||
id int
|
||||
}
|
||||
|
||||
type order struct {
|
||||
Status string `json:"status"`
|
||||
AuthzURLs []string `json:"authorizations"`
|
||||
FinalizeURL string `json:"finalize"` // CSR submit URL
|
||||
CertURL string `json:"certificate"` // already issued cert
|
||||
|
||||
leaf []byte // issued cert in DER format
|
||||
domain string
|
||||
}
|
||||
|
||||
func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
ca.t.Logf("%s %s", r.Method, r.URL)
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Verify nonce header for all POST requests.
|
||||
|
||||
switch {
|
||||
default:
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
err := fmt.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
// 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,
|
||||
},
|
||||
NewReg: ca.serverURL("/new-reg"),
|
||||
NewAuthz: ca.serverURL("/new-authz"),
|
||||
NewCert: ca.serverURL("/new-cert"),
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
panic(fmt.Sprintf("discovery response: %v", err))
|
||||
}
|
||||
|
||||
// Nonce requests.
|
||||
case r.URL.Path == "/new-nonce":
|
||||
// Nonce values are always set. Nothing else to do.
|
||||
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)
|
||||
w.Write([]byte("{}"))
|
||||
|
||||
// New order request.
|
||||
case r.URL.Path == "/new-order":
|
||||
// Domain authorization request.
|
||||
case r.URL.Path == "/new-authz":
|
||||
var req struct {
|
||||
Identifiers []struct{ Value string }
|
||||
Identifier struct{ Value string }
|
||||
}
|
||||
if err := decodePayload(&req, r.Body); err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
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))
|
||||
}
|
||||
orderID := len(ca.orders)
|
||||
ca.orders = append(ca.orders, o)
|
||||
w.Header().Set("Location", ca.serverURL("/orders/%d", orderID))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(o); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Existing order status requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/orders/"):
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/orders/"))
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(o); err != nil {
|
||||
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]
|
||||
ca.mu.Lock()
|
||||
supported := false
|
||||
for _, suppTyp := range ca.challengeTypes {
|
||||
if suppTyp == typ {
|
||||
supported = true
|
||||
authz, ok := ca.authorizations[req.Identifier.Value]
|
||||
if !ok {
|
||||
authz = &authorization{
|
||||
domain: req.Identifier.Value,
|
||||
Status: "pending",
|
||||
}
|
||||
for _, typ := range ca.challengeTypes {
|
||||
authz.Challenges = append(authz.Challenges, challenge{
|
||||
Type: typ,
|
||||
URI: ca.serverURL("/challenge/%s/%s", typ, authz.domain),
|
||||
Token: challengeToken(authz.domain, typ),
|
||||
})
|
||||
}
|
||||
ca.authorizations[authz.domain] = authz
|
||||
}
|
||||
a, err := ca.storedAuthz(id)
|
||||
ca.mu.Unlock()
|
||||
if !supported {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ)
|
||||
w.Header().Set("Location", ca.serverURL("/authz/%s", authz.domain))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(authz); err != nil {
|
||||
panic(fmt.Sprintf("new authz response: %v", err))
|
||||
}
|
||||
|
||||
// Accept tls-alpn-01 challenge type requests.
|
||||
// TODO: Add http-01 and dns-01 handlers.
|
||||
case strings.HasPrefix(r.URL.Path, "/challenge/tls-alpn-01/"):
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/challenge/tls-alpn-01/")
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
if _, ok := ca.authorizations[domain]; !ok {
|
||||
err := fmt.Errorf("challenge accept: no authz for %q", domain)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err)
|
||||
return
|
||||
}
|
||||
ca.validateChallenge(a, typ)
|
||||
go func(domain string) {
|
||||
err := ca.verifyALPNChallenge(domain)
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz := ca.authorizations[domain]
|
||||
if err != nil {
|
||||
authz.Status = "invalid"
|
||||
return
|
||||
}
|
||||
authz.Status = "valid"
|
||||
|
||||
}(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 {
|
||||
http.Error(w, fmt.Sprintf("no authz for %q", domain), http.StatusNotFound)
|
||||
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.
|
||||
case strings.HasPrefix(r.URL.Path, "/new-cert/"):
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
orderID := strings.TrimPrefix(r.URL.Path, "/new-cert/")
|
||||
o, err := ca.storedOrder(orderID)
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if o.Status != acme.StatusReady {
|
||||
ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
|
||||
return
|
||||
}
|
||||
// Validate CSR request.
|
||||
// Cert issuance request.
|
||||
case r.URL.Path == "/new-cert":
|
||||
var req struct {
|
||||
CSR string `json:"csr"`
|
||||
}
|
||||
@@ -427,101 +258,83 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
|
||||
csr, err := x509.ParseCertificateRequest(b)
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
names := unique(append(csr.DNSNames, csr.Subject.CommonName))
|
||||
if err := ca.matchWhitelist(names); err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if err := ca.authorized(names); err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Issue the certificate.
|
||||
der, err := ca.leafCert(csr)
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, "new-cert response: ca.leafCert: %v", err)
|
||||
return
|
||||
}
|
||||
o.leaf = der
|
||||
o.CertURL = ca.serverURL("/issued-cert/%s", orderID)
|
||||
o.Status = acme.StatusValid
|
||||
if err := json.NewEncoder(w).Encode(o); err != nil {
|
||||
panic(err)
|
||||
err = fmt.Errorf("new-cert response: ca.leafCert: %v", err)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=up", ca.serverURL("/ca-cert")))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(der)
|
||||
|
||||
// Already issued cert download requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/issued-cert/"):
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/issued-cert/"))
|
||||
if err != nil {
|
||||
ca.httpErrorf(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if o.Status != acme.StatusValid {
|
||||
ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/pem-certificate-chain")
|
||||
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: o.leaf})
|
||||
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: ca.rootCert})
|
||||
// CA chain cert request.
|
||||
case r.URL.Path == "/ca-cert":
|
||||
w.Write(ca.rootCert)
|
||||
}
|
||||
}
|
||||
|
||||
// storedOrder retrieves a previously created order at index i.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) storedOrder(i string) (*order, error) {
|
||||
idx, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storedOrder: %v", err)
|
||||
}
|
||||
if idx < 0 {
|
||||
return nil, fmt.Errorf("storedOrder: invalid order index %d", idx)
|
||||
}
|
||||
if idx > len(ca.orders)-1 {
|
||||
return nil, fmt.Errorf("storedOrder: no such order %d", idx)
|
||||
}
|
||||
|
||||
ca.updatePendingOrders()
|
||||
return ca.orders[idx], nil
|
||||
func (ca *CAServer) addError(err error) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.errors = append(ca.errors, err)
|
||||
}
|
||||
|
||||
// storedAuthz retrieves a previously created authz at index i.
|
||||
// It requires ca.mu to be locked.
|
||||
func (ca *CAServer) storedAuthz(i string) (*authorization, error) {
|
||||
idx, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storedAuthz: %v", err)
|
||||
}
|
||||
if idx < 0 {
|
||||
return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx)
|
||||
}
|
||||
if idx > len(ca.authorizations)-1 {
|
||||
return nil, fmt.Errorf("storedAuthz: no such authz %d", idx)
|
||||
}
|
||||
return ca.authorizations[idx], nil
|
||||
func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
|
||||
return ca.server.URL + fmt.Sprintf(format, arg...)
|
||||
}
|
||||
|
||||
// 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]
|
||||
if !ok {
|
||||
authzId := len(ca.authorizations)
|
||||
authz = &authorization{
|
||||
id: authzId,
|
||||
domain: identifier,
|
||||
Status: acme.StatusPending,
|
||||
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)
|
||||
}
|
||||
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),
|
||||
})
|
||||
}
|
||||
ca.authorizations = append(ca.authorizations, authz)
|
||||
}
|
||||
return authz
|
||||
if len(nomatch) > 0 {
|
||||
return fmt.Errorf("matchWhitelist: some domains don't match: %q", nomatch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ca *CAServer) authorized(dnsNames []string) error {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
var noauthz []string
|
||||
for _, name := range dnsNames {
|
||||
authz, ok := ca.authorizations[name]
|
||||
if !ok || authz.Status != "valid" {
|
||||
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) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.certCount++ // next leaf cert serial number
|
||||
leaf := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(ca.certCount)),
|
||||
@@ -539,231 +352,38 @@ 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")
|
||||
}
|
||||
|
||||
func (ca *CAServer) addr(domain string) (string, error) {
|
||||
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,
|
||||
addr, ok := ca.domainAddr[domain]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func (ca *CAServer) validateChallenge(authz *authorization, typ string) {
|
||||
var err error
|
||||
switch typ {
|
||||
case "tls-alpn-01":
|
||||
err = ca.verifyALPNChallenge(authz)
|
||||
case "http-01":
|
||||
err = ca.verifyHTTPChallenge(authz)
|
||||
default:
|
||||
panic(fmt.Sprintf("validation of %q is not implemented", typ))
|
||||
}
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
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() {
|
||||
// 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
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +399,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 {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
//
|
||||
// It enables one-line HTTPS servers:
|
||||
//
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
//
|
||||
// NewListener is a convenience function for a common configuration.
|
||||
// More complex or custom configurations can use the autocert.Manager
|
||||
@@ -72,6 +72,7 @@ func NewListener(domains ...string) net.Listener {
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: m.TLSConfig(),
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
@@ -79,6 +80,7 @@ func (m *Manager) Listener() net.Listener {
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
m *Manager
|
||||
conf *tls.Config
|
||||
|
||||
tcpListener net.Listener
|
||||
|
||||
+10
-25
@@ -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
|
||||
|
||||
+155
-95
@@ -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,98 @@ 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"}`))
|
||||
// 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 +150,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 +161,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)
|
||||
@@ -119,38 +173,59 @@ func TestRenewFromCache(t *testing.T) {
|
||||
}
|
||||
|
||||
// trigger renew
|
||||
hello := clientHelloInfo(exampleDomain, algECDSA)
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
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 +234,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 +261,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,26 +272,34 @@ 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
|
||||
hello := clientHelloInfo(exampleDomain, algECDSA)
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
tlscert, err := man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -239,31 +309,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, true)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-49
@@ -10,9 +10,8 @@ import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -156,16 +155,8 @@ 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
|
||||
// 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.
|
||||
func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||
return c.post(ctx, nil, url, noPayload, ok)
|
||||
}
|
||||
|
||||
// post issues a signed POST request in JWS format using the provided key
|
||||
// to the specified URL. If key is nil, c.Key is used instead.
|
||||
// to the specified URL.
|
||||
// It returns a non-error value only when ok reports true.
|
||||
//
|
||||
// post retries unsuccessful attempts according to c.RetryBackoff
|
||||
@@ -202,31 +193,14 @@ func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body i
|
||||
}
|
||||
|
||||
// postNoRetry signs the body with the given key and POSTs it to the provided url.
|
||||
// It is used by c.post to retry unsuccessful attempts.
|
||||
// The body argument must be JSON-serializable.
|
||||
//
|
||||
// If key argument is nil, c.Key is used to sign the request.
|
||||
// If key argument is nil and c.accountKID returns a non-zero keyID,
|
||||
// the request is sent in KID form. Otherwise, JWK form is used.
|
||||
//
|
||||
// In practice, when interfacing with RFC-compliant CAs most requests are sent in KID form
|
||||
// and JWK is used only when KID is unavailable: new account endpoint and certificate
|
||||
// revocation requests authenticated by a cert key.
|
||||
// See jwsEncodeJSON for other details.
|
||||
// It is used by c.post to retry unsuccessful attempts.
|
||||
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)
|
||||
}
|
||||
nonce, err := c.popNonce(ctx, url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
b, err := jwsEncodeJSON(body, key, kid, nonce, url)
|
||||
b, err := jwsEncodeJSON(body, key, nonce)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -245,7 +219,6 @@ func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string,
|
||||
|
||||
// doNoRetry issues a request req, replacing its context (if any) with ctx.
|
||||
func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", c.userAgent())
|
||||
res, err := c.httpClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -270,23 +243,6 @@ func (c *Client) httpClient() *http.Client {
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
// packageVersion is the version of the module that contains this package, for
|
||||
// sending as part of the User-Agent header. It's set in version_go112.go.
|
||||
var packageVersion string
|
||||
|
||||
// userAgent returns the User-Agent header value. It includes the package name,
|
||||
// the module version (if available), and the c.UserAgent value (if set).
|
||||
func (c *Client) userAgent() string {
|
||||
ua := "golang.org/x/crypto/acme"
|
||||
if packageVersion != "" {
|
||||
ua += "@" + packageVersion
|
||||
}
|
||||
if c.UserAgent != "" {
|
||||
ua = c.UserAgent + " " + ua
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
// isBadNonce reports whether err is an ACME "badnonce" error.
|
||||
func isBadNonce(err error) bool {
|
||||
// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
|
||||
@@ -310,7 +266,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
-47
@@ -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)
|
||||
@@ -87,7 +87,7 @@ func TestPostWithRetries(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
head, err := decodeJWSHead(r.Body)
|
||||
head, err := decodeJWSHead(r)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,45 +211,3 @@ func TestRetryBackoffArgs(t *testing.T) {
|
||||
t.Errorf("nretry = %d; want 3", nretry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
for _, custom := range []string{"", "CUSTOM_UA"} {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Log(r.UserAgent())
|
||||
if s := "golang.org/x/crypto/acme"; !strings.Contains(r.UserAgent(), s) {
|
||||
t.Errorf("expected User-Agent to contain %q, got %q", s, r.UserAgent())
|
||||
}
|
||||
if !strings.Contains(r.UserAgent(), custom) {
|
||||
t.Errorf("expected User-Agent to contain %q, got %q", custom, r.UserAgent())
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"newOrder": "sure"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := &Client{
|
||||
Key: testKey,
|
||||
DirectoryURL: ts.URL,
|
||||
UserAgent: custom,
|
||||
}
|
||||
if _, err := client.Discover(context.Background()); err != nil {
|
||||
t.Errorf("client.Discover: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,433 +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.
|
||||
|
||||
// The acmeprober program runs against an actual ACME CA implementation.
|
||||
// It spins up an HTTP server to fulfill authorization challenges
|
||||
// or execute a DNS script to provision a response to dns-01 challenge.
|
||||
//
|
||||
// For http-01 and tls-alpn-01 challenge types this requires the ACME CA
|
||||
// to be able to reach the HTTP server.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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.
|
||||
// To test tls-alpn-01 challenge, 443 port would need to be tunneled
|
||||
// to 0.0.0.0:8080.
|
||||
// When running with dns-01 challenge type, use -s argument instead of -a.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
var (
|
||||
// ACME CA directory URL.
|
||||
// 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).`)
|
||||
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.")
|
||||
domain = flag.String("domain", "", "Space separate domain identifiers.")
|
||||
ipaddr = flag.String("ip", "", "Space separate IP address identifiers.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintln(flag.CommandLine.Output(), `
|
||||
The prober program runs against an actual ACME CA implementation.
|
||||
It spins up an HTTP server to fulfill authorization challenges
|
||||
or execute a DNS script to provision a response to dns-01 challenge.
|
||||
|
||||
For http-01 and tls-alpn-01 challenge types this requires the ACME CA
|
||||
to be able to reach the HTTP server.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
To test tls-alpn-01 challenge, 443 port would need to be tunneled
|
||||
to 0.0.0.0:8080.
|
||||
When running with dns-01 challenge type, use -s argument instead of -a.
|
||||
`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
identifiers := acme.DomainIDs(strings.Fields(*domain)...)
|
||||
identifiers = append(identifiers, acme.IPIDs(strings.Fields(*ipaddr)...)...)
|
||||
if len(identifiers) == 0 {
|
||||
log.Fatal("at least one domain or IP addr identifier is required")
|
||||
}
|
||||
|
||||
// Duration of the whole run.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Create and register a new account.
|
||||
akey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cl := &acme.Client{Key: akey, DirectoryURL: *directory}
|
||||
a := &acme.Account{Contact: strings.Fields(*reginfo)}
|
||||
if _, err := cl.Register(ctx, a, acme.AcceptTOS); err != nil {
|
||||
log.Fatalf("Register: %v", err)
|
||||
}
|
||||
|
||||
// Run the desired flow test.
|
||||
p := &prober{
|
||||
client: cl,
|
||||
chalType: *chaltyp,
|
||||
localAddr: *addr,
|
||||
dnsScript: *dnsscript,
|
||||
}
|
||||
switch *flow {
|
||||
case "order":
|
||||
p.runOrder(ctx, identifiers)
|
||||
case "preauthz":
|
||||
p.runPreauthz(ctx, identifiers)
|
||||
default:
|
||||
log.Fatalf("unknown flow: %q", *flow)
|
||||
}
|
||||
if len(p.errors) > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type prober struct {
|
||||
client *acme.Client
|
||||
chalType string
|
||||
localAddr string
|
||||
dnsScript string
|
||||
|
||||
errors []error
|
||||
}
|
||||
|
||||
func (p *prober) errorf(format string, a ...interface{}) {
|
||||
err := fmt.Errorf(format, a...)
|
||||
log.Print(err)
|
||||
p.errors = append(p.errors, err)
|
||||
}
|
||||
|
||||
func (p *prober) runOrder(ctx context.Context, identifiers []acme.AuthzID) {
|
||||
// Create a new order and pick a challenge.
|
||||
// Note that Let's Encrypt will reply with 400 error:malformed
|
||||
// "NotBefore and NotAfter are not supported" when providing a NotAfter
|
||||
// value like WithOrderNotAfter(time.Now().Add(24 * time.Hour)).
|
||||
o, err := p.client.AuthorizeOrder(ctx, identifiers)
|
||||
if err != nil {
|
||||
log.Fatalf("AuthorizeOrder: %v", err)
|
||||
}
|
||||
|
||||
var zurls []string
|
||||
for _, u := range o.AuthzURLs {
|
||||
z, err := p.client.GetAuthorization(ctx, u)
|
||||
if err != nil {
|
||||
log.Fatalf("GetAuthorization(%q): %v", u, err)
|
||||
}
|
||||
log.Printf("%+v", z)
|
||||
if z.Status != acme.StatusPending {
|
||||
log.Printf("authz status is %q; skipping", z.Status)
|
||||
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", z.Identifier)
|
||||
}
|
||||
|
||||
log.Print("all challenges are done")
|
||||
if _, err := p.client.WaitOrder(ctx, o.URI); err != nil {
|
||||
log.Fatalf("WaitOrder(%q): %v", o.URI, err)
|
||||
}
|
||||
csr, certkey := newCSR(identifiers)
|
||||
der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
|
||||
if err != nil {
|
||||
log.Fatalf("CreateOrderCert: %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
|
||||
}
|
||||
}
|
||||
// Deactivate the account. We don't need it for any further calls.
|
||||
if err := p.client.DeactivateReg(ctx); err != nil {
|
||||
p.errorf("DeactivateReg: %v", err)
|
||||
}
|
||||
// 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) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) {
|
||||
dir, err := p.client.Discover(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Discover: %v", err)
|
||||
}
|
||||
if dir.AuthzURL == "" {
|
||||
log.Fatal("CA does not support pre-authorization")
|
||||
}
|
||||
|
||||
var zurls []string
|
||||
for _, id := range identifiers {
|
||||
z, err := authorize(ctx, p.client, id)
|
||||
if err != nil {
|
||||
log.Fatalf("AuthorizeID(%+v): %v", z, 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.
|
||||
// Expect all authorizations to be satisfied.
|
||||
log.Print("all challenges are done")
|
||||
o, err := p.client.AuthorizeOrder(ctx, identifiers)
|
||||
if err != nil {
|
||||
log.Fatalf("AuthorizeOrder: %v", err)
|
||||
}
|
||||
waitCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
if _, err := p.client.WaitOrder(waitCtx, o.URI); err != nil {
|
||||
log.Fatalf("WaitOrder(%q): %v", o.URI, err)
|
||||
}
|
||||
csr, certkey := newCSR(identifiers)
|
||||
der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
|
||||
if err != nil {
|
||||
log.Fatalf("CreateOrderCert: %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("RevokeAuthorization(%q): %v", v, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Deactivate the account. We don't need it for any further calls.
|
||||
if err := p.client.DeactivateReg(ctx); err != nil {
|
||||
p.errorf("DeactivateReg: %v", err)
|
||||
}
|
||||
// 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 {
|
||||
log.Printf("challenge %d: %+v", i, c)
|
||||
if c.Type == p.chalType {
|
||||
log.Printf("picked %s for authz %s", c.URI, z.URI)
|
||||
chal = c
|
||||
}
|
||||
}
|
||||
if chal == nil {
|
||||
return fmt.Errorf("challenge type %q wasn't offered for authz %s", p.chalType, z.URI)
|
||||
}
|
||||
|
||||
switch chal.Type {
|
||||
case "tls-alpn-01":
|
||||
return p.runTLSALPN01(ctx, z, chal)
|
||||
case "http-01":
|
||||
return p.runHTTP01(ctx, z, chal)
|
||||
case "dns-01":
|
||||
return p.runDNS01(ctx, z, chal)
|
||||
default:
|
||||
return fmt.Errorf("unknown challenge type %q", chal.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prober) runTLSALPN01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
|
||||
tokenCert, err := p.client.TLSALPN01ChallengeCert(chal.Token, z.Identifier.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TLSALPN01ChallengeCert: %v", err)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: p.localAddr,
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: []string{acme.ALPNProto},
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
log.Printf("hello: %+v", hello)
|
||||
return &tokenCert, nil
|
||||
},
|
||||
},
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}),
|
||||
}
|
||||
go s.ListenAndServeTLS("", "")
|
||||
defer s.Close()
|
||||
|
||||
if _, err := p.client.Accept(ctx, chal); err != nil {
|
||||
return fmt.Errorf("Accept(%q): %v", chal.URI, err)
|
||||
}
|
||||
_, zerr := p.client.WaitAuthorization(ctx, z.URI)
|
||||
return zerr
|
||||
}
|
||||
|
||||
func (p *prober) runHTTP01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
|
||||
body, err := p.client.HTTP01ChallengeResponse(chal.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP01ChallengeResponse: %v", err)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: p.localAddr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL)
|
||||
if r.URL.Path != p.client.HTTP01ChallengePath(chal.Token) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Write([]byte(body))
|
||||
}),
|
||||
}
|
||||
go s.ListenAndServe()
|
||||
defer s.Close()
|
||||
|
||||
if _, err := p.client.Accept(ctx, chal); err != nil {
|
||||
return fmt.Errorf("Accept(%q): %v", chal.URI, err)
|
||||
}
|
||||
_, zerr := p.client.WaitAuthorization(ctx, z.URI)
|
||||
return zerr
|
||||
}
|
||||
|
||||
func (p *prober) runDNS01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
|
||||
token, err := p.client.DNS01ChallengeRecord(chal.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS01ChallengeRecord: %v", err)
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("_acme-challenge.%s", z.Identifier.Value)
|
||||
cmd := exec.CommandContext(ctx, p.dnsScript, name, token)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("%s: %v", p.dnsScript, err)
|
||||
}
|
||||
|
||||
if _, err := p.client.Accept(ctx, chal); err != nil {
|
||||
return fmt.Errorf("Accept(%q): %v", chal.URI, err)
|
||||
}
|
||||
_, zerr := p.client.WaitAuthorization(ctx, z.URI)
|
||||
return zerr
|
||||
}
|
||||
|
||||
func authorize(ctx context.Context, client *acme.Client, id acme.AuthzID) (*acme.Authorization, error) {
|
||||
if id.Type == "ip" {
|
||||
return client.AuthorizeIP(ctx, id.Value)
|
||||
}
|
||||
return client.Authorize(ctx, id.Value)
|
||||
}
|
||||
|
||||
func checkCert(derChain [][]byte, id []acme.AuthzID) error {
|
||||
if len(derChain) == 0 {
|
||||
return errors.New("cert chain is zero bytes")
|
||||
}
|
||||
for i, b := range derChain {
|
||||
crt, err := x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d: ParseCertificate: %v", i, err)
|
||||
}
|
||||
log.Printf("%d: serial: 0x%s", i, crt.SerialNumber)
|
||||
log.Printf("%d: subject: %s", i, crt.Subject)
|
||||
log.Printf("%d: issuer: %s", i, crt.Issuer)
|
||||
log.Printf("%d: expires in %.1f day(s)", i, time.Until(crt.NotAfter).Hours()/24)
|
||||
if i > 0 { // not a leaf cert
|
||||
continue
|
||||
}
|
||||
p := &pem.Block{Type: "CERTIFICATE", Bytes: b}
|
||||
log.Printf("%d: leaf:\n%s", i, pem.EncodeToMemory(p))
|
||||
for _, v := range id {
|
||||
if err := crt.VerifyHostname(v.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCSR(identifiers []acme.AuthzID) ([]byte, crypto.Signer) {
|
||||
var csr x509.CertificateRequest
|
||||
for _, id := range identifiers {
|
||||
switch id.Type {
|
||||
case "dns":
|
||||
csr.DNSNames = append(csr.DNSNames, id.Value)
|
||||
case "ip":
|
||||
csr.IPAddresses = append(csr.IPAddresses, net.ParseIP(id.Value))
|
||||
default:
|
||||
panic(fmt.Sprintf("newCSR: unknown identifier type %q", id.Type))
|
||||
}
|
||||
}
|
||||
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("newCSR: ecdsa.GenerateKey for a cert: %v", err))
|
||||
}
|
||||
b, err := x509.CreateCertificateRequest(rand.Reader, &csr, k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("newCSR: x509.CreateCertificateRequest: %v", err))
|
||||
}
|
||||
return b, k
|
||||
}
|
||||
+27
-128
@@ -7,109 +7,47 @@ package acme
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512" // need for EC keys
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// KeyID is the account key identity provided by a CA during registration.
|
||||
type KeyID string
|
||||
|
||||
// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noKeyID = KeyID("")
|
||||
|
||||
// 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
|
||||
// authenticated GET requests via POSTing with an empty payload.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
|
||||
const noPayload = ""
|
||||
|
||||
// noNonce indicates that the nonce should be omitted from the protected header.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noNonce = ""
|
||||
|
||||
// jsonWebSignature can be easily serialized into a JWS following
|
||||
// https://tools.ietf.org/html/rfc7515#section-3.2.
|
||||
type jsonWebSignature struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format containing either kid or jwk
|
||||
// fields based on the provided KeyID value.
|
||||
//
|
||||
// The claimset is marshalled using json.Marshal unless it is a string.
|
||||
// In which case it is inserted directly into the message.
|
||||
//
|
||||
// If kid is non-empty, its quoted value is inserted in the protected header
|
||||
// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
|
||||
// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
|
||||
//
|
||||
// If nonce is non-empty, its quoted value is inserted in the protected header.
|
||||
//
|
||||
// The result is serialized in JSON format.
|
||||
// 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, nonce string) ([]byte, error) {
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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,
|
||||
}
|
||||
switch kid {
|
||||
case noKeyID:
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers.JWK = json.RawMessage(jwk)
|
||||
default:
|
||||
headers.KID = string(kid)
|
||||
}
|
||||
phJSON, err := json.Marshal(headers)
|
||||
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
|
||||
var payload string
|
||||
if val, ok := claimset.(string); ok {
|
||||
payload = val
|
||||
} else {
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = base64.RawURLEncoding.EncodeToString(cs)
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
||||
hash := sha.New()
|
||||
hash.Write([]byte(phead + "." + payload))
|
||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc := jsonWebSignature{
|
||||
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
@@ -117,43 +55,6 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, ur
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwsWithMAC creates and signs a JWS using the given key and the HS256
|
||||
// algorithm. kid and url are included in the protected header. rawPayload
|
||||
// should not be base64-URL-encoded.
|
||||
func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
|
||||
if len(key) == 0 {
|
||||
return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
|
||||
}
|
||||
header := struct {
|
||||
Algorithm string `json:"alg"`
|
||||
KID string `json:"kid"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}{
|
||||
// Only HMAC-SHA256 is supported.
|
||||
Algorithm: "HS256",
|
||||
KID: kid,
|
||||
URL: url,
|
||||
}
|
||||
rawProtected, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protected := base64.RawURLEncoding.EncodeToString(rawProtected)
|
||||
payload := base64.RawURLEncoding.EncodeToString(rawPayload)
|
||||
|
||||
h := hmac.New(sha256.New, key)
|
||||
if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := h.Sum(nil)
|
||||
|
||||
return &jsonWebSignature{
|
||||
Protected: protected,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(mac),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
@@ -197,23 +98,21 @@ func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||
|
||||
// jwsSign signs the digest using the given key.
|
||||
// The hash is unused for ECDSA keys.
|
||||
//
|
||||
// Note: non-stdlib crypto.Signer implementations are expected to return
|
||||
// the signature in the format as specified in RFC7518.
|
||||
// See https://tools.ietf.org/html/rfc7518 for more details.
|
||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||
switch pub := key.Public().(type) {
|
||||
case *rsa.PublicKey:
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
case *ecdsa.PublicKey:
|
||||
sigASN1, err := key.Sign(rand.Reader, digest, hash)
|
||||
if key, ok := key.(*ecdsa.PrivateKey); ok {
|
||||
// The key.Sign method of ecdsa returns ASN1-encoded signature.
|
||||
// So, we use the package Sign function instead
|
||||
// to get R and S values directly and format the result accordingly.
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rs struct{ R, S *big.Int }
|
||||
if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rb, sb := rs.R.Bytes(), rs.S.Bytes()
|
||||
size := pub.Params().BitSize / 8
|
||||
rb, sb := r.Bytes(), s.Bytes()
|
||||
size := key.Params().BitSize / 8
|
||||
if size%8 > 0 {
|
||||
size++
|
||||
}
|
||||
@@ -222,7 +121,7 @@ func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error)
|
||||
copy(sig[size*2-len(sb):], sb)
|
||||
return sig, nil
|
||||
}
|
||||
return nil, ErrUnsupportedKey
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
}
|
||||
|
||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||
|
||||
+36
-192
@@ -9,7 +9,6 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -20,18 +19,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The following shell command alias is used in the comments
|
||||
// throughout this file:
|
||||
// alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'"
|
||||
|
||||
const (
|
||||
// Modulus in raw base64:
|
||||
// 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34
|
||||
// g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON
|
||||
// a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh
|
||||
// Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg
|
||||
// JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n
|
||||
// NqVhoLeIvXC4nnWdJMZ6rogxyQQ
|
||||
testKeyPEM = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
|
||||
@@ -94,7 +82,7 @@ GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
|
||||
`
|
||||
// 1. openssl ec -in key.pem -noout -text
|
||||
// 2. remove first byte, 04 (the header); the rest is X and Y
|
||||
// 3. convert each with: echo <val> | xxd -r -p | b64raw
|
||||
// 3. convert each with: echo <val> | xxd -r -p | base64 -w 100 | tr -d '=' | tr '/+' '_-'
|
||||
testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
|
||||
testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
|
||||
testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
|
||||
@@ -103,7 +91,7 @@ GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
|
||||
testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
|
||||
|
||||
// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
|
||||
// openssl dgst -binary -sha256 | b64raw
|
||||
// openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-'
|
||||
testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
|
||||
)
|
||||
|
||||
@@ -152,7 +140,7 @@ func TestJWSEncodeJSON(t *testing.T) {
|
||||
// JWS signed with testKey and "nonce" as the nonce value
|
||||
// JSON-serialized JWS fields are split for easier testing
|
||||
const (
|
||||
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
|
||||
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
|
||||
protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
|
||||
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
|
||||
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
|
||||
@@ -163,20 +151,19 @@ func TestJWSEncodeJSON(t *testing.T) {
|
||||
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
|
||||
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
|
||||
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
|
||||
"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
||||
"UVEifSwibm9uY2UiOiJub25jZSJ9"
|
||||
// {"Msg":"Hello JWS"}
|
||||
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
||||
// printf '<protected>.<payload>' | openssl dgst -binary -sha256 -sign testKey | b64raw
|
||||
signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" +
|
||||
"Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" +
|
||||
"uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" +
|
||||
"nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" +
|
||||
"DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" +
|
||||
"bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" +
|
||||
"50rFt_9qOfJ4sfbLtG1Wwae57BQx1g"
|
||||
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
||||
signature = "eAGUikStX_UxyiFhxSLMyuyBcIB80GeBkFROCpap2sW3EmkU_ggF" +
|
||||
"knaQzxrTfItICSAXsCLIquZ5BbrSWA_4vdEYrwWtdUj7NqFKjHRa" +
|
||||
"zpLHcoR7r1rEHvkoP1xj49lS5fc3Wjjq8JUhffkhGbWZ8ZVkgPdC" +
|
||||
"4tMBWiQDoth-x8jELP_3LYOB_ScUXi2mETBawLgOT2K8rA0Vbbmx" +
|
||||
"hWNlOWuUf-8hL5YX4IOEwsS8JK_TrTq5Zc9My0zHJmaieqDV0UlP" +
|
||||
"k0onFjPFkGm7MrPSgd0MqRG-4vSAg2O4hDo7rKv4n8POjjXlNQvM" +
|
||||
"9IPLr8qZ7usYBKhEGwX3yq_eicAwBw"
|
||||
)
|
||||
|
||||
b, err := jwsEncodeJSON(claims, testKey, noKeyID, "nonce", "url")
|
||||
b, err := jwsEncodeJSON(claims, testKey, "nonce")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -195,84 +182,6 @@ 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")
|
||||
claims := struct{ Msg string }{"Hello JWS"}
|
||||
// JWS signed with testKeyEC
|
||||
const (
|
||||
// {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
|
||||
protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" +
|
||||
"vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
||||
// {"Msg":"Hello JWS"}
|
||||
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
||||
)
|
||||
|
||||
b, err := jwsEncodeJSON(claims, testKeyEC, kid, "nonce", "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 TestJWSEncodeJSONEC(t *testing.T) {
|
||||
tt := []struct {
|
||||
key *ecdsa.PrivateKey
|
||||
@@ -285,7 +194,7 @@ func TestJWSEncodeJSONEC(t *testing.T) {
|
||||
}
|
||||
for i, test := range tt {
|
||||
claims := struct{ Msg string }{"Hello JWS"}
|
||||
b, err := jwsEncodeJSON(claims, test.key, noKeyID, "nonce", "url")
|
||||
b, err := jwsEncodeJSON(claims, test.key, "nonce")
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
@@ -303,8 +212,6 @@ func TestJWSEncodeJSONEC(t *testing.T) {
|
||||
var head struct {
|
||||
Alg string
|
||||
Nonce string
|
||||
URL string `json:"url"`
|
||||
KID string `json:"kid"`
|
||||
JWK struct {
|
||||
Crv string
|
||||
Kty string
|
||||
@@ -321,13 +228,6 @@ func TestJWSEncodeJSONEC(t *testing.T) {
|
||||
if head.Nonce != "nonce" {
|
||||
t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
|
||||
}
|
||||
if head.URL != "url" {
|
||||
t.Errorf("%d: head.URL = %q; want 'url'", i, head.URL)
|
||||
}
|
||||
if head.KID != "" {
|
||||
// We used noKeyID in jwsEncodeJSON: expect no kid value.
|
||||
t.Errorf("%d: head.KID = %q; want empty", i, head.KID)
|
||||
}
|
||||
if head.JWK.Crv != test.crv {
|
||||
t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
|
||||
}
|
||||
@@ -356,27 +256,18 @@ func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, e
|
||||
func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||
claims := struct{ Msg string }{"hello"}
|
||||
const (
|
||||
// printf '{"Msg":"hello"}' | b64raw
|
||||
// printf '{"Msg":"hello"}' | base64 | tr -d '=' | tr '/+' '_-'
|
||||
payload = "eyJNc2ciOiJoZWxsbyJ9"
|
||||
// printf 'testsig' | b64raw
|
||||
// printf 'testsig' | base64 | tr -d '='
|
||||
testsig = "dGVzdHNpZw"
|
||||
|
||||
// the example P256 curve point from https://tools.ietf.org/html/rfc7515#appendix-A.3.1
|
||||
// encoded as ASN.1…
|
||||
es256stdsig = "MEUCIA7RIVN5Y2xIPC9/FVgH1AKjsigDOvl8fheBmsMWnqZlAiEA" +
|
||||
"xQoH04w8cOXY8S2vCEpUgKZlkMXyk1Cajz9/ioOjVNU"
|
||||
// …and RFC7518 (https://tools.ietf.org/html/rfc7518#section-3.4)
|
||||
es256jwsig = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw" +
|
||||
"5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
|
||||
// printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>,"nonce":"nonce"}' | \
|
||||
// base64 | tr -d '=' | tr '/+' '_-'
|
||||
es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjVsaEV1" +
|
||||
"ZzV4SzR4QkRaMm5BYmF4THRhTGl2ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFH" +
|
||||
"a3YwVGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6Im5vbmNlIn0"
|
||||
|
||||
// printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>},"nonce":"nonce","url":"url"}' | b64raw
|
||||
es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" +
|
||||
"eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" +
|
||||
"ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" +
|
||||
"VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" +
|
||||
"Im5vbmNlIiwidXJsIjoidXJsIn0"
|
||||
|
||||
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
|
||||
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
|
||||
rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
|
||||
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
|
||||
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
|
||||
@@ -387,34 +278,28 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
|
||||
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
|
||||
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
|
||||
"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
||||
"UVEifSwibm9uY2UiOiJub25jZSJ9"
|
||||
)
|
||||
|
||||
tt := []struct {
|
||||
alg, phead string
|
||||
pub crypto.PublicKey
|
||||
stdsig, jwsig string
|
||||
alg, phead string
|
||||
pub crypto.PublicKey
|
||||
}{
|
||||
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
|
||||
{"RS256", rs256phead, testKey.Public(), testsig, testsig},
|
||||
{"RS256", rs256phead, testKey.Public()},
|
||||
{"ES256", es256phead, testKeyEC.Public()},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.alg, func(t *testing.T) {
|
||||
stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't decode test vector: %v", err)
|
||||
}
|
||||
signer := &customTestSigner{
|
||||
sig: stdsig,
|
||||
sig: []byte("testsig"),
|
||||
pub: tc.pub,
|
||||
}
|
||||
|
||||
b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
|
||||
b, err := jwsEncodeJSON(claims, signer, "nonce")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var j jsonWebSignature
|
||||
var j struct{ Protected, Payload, Signature string }
|
||||
if err := json.Unmarshal(b, &j); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -424,56 +309,13 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
|
||||
if j.Payload != payload {
|
||||
t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
|
||||
}
|
||||
if j.Sig != tc.jwsig {
|
||||
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
|
||||
if j.Signature != testsig {
|
||||
t.Errorf("j.Signature = %q\nwant %q", j.Signature, testsig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSWithMAC(t *testing.T) {
|
||||
// Example from RFC 7520 Section 4.4.3.
|
||||
// https://tools.ietf.org/html/rfc7520#section-4.4.3
|
||||
b64Key := "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
|
||||
rawPayload := []byte("It\xe2\x80\x99s a dangerous business, Frodo, going out your " +
|
||||
"door. You step onto the road, and if you don't keep your feet, " +
|
||||
"there\xe2\x80\x99s no knowing where you might be swept off " +
|
||||
"to.")
|
||||
protected := "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" +
|
||||
"VlZjMxNGJjNzAzNyJ9"
|
||||
payload := "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg" +
|
||||
"Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h" +
|
||||
"ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi" +
|
||||
"gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m" +
|
||||
"ZiB0by4"
|
||||
sig := "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0"
|
||||
|
||||
key, err := base64.RawURLEncoding.DecodeString(b64Key)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode key: %q", b64Key)
|
||||
}
|
||||
got, err := jwsWithMAC(key, "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "", rawPayload)
|
||||
if err != nil {
|
||||
t.Fatalf("jwsWithMAC() = %q", err)
|
||||
}
|
||||
if got.Protected != protected {
|
||||
t.Errorf("got.Protected = %q\nwant %q", got.Protected, protected)
|
||||
}
|
||||
if got.Payload != payload {
|
||||
t.Errorf("got.Payload = %q\nwant %q", got.Payload, payload)
|
||||
}
|
||||
if got.Sig != sig {
|
||||
t.Errorf("got.Signature = %q\nwant %q", got.Sig, sig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSWithMACError(t *testing.T) {
|
||||
p := "{}"
|
||||
if _, err := jwsWithMAC(nil, "", "", []byte(p)); err == nil {
|
||||
t.Errorf("jwsWithMAC(nil, ...) = success; want err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWKThumbprintRSA(t *testing.T) {
|
||||
// Key example from RFC 7638
|
||||
const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
|
||||
@@ -510,8 +352,10 @@ func TestJWKThumbprintRSA(t *testing.T) {
|
||||
func TestJWKThumbprintEC(t *testing.T) {
|
||||
// Key example from RFC 7520
|
||||
// expected was computed with
|
||||
// printf '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
|
||||
// openssl dgst -binary -sha256 | b64raw
|
||||
// echo -n '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
|
||||
// openssl dgst -binary -sha256 | \
|
||||
// base64 | \
|
||||
// tr -d '=' | tr '/+' '_-'
|
||||
const (
|
||||
base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
|
||||
"KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
|
||||
|
||||
-476
@@ -1,476 +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.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DeactivateReg permanently disables an existing account associated with c.Key.
|
||||
// A deactivated account can no longer request certificate issuance or access
|
||||
// resources related to the account, such as orders or authorizations.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
req := json.RawMessage(`{"status": "deactivated"}`)
|
||||
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerRFC is equivalent to c.Register but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
c.cacheMu.Lock() // guard c.kid access
|
||||
defer c.cacheMu.Unlock()
|
||||
|
||||
req := struct {
|
||||
TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
ExternalAccountBinding *jsonWebSignature `json:"externalAccountBinding,omitempty"`
|
||||
}{
|
||||
Contact: acct.Contact,
|
||||
}
|
||||
if c.dir.Terms != "" {
|
||||
req.TermsAgreed = prompt(c.dir.Terms)
|
||||
}
|
||||
|
||||
// set 'externalAccountBinding' field if requested
|
||||
if acct.ExternalAccountBinding != nil {
|
||||
eabJWS, err := c.encodeExternalAccountBinding(acct.ExternalAccountBinding)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: failed to encode external account binding: %v", err)
|
||||
}
|
||||
req.ExternalAccountBinding = eabJWS
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(
|
||||
http.StatusOK, // account with this key already registered
|
||||
http.StatusCreated, // new account created
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
a, err := responseAccount(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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)
|
||||
if res.StatusCode == http.StatusOK {
|
||||
return nil, ErrAccountAlreadyExists
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// encodeExternalAccountBinding will encode an external account binding stanza
|
||||
// as described in https://tools.ietf.org/html/rfc8555#section-7.3.4.
|
||||
func (c *Client) encodeExternalAccountBinding(eab *ExternalAccountBinding) (*jsonWebSignature, error) {
|
||||
jwk, err := jwkEncode(c.Key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jwsWithMAC(eab.Key, eab.KID, c.dir.RegURL, []byte(jwk))
|
||||
}
|
||||
|
||||
// updateRegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
|
||||
url := string(c.accountKID(ctx))
|
||||
if url == "" {
|
||||
return nil, ErrNoAccount
|
||||
}
|
||||
req := struct {
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
}{
|
||||
Contact: a.Contact,
|
||||
}
|
||||
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return responseAccount(res)
|
||||
}
|
||||
|
||||
// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
|
||||
// It expects c.Discover to have already been called.
|
||||
func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
|
||||
req := json.RawMessage(`{"onlyReturnExisting": true}`)
|
||||
res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(http.StatusOK))
|
||||
if e, ok := err.(*Error); ok && e.ProblemType == "urn:ietf:params:acme:error:accountDoesNotExist" {
|
||||
return nil, ErrNoAccount
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
return responseAccount(res)
|
||||
}
|
||||
|
||||
func responseAccount(res *http.Response) (*Account, error) {
|
||||
var v struct {
|
||||
Status string
|
||||
Contact []string
|
||||
Orders string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid account response: %v", err)
|
||||
}
|
||||
return &Account{
|
||||
URI: res.Header.Get("Location"),
|
||||
Status: v.Status,
|
||||
Contact: v.Contact,
|
||||
OrdersURL: v.Orders,
|
||||
}, 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.
|
||||
//
|
||||
// The caller then needs to fetch each authorization with GetAuthorization,
|
||||
// identify those with StatusPending status and fulfill a challenge using Accept.
|
||||
// Once all authorizations are satisfied, the caller will typically want to poll
|
||||
// order status using WaitOrder until it's in StatusReady state.
|
||||
// To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert.
|
||||
func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) {
|
||||
dir, err := c.Discover(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Identifiers []wireAuthzID `json:"identifiers"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}{}
|
||||
for _, v := range id {
|
||||
req.Identifiers = append(req.Identifiers, wireAuthzID{
|
||||
Type: v.Type,
|
||||
Value: v.Value,
|
||||
})
|
||||
}
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case orderNotBeforeOpt:
|
||||
req.NotBefore = time.Time(o).Format(time.RFC3339)
|
||||
case orderNotAfterOpt:
|
||||
req.NotAfter = time.Time(o).Format(time.RFC3339)
|
||||
default:
|
||||
// Package's fault if we let this happen.
|
||||
panic(fmt.Sprintf("unsupported order option type %T", o))
|
||||
}
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, nil, dir.OrderURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return responseOrder(res)
|
||||
}
|
||||
|
||||
// GetOrder retrives an order identified by the given URL.
|
||||
// For orders created with AuthorizeOrder, the url value is Order.URI.
|
||||
//
|
||||
// If a caller needs to poll an order until its status is final,
|
||||
// see the WaitOrder method.
|
||||
func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return responseOrder(res)
|
||||
}
|
||||
|
||||
// WaitOrder polls an order from the given URL until it is in one of the final states,
|
||||
// StatusReady, StatusValid or StatusInvalid, the CA responded with a non-retryable error
|
||||
// or the context is done.
|
||||
//
|
||||
// It returns a non-nil Order only if its Status is StatusReady or StatusValid.
|
||||
// In all other cases WaitOrder returns an error.
|
||||
// If the Status is StatusInvalid, the returned error is of type *OrderError.
|
||||
func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := responseOrder(res)
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case o.Status == StatusInvalid:
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
case o.Status == StatusReady || o.Status == StatusValid:
|
||||
return o, nil
|
||||
}
|
||||
|
||||
d := retryAfter(res.Header.Get("Retry-After"))
|
||||
if d == 0 {
|
||||
// Default retry-after.
|
||||
// Same reasoning as in WaitAuthorization.
|
||||
d = time.Second
|
||||
}
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return nil, ctx.Err()
|
||||
case <-t.C:
|
||||
// Retry.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func responseOrder(res *http.Response) (*Order, error) {
|
||||
var v struct {
|
||||
Status string
|
||||
Expires time.Time
|
||||
Identifiers []wireAuthzID
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
Error *wireError
|
||||
Authorizations []string
|
||||
Finalize string
|
||||
Certificate string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: error reading order: %v", err)
|
||||
}
|
||||
o := &Order{
|
||||
URI: res.Header.Get("Location"),
|
||||
Status: v.Status,
|
||||
Expires: v.Expires,
|
||||
NotBefore: v.NotBefore,
|
||||
NotAfter: v.NotAfter,
|
||||
AuthzURLs: v.Authorizations,
|
||||
FinalizeURL: v.Finalize,
|
||||
CertURL: v.Certificate,
|
||||
}
|
||||
for _, id := range v.Identifiers {
|
||||
o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})
|
||||
}
|
||||
if v.Error != nil {
|
||||
o.Error = v.Error.error(nil /* headers */)
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// CreateOrderCert submits the CSR (Certificate Signing Request) to a CA at the specified URL.
|
||||
// The URL is the FinalizeURL field of an Order created with AuthorizeOrder.
|
||||
//
|
||||
// If the bundle argument is true, the returned value also contain the CA (issuer)
|
||||
// certificate chain. Otherwise, only a leaf certificate is returned.
|
||||
// The returned URL can be used to re-fetch the certificate using FetchCert.
|
||||
//
|
||||
// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs.
|
||||
//
|
||||
// CreateOrderCert returns an error if the CA's response is unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||
func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
|
||||
if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// RFC describes this as "finalize order" request.
|
||||
req := struct {
|
||||
CSR string `json:"csr"`
|
||||
}{
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
o, err := responseOrder(res)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Wait for CA to issue the cert if they haven't.
|
||||
if o.Status != StatusValid {
|
||||
o, err = c.WaitOrder(ctx, o.URI)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// The only acceptable status post finalize and WaitOrder is "valid".
|
||||
if o.Status != StatusValid {
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
}
|
||||
crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
|
||||
return crt, o.CertURL, err
|
||||
}
|
||||
|
||||
// fetchCertRFC downloads issued certificate from the given URL.
|
||||
// It expects the CA to respond with PEM-encoded certificate chain.
|
||||
//
|
||||
// The URL argument is the CertURL field of Order.
|
||||
func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// 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))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
|
||||
}
|
||||
if len(b) > max {
|
||||
return nil, errors.New("acme: certificate chain is too big")
|
||||
}
|
||||
|
||||
// Decode PEM chain.
|
||||
var chain [][]byte
|
||||
for {
|
||||
var p *pem.Block
|
||||
p, b = pem.Decode(b)
|
||||
if p == nil {
|
||||
break
|
||||
}
|
||||
if p.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("acme: invalid PEM cert type %q", p.Type)
|
||||
}
|
||||
|
||||
chain = append(chain, p.Bytes)
|
||||
if !bundle {
|
||||
return chain, nil
|
||||
}
|
||||
if len(chain) > maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too long")
|
||||
}
|
||||
}
|
||||
if len(chain) == 0 {
|
||||
return nil, errors.New("acme: certificate chain is empty")
|
||||
}
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// sends a cert revocation request in either JWK form when key is non-nil or KID form otherwise.
|
||||
func (c *Client) revokeCertRFC(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
|
||||
req := &struct {
|
||||
Cert string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Cert: base64.RawURLEncoding.EncodeToString(cert),
|
||||
Reason: int(reason),
|
||||
}
|
||||
res, err := c.post(ctx, key, c.dir.RevokeURL, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
if isAlreadyRevoked(err) {
|
||||
// Assume it is not an error to revoke an already revoked cert.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+49
-334
@@ -14,18 +14,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ACME status values of Account, Order, Authorization and Challenge objects.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details.
|
||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||
const (
|
||||
StatusDeactivated = "deactivated"
|
||||
StatusExpired = "expired"
|
||||
StatusInvalid = "invalid"
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusReady = "ready"
|
||||
StatusRevoked = "revoked"
|
||||
StatusUnknown = "unknown"
|
||||
StatusValid = "valid"
|
||||
StatusUnknown = "unknown"
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusValid = "valid"
|
||||
StatusInvalid = "invalid"
|
||||
StatusRevoked = "revoked"
|
||||
)
|
||||
|
||||
// CRLReasonCode identifies the reason for a certificate revocation.
|
||||
@@ -45,43 +41,8 @@ const (
|
||||
CRLReasonAACompromise CRLReasonCode = 10
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||
ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// ErrAccountAlreadyExists indicates that the Client's key has already been registered
|
||||
// with the CA. It is returned by Register method.
|
||||
ErrAccountAlreadyExists = errors.New("acme: account already exists")
|
||||
|
||||
// ErrNoAccount indicates that the Client's key has not been registered with the CA.
|
||||
ErrNoAccount = errors.New("acme: account does not exist")
|
||||
)
|
||||
|
||||
// A Subproblem describes an ACME subproblem as reported in an Error.
|
||||
type Subproblem struct {
|
||||
// Type is a URI reference that identifies the problem type,
|
||||
// typically in a "urn:acme:error:xxx" form.
|
||||
Type string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Instance indicates a URL that the client should direct a human user to visit
|
||||
// in order for instructions on how to agree to the updated Terms of Service.
|
||||
// In such an event CA sets StatusCode to 403, Type to
|
||||
// "urn:ietf:params:acme:error:userActionRequired", and adds a Link header with relation
|
||||
// "terms-of-service" containing the latest TOS URL.
|
||||
Instance string
|
||||
// Identifier may contain the ACME identifier that the error is for.
|
||||
Identifier *AuthzID
|
||||
}
|
||||
|
||||
func (sp Subproblem) String() string {
|
||||
str := fmt.Sprintf("%s: ", sp.Type)
|
||||
if sp.Identifier != nil {
|
||||
str += fmt.Sprintf("[%s: %s] ", sp.Identifier.Type, sp.Identifier.Value)
|
||||
}
|
||||
str += sp.Detail
|
||||
return str
|
||||
}
|
||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||
@@ -93,30 +54,13 @@ type Error struct {
|
||||
ProblemType string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Instance indicates a URL that the client should direct a human user to visit
|
||||
// in order for instructions on how to agree to the updated Terms of Service.
|
||||
// In such an event CA sets StatusCode to 403, ProblemType to
|
||||
// "urn:ietf:params:acme:error:userActionRequired" and a Link header with relation
|
||||
// "terms-of-service" containing the latest TOS URL.
|
||||
Instance string
|
||||
// Header is the original server error response headers.
|
||||
// It may be nil.
|
||||
Header http.Header
|
||||
// Subproblems may contain more detailed information about the individual problems
|
||||
// that caused the error. This field is only sent by RFC 8555 compatible ACME
|
||||
// servers. Defined in RFC 8555 Section 6.7.1.
|
||||
Subproblems []Subproblem
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
if len(e.Subproblems) > 0 {
|
||||
str += fmt.Sprintf("; subproblems:")
|
||||
for _, sp := range e.Subproblems {
|
||||
str += fmt.Sprintf("\n\t%s", sp)
|
||||
}
|
||||
}
|
||||
return str
|
||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
}
|
||||
|
||||
// AuthorizationError indicates that an authorization for an identifier
|
||||
@@ -139,27 +83,7 @@ func (a *AuthorizationError) Error() string {
|
||||
for i, err := range a.Errors {
|
||||
e[i] = err.Error()
|
||||
}
|
||||
|
||||
if a.Identifier != "" {
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
// OrderError is returned from Client's order related methods.
|
||||
// It indicates the order is unusable and the clients should start over with
|
||||
// AuthorizeOrder.
|
||||
//
|
||||
// The clients can still fetch the order object from CA using GetOrder
|
||||
// to inspect its state.
|
||||
type OrderError struct {
|
||||
OrderURL string
|
||||
Status string
|
||||
}
|
||||
|
||||
func (oe *OrderError) Error() string {
|
||||
return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
// RateLimit reports whether err represents a rate limit error and
|
||||
@@ -184,110 +108,49 @@ func RateLimit(err error) (time.Duration, bool) {
|
||||
}
|
||||
|
||||
// Account is a user account. It is associated with a private key.
|
||||
// Non-RFC 8555 fields are empty when interfacing with a compliant CA.
|
||||
type Account struct {
|
||||
// URI is the account unique ID, which is also a URL used to retrieve
|
||||
// account data from the CA.
|
||||
// When interfacing with RFC 8555-compliant CAs, URI is the "kid" field
|
||||
// value in JWS signed requests.
|
||||
URI string
|
||||
|
||||
// Contact is a slice of contact info used during registration.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.3 for supported
|
||||
// formats.
|
||||
Contact []string
|
||||
|
||||
// Status indicates current account status as returned by the CA.
|
||||
// Possible values are StatusValid, StatusDeactivated, and StatusRevoked.
|
||||
Status string
|
||||
|
||||
// OrdersURL is a URL from which a list of orders submitted by this account
|
||||
// can be fetched.
|
||||
OrdersURL string
|
||||
|
||||
// The terms user has agreed to.
|
||||
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
||||
// to the actual Terms of Service of the CA.
|
||||
//
|
||||
// It is non-RFC 8555 compliant. Package users can store the ToS they agree to
|
||||
// during Client's Register call in the prompt callback function.
|
||||
AgreedTerms string
|
||||
|
||||
// Actual terms of a CA.
|
||||
//
|
||||
// It is non-RFC 8555 compliant. Use Directory's Terms field.
|
||||
// When a CA updates their terms and requires an account agreement,
|
||||
// a URL at which instructions to do so is available in Error's Instance field.
|
||||
CurrentTerms string
|
||||
|
||||
// Authz is the authorization URL used to initiate a new authz flow.
|
||||
//
|
||||
// It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL.
|
||||
Authz string
|
||||
|
||||
// Authorizations is a URI from which a list of authorizations
|
||||
// granted to this account can be fetched via a GET request.
|
||||
//
|
||||
// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
|
||||
Authorizations string
|
||||
|
||||
// Certificates is a URI from which a list of certificates
|
||||
// issued for this account can be fetched via a GET request.
|
||||
//
|
||||
// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
|
||||
Certificates string
|
||||
|
||||
// ExternalAccountBinding represents an arbitrary binding to an account of
|
||||
// the CA which the ACME server is tied to.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
|
||||
ExternalAccountBinding *ExternalAccountBinding
|
||||
}
|
||||
|
||||
// ExternalAccountBinding contains the data needed to form a request with
|
||||
// an external account binding.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
|
||||
type ExternalAccountBinding struct {
|
||||
// KID is the Key ID of the symmetric MAC key that the CA provides to
|
||||
// identify an external account from ACME.
|
||||
KID string
|
||||
|
||||
// Key is the bytes of the symmetric key that the CA provides to identify
|
||||
// the account. Key must correspond to the KID.
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (e *ExternalAccountBinding) String() string {
|
||||
return fmt.Sprintf("&{KID: %q, Key: redacted}", e.KID)
|
||||
}
|
||||
|
||||
// Directory is ACME server discovery data.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.1 for more details.
|
||||
type Directory struct {
|
||||
// NonceURL indicates an endpoint where to fetch fresh nonce values from.
|
||||
NonceURL string
|
||||
|
||||
// RegURL is an account endpoint URL, allowing for creating new accounts.
|
||||
// Pre-RFC 8555 CAs also allow modifying existing accounts at this URL.
|
||||
// RegURL is an account endpoint URL, allowing for creating new
|
||||
// and modifying existing accounts.
|
||||
RegURL string
|
||||
|
||||
// OrderURL is used to initiate the certificate issuance flow
|
||||
// as described in RFC 8555.
|
||||
OrderURL string
|
||||
|
||||
// AuthzURL is used to initiate identifier pre-authorization flow.
|
||||
// Empty string indicates the flow is unsupported by the CA.
|
||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||
AuthzURL string
|
||||
|
||||
// CertURL is a new certificate issuance endpoint URL.
|
||||
// It is non-RFC 8555 compliant and is obsoleted by OrderURL.
|
||||
CertURL string
|
||||
|
||||
// RevokeURL is used to initiate a certificate revocation flow.
|
||||
RevokeURL string
|
||||
|
||||
// KeyChangeURL allows to perform account key rollover flow.
|
||||
KeyChangeURL string
|
||||
|
||||
// Term is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
@@ -297,120 +160,46 @@ 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
|
||||
// requests to include external account binding information.
|
||||
ExternalAccountRequired bool
|
||||
}
|
||||
|
||||
// Order represents a client's request for a certificate.
|
||||
// It tracks the request flow progress through to issuance.
|
||||
type Order struct {
|
||||
// URI uniquely identifies an order.
|
||||
// Challenge encodes a returned CA challenge.
|
||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
||||
// with StatusInvalid.
|
||||
type Challenge struct {
|
||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
||||
Type string
|
||||
|
||||
// URI is where a challenge response can be posted to.
|
||||
URI string
|
||||
|
||||
// Status represents the current status of the order.
|
||||
// It indicates which action the client should take.
|
||||
//
|
||||
// Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid.
|
||||
// Pending means the CA does not believe that the client has fulfilled the requirements.
|
||||
// Ready indicates that the client has fulfilled all the requirements and can submit a CSR
|
||||
// to obtain a certificate. This is done with Client's CreateOrderCert.
|
||||
// Processing means the certificate is being issued.
|
||||
// Valid indicates the CA has issued the certificate. It can be downloaded
|
||||
// from the Order's CertURL. This is done with Client's FetchCert.
|
||||
// Invalid means the certificate will not be issued. Users should consider this order
|
||||
// abandoned.
|
||||
// Token is a random value that uniquely identifies the challenge.
|
||||
Token string
|
||||
|
||||
// Status identifies the status of this challenge.
|
||||
Status string
|
||||
|
||||
// Expires is the timestamp after which CA considers this order invalid.
|
||||
Expires time.Time
|
||||
|
||||
// Identifiers contains all identifier objects which the order pertains to.
|
||||
Identifiers []AuthzID
|
||||
|
||||
// NotBefore is the requested value of the notBefore field in the certificate.
|
||||
NotBefore time.Time
|
||||
|
||||
// NotAfter is the requested value of the notAfter field in the certificate.
|
||||
NotAfter time.Time
|
||||
|
||||
// AuthzURLs represents authorizations to complete before a certificate
|
||||
// for identifiers specified in the order can be issued.
|
||||
// It also contains unexpired authorizations that the client has completed
|
||||
// in the past.
|
||||
//
|
||||
// Authorization objects can be fetched using Client's GetAuthorization method.
|
||||
//
|
||||
// The required authorizations are dictated by CA policies.
|
||||
// There may not be a 1:1 relationship between the identifiers and required authorizations.
|
||||
// Required authorizations can be identified by their StatusPending status.
|
||||
//
|
||||
// For orders in the StatusValid or StatusInvalid state these are the authorizations
|
||||
// which were completed.
|
||||
AuthzURLs []string
|
||||
|
||||
// FinalizeURL is the endpoint at which a CSR is submitted to obtain a certificate
|
||||
// once all the authorizations are satisfied.
|
||||
FinalizeURL string
|
||||
|
||||
// CertURL points to the certificate that has been issued in response to this order.
|
||||
CertURL string
|
||||
|
||||
// The error that occurred while processing the order as received from a CA, if any.
|
||||
Error *Error
|
||||
// Error indicates the reason for an authorization failure
|
||||
// when this challenge was used.
|
||||
// The type of a non-nil value is *Error.
|
||||
Error error
|
||||
}
|
||||
|
||||
// OrderOption allows customizing Client.AuthorizeOrder call.
|
||||
type OrderOption interface {
|
||||
privateOrderOpt()
|
||||
}
|
||||
|
||||
// WithOrderNotBefore sets order's NotBefore field.
|
||||
func WithOrderNotBefore(t time.Time) OrderOption {
|
||||
return orderNotBeforeOpt(t)
|
||||
}
|
||||
|
||||
// WithOrderNotAfter sets order's NotAfter field.
|
||||
func WithOrderNotAfter(t time.Time) OrderOption {
|
||||
return orderNotAfterOpt(t)
|
||||
}
|
||||
|
||||
type orderNotBeforeOpt time.Time
|
||||
|
||||
func (orderNotBeforeOpt) privateOrderOpt() {}
|
||||
|
||||
type orderNotAfterOpt time.Time
|
||||
|
||||
func (orderNotAfterOpt) privateOrderOpt() {}
|
||||
|
||||
// Authorization encodes an authorization response.
|
||||
type Authorization struct {
|
||||
// URI uniquely identifies a authorization.
|
||||
URI string
|
||||
|
||||
// Status is the current status of an authorization.
|
||||
// Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated,
|
||||
// StatusExpired and StatusRevoked.
|
||||
// Status identifies the status of an authorization.
|
||||
Status string
|
||||
|
||||
// Identifier is what the account is authorized to represent.
|
||||
Identifier AuthzID
|
||||
|
||||
// The timestamp after which the CA considers the authorization invalid.
|
||||
Expires time.Time
|
||||
|
||||
// Wildcard is true for authorizations of a wildcard domain name.
|
||||
Wildcard bool
|
||||
|
||||
// Challenges that the client needs to fulfill in order to prove possession
|
||||
// of the identifier (for pending authorizations).
|
||||
// For valid authorizations, the challenge that was validated.
|
||||
// For invalid authorizations, the challenge that was attempted and failed.
|
||||
//
|
||||
// RFC 8555 compatible CAs require users to fuflfill only one of the challenges.
|
||||
// For final authorizations, the challenges that were used.
|
||||
Challenges []*Challenge
|
||||
|
||||
// A collection of sets of challenges, each of which would be sufficient
|
||||
@@ -418,52 +207,24 @@ type Authorization struct {
|
||||
// Clients must complete a set of challenges that covers at least one set.
|
||||
// Challenges are identified by their indices in the challenges array.
|
||||
// If this field is empty, the client needs to complete all challenges.
|
||||
//
|
||||
// This field is unused in RFC 8555.
|
||||
Combinations [][]int
|
||||
}
|
||||
|
||||
// AuthzID is an identifier that an account is authorized to represent.
|
||||
type AuthzID struct {
|
||||
Type string // The type of identifier, "dns" or "ip".
|
||||
Type string // The type of identifier, e.g. "dns".
|
||||
Value string // The identifier itself, e.g. "example.org".
|
||||
}
|
||||
|
||||
// DomainIDs creates a slice of AuthzID with "dns" identifier type.
|
||||
func DomainIDs(names ...string) []AuthzID {
|
||||
a := make([]AuthzID, len(names))
|
||||
for i, v := range names {
|
||||
a[i] = AuthzID{Type: "dns", Value: v}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 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.
|
||||
func IPIDs(addr ...string) []AuthzID {
|
||||
a := make([]AuthzID, len(addr))
|
||||
for i, v := range addr {
|
||||
a[i] = AuthzID{Type: "ip", Value: v}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// wireAuthzID is ACME JSON representation of authorization identifier objects.
|
||||
type wireAuthzID struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||
type wireAuthz struct {
|
||||
Identifier wireAuthzID
|
||||
Status string
|
||||
Expires time.Time
|
||||
Wildcard bool
|
||||
Challenges []wireChallenge
|
||||
Combinations [][]int
|
||||
Error *wireError
|
||||
Identifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
|
||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
@@ -471,10 +232,8 @@ func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
URI: uri,
|
||||
Status: z.Status,
|
||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||
Expires: z.Expires,
|
||||
Wildcard: z.Wildcard,
|
||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||
Combinations: z.Combinations, // shallow copy
|
||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||
}
|
||||
for i, v := range z.Challenges {
|
||||
a.Challenges[i] = v.challenge()
|
||||
@@ -487,69 +246,30 @@ func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||
URI: uri,
|
||||
Identifier: z.Identifier.Value,
|
||||
}
|
||||
|
||||
if z.Error != nil {
|
||||
err.Errors = append(err.Errors, z.Error.error(nil))
|
||||
}
|
||||
|
||||
for _, raw := range z.Challenges {
|
||||
if raw.Error != nil {
|
||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Challenge encodes a returned CA challenge.
|
||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
||||
// with StatusInvalid.
|
||||
type Challenge struct {
|
||||
// Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01".
|
||||
Type string
|
||||
|
||||
// URI is where a challenge response can be posted to.
|
||||
URI string
|
||||
|
||||
// Token is a random value that uniquely identifies the challenge.
|
||||
Token string
|
||||
|
||||
// Status identifies the status of this challenge.
|
||||
// In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid,
|
||||
// and StatusInvalid.
|
||||
Status string
|
||||
|
||||
// Validated is the time at which the CA validated this challenge.
|
||||
// Always zero value in pre-RFC 8555.
|
||||
Validated time.Time
|
||||
|
||||
// Error indicates the reason for an authorization failure
|
||||
// when this challenge was used.
|
||||
// The type of a non-nil value is *Error.
|
||||
Error error
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
type wireChallenge struct {
|
||||
URL string `json:"url"` // RFC
|
||||
URI string `json:"uri"` // pre-RFC
|
||||
Type string
|
||||
Token string
|
||||
Status string
|
||||
Validated time.Time
|
||||
Error *wireError
|
||||
URI string `json:"uri"`
|
||||
Type string
|
||||
Token string
|
||||
Status string
|
||||
Error *wireError
|
||||
}
|
||||
|
||||
func (c *wireChallenge) challenge() *Challenge {
|
||||
v := &Challenge{
|
||||
URI: c.URL,
|
||||
URI: c.URI,
|
||||
Type: c.Type,
|
||||
Token: c.Token,
|
||||
Status: c.Status,
|
||||
}
|
||||
if v.URI == "" {
|
||||
v.URI = c.URI // c.URL was empty; use legacy
|
||||
}
|
||||
if v.Status == "" {
|
||||
v.Status = StatusPending
|
||||
}
|
||||
@@ -562,23 +282,18 @@ func (c *wireChallenge) challenge() *Challenge {
|
||||
// wireError is a subset of fields of the Problem Details object
|
||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
||||
type wireError struct {
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
Instance string
|
||||
Subproblems []Subproblem
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
}
|
||||
|
||||
func (e *wireError) error(h http.Header) *Error {
|
||||
err := &Error{
|
||||
return &Error{
|
||||
StatusCode: e.Status,
|
||||
ProblemType: e.Type,
|
||||
Detail: e.Detail,
|
||||
Instance: e.Instance,
|
||||
Header: h,
|
||||
Subproblems: e.Subproblems,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||
|
||||
@@ -7,23 +7,10 @@ package acme
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExternalAccountBindingString(t *testing.T) {
|
||||
eab := ExternalAccountBinding{
|
||||
KID: "kid",
|
||||
Key: []byte("key"),
|
||||
}
|
||||
got := eab.String()
|
||||
want := `&{KID: "kid", Key: redacted}`
|
||||
if got != want {
|
||||
t.Errorf("eab.String() = %q, want: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit(t *testing.T) {
|
||||
now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
|
||||
f := timeNow
|
||||
@@ -74,146 +61,3 @@ func TestRateLimit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationError(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
err *AuthorizationError
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
desc: "when auth error identifier is set",
|
||||
err: &AuthorizationError{
|
||||
Identifier: "domain.com",
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for domain.com prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
},
|
||||
msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
|
||||
},
|
||||
|
||||
{
|
||||
desc: "when auth error identifier is unset",
|
||||
err: &AuthorizationError{
|
||||
Errors: []error{
|
||||
(&wireError{
|
||||
Status: 403,
|
||||
Type: "urn:ietf:params:acme:error:caa",
|
||||
Detail: "CAA record for domain.com prevents issuance",
|
||||
}).error(nil),
|
||||
},
|
||||
},
|
||||
msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.err.Error() != tt.msg {
|
||||
t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubproblems(t *testing.T) {
|
||||
tests := []struct {
|
||||
wire wireError
|
||||
expectedOut Error
|
||||
}{
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
},
|
||||
},
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
wire: wireError{
|
||||
Status: 1,
|
||||
Type: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOut: Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
out := tc.wire.error(nil)
|
||||
if !reflect.DeepEqual(*out, tc.expectedOut) {
|
||||
t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorStringerWithSubproblems(t *testing.T) {
|
||||
err := Error{
|
||||
StatusCode: 1,
|
||||
ProblemType: "urn:error",
|
||||
Detail: "it's an error",
|
||||
Subproblems: []Subproblem{
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
},
|
||||
{
|
||||
Type: "urn:error:sub",
|
||||
Detail: "it's a subproblem",
|
||||
Identifier: &AuthzID{Type: "dns", Value: "example"},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem"
|
||||
if err.Error() != expectedStr {
|
||||
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +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.12
|
||||
// +build go1.12
|
||||
|
||||
package acme
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func init() {
|
||||
// Set packageVersion if the binary was built in modules mode and x/crypto
|
||||
// was not replaced with a different module.
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, m := range info.Deps {
|
||||
if m.Path != "golang.org/x/crypto" {
|
||||
continue
|
||||
}
|
||||
if m.Replace == nil {
|
||||
packageVersion = m.Version
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -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
|
||||
|
||||
@@ -2,8 +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
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
@@ -2,8 +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
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
|
||||
@@ -2,8 +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 || purego || !gc
|
||||
// +build !amd64 purego !gc
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
+1
-10
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xb.
|
||||
//
|
||||
// BLAKE2b is optimized for 64-bit platforms—including NEON-enabled ARMs—and
|
||||
// produces digests of any size between 1 and 64 bytes.
|
||||
// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xb see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// 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
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
+51
-46
@@ -2,8 +2,7 @@
|
||||
// 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
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -283,12 +282,14 @@ TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, DX
|
||||
ADDQ $31, DX
|
||||
ANDQ $~31, DX
|
||||
MOVQ SP, R9
|
||||
ADDQ $31, R9
|
||||
ANDQ $~31, R9
|
||||
MOVQ R9, SP
|
||||
|
||||
MOVQ CX, 16(DX)
|
||||
MOVQ CX, 16(SP)
|
||||
XORQ CX, CX
|
||||
MOVQ CX, 24(DX)
|
||||
MOVQ CX, 24(SP)
|
||||
|
||||
VMOVDQU ·AVX2_c40<>(SB), Y4
|
||||
VMOVDQU ·AVX2_c48<>(SB), Y5
|
||||
@@ -300,33 +301,33 @@ TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment
|
||||
|
||||
MOVQ 0(BX), R8
|
||||
MOVQ 8(BX), R9
|
||||
MOVQ R9, 8(DX)
|
||||
MOVQ R9, 8(SP)
|
||||
|
||||
loop:
|
||||
ADDQ $128, R8
|
||||
MOVQ R8, 0(DX)
|
||||
MOVQ R8, 0(SP)
|
||||
CMPQ R8, $128
|
||||
JGE noinc
|
||||
INCQ R9
|
||||
MOVQ R9, 8(DX)
|
||||
MOVQ R9, 8(SP)
|
||||
|
||||
noinc:
|
||||
VMOVDQA Y8, Y0
|
||||
VMOVDQA Y9, Y1
|
||||
VMOVDQA Y6, Y2
|
||||
VPXOR 0(DX), Y7, Y3
|
||||
VPXOR 0(SP), Y7, Y3
|
||||
|
||||
LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15()
|
||||
VMOVDQA Y12, 32(DX)
|
||||
VMOVDQA Y13, 64(DX)
|
||||
VMOVDQA Y14, 96(DX)
|
||||
VMOVDQA Y15, 128(DX)
|
||||
VMOVDQA Y12, 32(SP)
|
||||
VMOVDQA Y13, 64(SP)
|
||||
VMOVDQA Y14, 96(SP)
|
||||
VMOVDQA Y15, 128(SP)
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3()
|
||||
VMOVDQA Y12, 160(DX)
|
||||
VMOVDQA Y13, 192(DX)
|
||||
VMOVDQA Y14, 224(DX)
|
||||
VMOVDQA Y15, 256(DX)
|
||||
VMOVDQA Y12, 160(SP)
|
||||
VMOVDQA Y13, 192(SP)
|
||||
VMOVDQA Y14, 224(SP)
|
||||
VMOVDQA Y15, 256(SP)
|
||||
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4()
|
||||
@@ -346,8 +347,8 @@ noinc:
|
||||
LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0()
|
||||
ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5)
|
||||
|
||||
ROUND_AVX2(32(DX), 64(DX), 96(DX), 128(DX), Y10, Y4, Y5)
|
||||
ROUND_AVX2(160(DX), 192(DX), 224(DX), 256(DX), Y10, Y4, Y5)
|
||||
ROUND_AVX2(32(SP), 64(SP), 96(SP), 128(SP), Y10, Y4, Y5)
|
||||
ROUND_AVX2(160(SP), 192(SP), 224(SP), 256(SP), Y10, Y4, Y5)
|
||||
|
||||
VPXOR Y0, Y8, Y8
|
||||
VPXOR Y1, Y9, Y9
|
||||
@@ -365,6 +366,7 @@ noinc:
|
||||
VMOVDQU Y9, 32(AX)
|
||||
VZEROUPPER
|
||||
|
||||
MOVQ DX, SP
|
||||
RET
|
||||
|
||||
#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA
|
||||
@@ -582,9 +584,11 @@ TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
MOVQ blocks_base+24(FP), SI
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, R10
|
||||
ADDQ $15, R10
|
||||
ANDQ $~15, R10
|
||||
MOVQ SP, BP
|
||||
MOVQ SP, R9
|
||||
ADDQ $15, R9
|
||||
ANDQ $~15, R9
|
||||
MOVQ R9, SP
|
||||
|
||||
VMOVDQU ·AVX_c40<>(SB), X0
|
||||
VMOVDQU ·AVX_c48<>(SB), X1
|
||||
@@ -592,8 +596,8 @@ TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
VMOVDQA X1, X9
|
||||
|
||||
VMOVDQU ·AVX_iv3<>(SB), X0
|
||||
VMOVDQA X0, 0(R10)
|
||||
XORQ CX, 0(R10) // 0(R10) = ·AVX_iv3 ^ (CX || 0)
|
||||
VMOVDQA X0, 0(SP)
|
||||
XORQ CX, 0(SP) // 0(SP) = ·AVX_iv3 ^ (CX || 0)
|
||||
|
||||
VMOVDQU 0(AX), X10
|
||||
VMOVDQU 16(AX), X11
|
||||
@@ -620,35 +624,35 @@ noinc:
|
||||
VMOVDQU ·AVX_iv2<>(SB), X6
|
||||
|
||||
VPXOR X15, X6, X6
|
||||
VMOVDQA 0(R10), X7
|
||||
VMOVDQA 0(SP), X7
|
||||
|
||||
LOAD_MSG_AVX_0_2_4_6_1_3_5_7()
|
||||
VMOVDQA X12, 16(R10)
|
||||
VMOVDQA X13, 32(R10)
|
||||
VMOVDQA X14, 48(R10)
|
||||
VMOVDQA X15, 64(R10)
|
||||
VMOVDQA X12, 16(SP)
|
||||
VMOVDQA X13, 32(SP)
|
||||
VMOVDQA X14, 48(SP)
|
||||
VMOVDQA X15, 64(SP)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15)
|
||||
VMOVDQA X12, 80(R10)
|
||||
VMOVDQA X13, 96(R10)
|
||||
VMOVDQA X14, 112(R10)
|
||||
VMOVDQA X15, 128(R10)
|
||||
VMOVDQA X12, 80(SP)
|
||||
VMOVDQA X13, 96(SP)
|
||||
VMOVDQA X14, 112(SP)
|
||||
VMOVDQA X15, 128(SP)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6)
|
||||
VMOVDQA X12, 144(R10)
|
||||
VMOVDQA X13, 160(R10)
|
||||
VMOVDQA X14, 176(R10)
|
||||
VMOVDQA X15, 192(R10)
|
||||
VMOVDQA X12, 144(SP)
|
||||
VMOVDQA X13, 160(SP)
|
||||
VMOVDQA X14, 176(SP)
|
||||
VMOVDQA X15, 192(SP)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
LOAD_MSG_AVX_1_0_11_5_12_2_7_3()
|
||||
VMOVDQA X12, 208(R10)
|
||||
VMOVDQA X13, 224(R10)
|
||||
VMOVDQA X14, 240(R10)
|
||||
VMOVDQA X15, 256(R10)
|
||||
VMOVDQA X12, 208(SP)
|
||||
VMOVDQA X13, 224(SP)
|
||||
VMOVDQA X14, 240(SP)
|
||||
VMOVDQA X15, 256(SP)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
@@ -708,14 +712,14 @@ noinc:
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(SP), 96(SP), 112(SP), 128(SP), X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(SP), 160(SP), 176(SP), 192(SP), X15, X8, X9)
|
||||
SHUFFLE_AVX()
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X15, X8, X9)
|
||||
HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(SP), 224(SP), 240(SP), 256(SP), X15, X8, X9)
|
||||
SHUFFLE_AVX_INV()
|
||||
|
||||
VMOVDQU 32(AX), X14
|
||||
@@ -742,4 +746,5 @@ noinc:
|
||||
MOVQ R9, 8(BX)
|
||||
VZEROUPPER
|
||||
|
||||
MOVQ BP, SP
|
||||
RET
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// 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
|
||||
// +build !go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
+30
-28
@@ -2,8 +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
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -119,13 +118,15 @@ TEXT ·hashBlocksSSE4(SB), 4, $288-48 // frame size = 272 + 16 byte alignment
|
||||
MOVQ blocks_base+24(FP), SI
|
||||
MOVQ blocks_len+32(FP), DI
|
||||
|
||||
MOVQ SP, R10
|
||||
ADDQ $15, R10
|
||||
ANDQ $~15, R10
|
||||
MOVQ SP, BP
|
||||
MOVQ SP, R9
|
||||
ADDQ $15, R9
|
||||
ANDQ $~15, R9
|
||||
MOVQ R9, SP
|
||||
|
||||
MOVOU ·iv3<>(SB), X0
|
||||
MOVO X0, 0(R10)
|
||||
XORQ CX, 0(R10) // 0(R10) = ·iv3 ^ (CX || 0)
|
||||
MOVO X0, 0(SP)
|
||||
XORQ CX, 0(SP) // 0(SP) = ·iv3 ^ (CX || 0)
|
||||
|
||||
MOVOU ·c40<>(SB), X13
|
||||
MOVOU ·c48<>(SB), X14
|
||||
@@ -155,35 +156,35 @@ noinc:
|
||||
MOVOU ·iv2<>(SB), X6
|
||||
|
||||
PXOR X8, X6
|
||||
MOVO 0(R10), X7
|
||||
MOVO 0(SP), X7
|
||||
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7)
|
||||
MOVO X8, 16(R10)
|
||||
MOVO X9, 32(R10)
|
||||
MOVO X10, 48(R10)
|
||||
MOVO X11, 64(R10)
|
||||
MOVO X8, 16(SP)
|
||||
MOVO X9, 32(SP)
|
||||
MOVO X10, 48(SP)
|
||||
MOVO X11, 64(SP)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 8, 10, 12, 14, 9, 11, 13, 15)
|
||||
MOVO X8, 80(R10)
|
||||
MOVO X9, 96(R10)
|
||||
MOVO X10, 112(R10)
|
||||
MOVO X11, 128(R10)
|
||||
MOVO X8, 80(SP)
|
||||
MOVO X9, 96(SP)
|
||||
MOVO X10, 112(SP)
|
||||
MOVO X11, 128(SP)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 14, 4, 9, 13, 10, 8, 15, 6)
|
||||
MOVO X8, 144(R10)
|
||||
MOVO X9, 160(R10)
|
||||
MOVO X10, 176(R10)
|
||||
MOVO X11, 192(R10)
|
||||
MOVO X8, 144(SP)
|
||||
MOVO X9, 160(SP)
|
||||
MOVO X10, 176(SP)
|
||||
MOVO X11, 192(SP)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 1, 0, 11, 5, 12, 2, 7, 3)
|
||||
MOVO X8, 208(R10)
|
||||
MOVO X9, 224(R10)
|
||||
MOVO X10, 240(R10)
|
||||
MOVO X11, 256(R10)
|
||||
MOVO X8, 208(SP)
|
||||
MOVO X9, 224(SP)
|
||||
MOVO X10, 240(SP)
|
||||
MOVO X11, 256(SP)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
@@ -243,14 +244,14 @@ noinc:
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(SP), 96(SP), 112(SP), 128(SP), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(SP), 160(SP), 176(SP), 192(SP), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X11, X13, X14)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(SP), 224(SP), 240(SP), 256(SP), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
MOVOU 32(AX), X10
|
||||
@@ -276,4 +277,5 @@ noinc:
|
||||
MOVQ R8, 0(BX)
|
||||
MOVQ R9, 8(BX)
|
||||
|
||||
MOVQ BP, SP
|
||||
RET
|
||||
|
||||
@@ -2,8 +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 || purego || !gc
|
||||
// +build !amd64 purego !gc
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
// Package blake2s implements the BLAKE2s hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xs.
|
||||
//
|
||||
// BLAKE2s is optimized for 8- to 32-bit platforms and produces digests of any
|
||||
// size between 1 and 32 bytes.
|
||||
// For a detailed specification of BLAKE2s see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xs see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
|
||||
@@ -2,8 +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
|
||||
// +build 386,!gccgo,!appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+49
-44
@@ -2,8 +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
|
||||
// +build 386,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -298,17 +297,19 @@ TEXT ·hashBlocksSSE2(SB), 0, $672-24 // frame = 656 + 16 byte alignment
|
||||
MOVL blocks_base+12(FP), SI
|
||||
MOVL blocks_len+16(FP), DX
|
||||
|
||||
MOVL SP, BP
|
||||
MOVL SP, DI
|
||||
ADDL $15, DI
|
||||
ANDL $~15, DI
|
||||
MOVL DI, SP
|
||||
|
||||
MOVL CX, 8(DI)
|
||||
MOVL CX, 8(SP)
|
||||
MOVL 0(BX), CX
|
||||
MOVL CX, 0(DI)
|
||||
MOVL CX, 0(SP)
|
||||
MOVL 4(BX), CX
|
||||
MOVL CX, 4(DI)
|
||||
MOVL CX, 4(SP)
|
||||
XORL CX, CX
|
||||
MOVL CX, 12(DI)
|
||||
MOVL CX, 12(SP)
|
||||
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 16(AX), X1
|
||||
@@ -320,22 +321,22 @@ loop:
|
||||
MOVOU iv0<>(SB), X6
|
||||
MOVOU iv1<>(SB), X7
|
||||
|
||||
MOVO 0(DI), X3
|
||||
MOVO 0(SP), X3
|
||||
PADDQ X2, X3
|
||||
PXOR X3, X7
|
||||
MOVO X3, 0(DI)
|
||||
MOVO X3, 0(SP)
|
||||
|
||||
PRECOMPUTE(DI, 16, SI, CX)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3)
|
||||
PRECOMPUTE(SP, 16, SI, CX)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X3)
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X3)
|
||||
|
||||
PXOR X4, X0
|
||||
PXOR X5, X1
|
||||
@@ -346,14 +347,15 @@ loop:
|
||||
SUBL $64, DX
|
||||
JNE loop
|
||||
|
||||
MOVL 0(DI), CX
|
||||
MOVL 0(SP), CX
|
||||
MOVL CX, 0(BX)
|
||||
MOVL 4(DI), CX
|
||||
MOVL 4(SP), CX
|
||||
MOVL CX, 4(BX)
|
||||
|
||||
MOVOU X0, 0(AX)
|
||||
MOVOU X1, 16(AX)
|
||||
|
||||
MOVL BP, SP
|
||||
RET
|
||||
|
||||
// func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte)
|
||||
@@ -364,52 +366,54 @@ TEXT ·hashBlocksSSSE3(SB), 0, $704-24 // frame = 688 + 16 byte alignment
|
||||
MOVL blocks_base+12(FP), SI
|
||||
MOVL blocks_len+16(FP), DX
|
||||
|
||||
MOVL SP, BP
|
||||
MOVL SP, DI
|
||||
ADDL $15, DI
|
||||
ANDL $~15, DI
|
||||
MOVL DI, SP
|
||||
|
||||
MOVL CX, 8(DI)
|
||||
MOVL CX, 8(SP)
|
||||
MOVL 0(BX), CX
|
||||
MOVL CX, 0(DI)
|
||||
MOVL CX, 0(SP)
|
||||
MOVL 4(BX), CX
|
||||
MOVL CX, 4(DI)
|
||||
MOVL CX, 4(SP)
|
||||
XORL CX, CX
|
||||
MOVL CX, 12(DI)
|
||||
MOVL CX, 12(SP)
|
||||
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 16(AX), X1
|
||||
MOVOU counter<>(SB), X2
|
||||
|
||||
loop:
|
||||
MOVO X0, 656(DI)
|
||||
MOVO X1, 672(DI)
|
||||
MOVO X0, 656(SP)
|
||||
MOVO X1, 672(SP)
|
||||
MOVO X0, X4
|
||||
MOVO X1, X5
|
||||
MOVOU iv0<>(SB), X6
|
||||
MOVOU iv1<>(SB), X7
|
||||
|
||||
MOVO 0(DI), X3
|
||||
MOVO 0(SP), X3
|
||||
PADDQ X2, X3
|
||||
PXOR X3, X7
|
||||
MOVO X3, 0(DI)
|
||||
MOVO X3, 0(SP)
|
||||
|
||||
MOVOU rol16<>(SB), X0
|
||||
MOVOU rol8<>(SB), X1
|
||||
|
||||
PRECOMPUTE(DI, 16, SI, CX)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3, X0, X1)
|
||||
PRECOMPUTE(SP, 16, SI, CX)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X3, X0, X1)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X3, X0, X1)
|
||||
|
||||
MOVO 656(DI), X0
|
||||
MOVO 672(DI), X1
|
||||
MOVO 656(SP), X0
|
||||
MOVO 672(SP), X1
|
||||
PXOR X4, X0
|
||||
PXOR X5, X1
|
||||
PXOR X6, X0
|
||||
@@ -419,12 +423,13 @@ loop:
|
||||
SUBL $64, DX
|
||||
JNE loop
|
||||
|
||||
MOVL 0(DI), CX
|
||||
MOVL 0(SP), CX
|
||||
MOVL CX, 0(BX)
|
||||
MOVL 4(DI), CX
|
||||
MOVL 4(SP), CX
|
||||
MOVL CX, 4(BX)
|
||||
|
||||
MOVOU X0, 0(AX)
|
||||
MOVOU X1, 16(AX)
|
||||
|
||||
MOVL BP, SP
|
||||
RET
|
||||
|
||||
@@ -2,8 +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
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+37
-32
@@ -2,8 +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
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -318,30 +317,30 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVL R15, 8*4+off+576(dst)
|
||||
|
||||
#define BLAKE2s_SSE2() \
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(BP), 32(BP), 48(BP), 64(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(BP), 32+64(BP), 48+64(BP), 64+64(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(BP), 32+128(BP), 48+128(BP), 64+128(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(BP), 32+192(BP), 48+192(BP), 64+192(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(BP), 32+256(BP), 48+256(BP), 64+256(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(BP), 32+320(BP), 48+320(BP), 64+320(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(BP), 32+384(BP), 48+384(BP), 64+384(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(BP), 32+448(BP), 48+448(BP), 64+448(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(BP), 32+512(BP), 48+512(BP), 64+512(BP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(BP), 32+576(BP), 48+576(BP), 64+576(BP), X8)
|
||||
PRECOMPUTE_MSG(SP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X8); \
|
||||
ROUND_SSE2(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X8)
|
||||
|
||||
#define BLAKE2s_SSSE3() \
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(BP), 32(BP), 48(BP), 64(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(BP), 32+64(BP), 48+64(BP), 64+64(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(BP), 32+128(BP), 48+128(BP), 64+128(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(BP), 32+192(BP), 48+192(BP), 64+192(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(BP), 32+256(BP), 48+256(BP), 64+256(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(BP), 32+320(BP), 48+320(BP), 64+320(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(BP), 32+384(BP), 48+384(BP), 64+384(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(BP), 32+448(BP), 48+448(BP), 64+448(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(BP), 32+512(BP), 48+512(BP), 64+512(BP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(BP), 32+576(BP), 48+576(BP), 64+576(BP), X8, X13, X14)
|
||||
PRECOMPUTE_MSG(SP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16(SP), 32(SP), 48(SP), 64(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+64(SP), 32+64(SP), 48+64(SP), 64+64(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+128(SP), 32+128(SP), 48+128(SP), 64+128(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+192(SP), 32+192(SP), 48+192(SP), 64+192(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+256(SP), 32+256(SP), 48+256(SP), 64+256(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+320(SP), 32+320(SP), 48+320(SP), 64+320(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+384(SP), 32+384(SP), 48+384(SP), 64+384(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+448(SP), 32+448(SP), 48+448(SP), 64+448(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+512(SP), 32+512(SP), 48+512(SP), 64+512(SP), X8, X13, X14); \
|
||||
ROUND_SSSE3(X4, X5, X6, X7, 16+576(SP), 32+576(SP), 48+576(SP), 64+576(SP), X8, X13, X14)
|
||||
|
||||
#define BLAKE2s_SSE4() \
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15); \
|
||||
@@ -373,12 +372,16 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVQ blocks_len, DX; \
|
||||
\
|
||||
MOVQ SP, BP; \
|
||||
ADDQ $15, BP; \
|
||||
ANDQ $~15, BP; \
|
||||
MOVQ SP, R9; \
|
||||
ADDQ $15, R9; \
|
||||
ANDQ $~15, R9; \
|
||||
MOVQ R9, SP; \
|
||||
\
|
||||
MOVQ 0(BX), R9; \
|
||||
MOVQ R9, 0(BP); \
|
||||
MOVQ CX, 8(BP); \
|
||||
MOVQ R9, 0(SP); \
|
||||
XORQ R9, R9; \
|
||||
MOVQ R9, 8(SP); \
|
||||
MOVL CX, 8(SP); \
|
||||
\
|
||||
MOVOU 0(AX), X0; \
|
||||
MOVOU 16(AX), X1; \
|
||||
@@ -388,7 +391,7 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
MOVOU counter<>(SB), X12; \
|
||||
MOVOU rol16<>(SB), X13; \
|
||||
MOVOU rol8<>(SB), X14; \
|
||||
MOVO 0(BP), X15; \
|
||||
MOVO 0(SP), X15; \
|
||||
\
|
||||
loop: \
|
||||
MOVO X0, X4; \
|
||||
@@ -410,12 +413,14 @@ GLOBL counter<>(SB), (NOPTR+RODATA), $16
|
||||
SUBQ $64, DX; \
|
||||
JNE loop; \
|
||||
\
|
||||
MOVO X15, 0(BP); \
|
||||
MOVQ 0(BP), R9; \
|
||||
MOVO X15, 0(SP); \
|
||||
MOVQ 0(SP), R9; \
|
||||
MOVQ R9, 0(BX); \
|
||||
\
|
||||
MOVOU X0, 0(AX); \
|
||||
MOVOU X1, 16(AX)
|
||||
MOVOU X1, 16(AX); \
|
||||
\
|
||||
MOVQ BP, SP
|
||||
|
||||
// func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte)
|
||||
TEXT ·hashBlocksSSE2(SB), 0, $672-48 // frame = 656 + 16 byte alignment
|
||||
|
||||
@@ -2,8 +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 && !386) || !gc || purego
|
||||
// +build !amd64,!386 !gc purego
|
||||
// +build !amd64,!386 gccgo appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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 blake2s
|
||||
|
||||
+1
-1
@@ -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
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2018 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.11 && gc && !purego
|
||||
// +build go1.11,gc,!purego
|
||||
|
||||
package chacha20
|
||||
|
||||
const bufSize = 256
|
||||
|
||||
//go:noescape
|
||||
func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32)
|
||||
|
||||
func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) {
|
||||
xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter)
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package chacha20 implements the ChaCha20 and XChaCha20 encryption algorithms
|
||||
// as specified in RFC 8439 and draft-irtf-cfrg-xchacha-01.
|
||||
package chacha20
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeySize is the size of the key used by this cipher, in bytes.
|
||||
KeySize = 32
|
||||
|
||||
// NonceSize is the size of the nonce used with the standard variant of this
|
||||
// cipher, in bytes.
|
||||
//
|
||||
// Note that this is too short to be safely generated at random if the same
|
||||
// key is reused more than 2³² times.
|
||||
NonceSize = 12
|
||||
|
||||
// NonceSizeX is the size of the nonce used with the XChaCha20 variant of
|
||||
// this cipher, in bytes.
|
||||
NonceSizeX = 24
|
||||
)
|
||||
|
||||
// Cipher is a stateful instance of ChaCha20 or XChaCha20 using a particular key
|
||||
// and nonce. A *Cipher implements the cipher.Stream interface.
|
||||
type Cipher struct {
|
||||
// The ChaCha20 state is 16 words: 4 constant, 8 of key, 1 of counter
|
||||
// (incremented after each block), and 3 of nonce.
|
||||
key [8]uint32
|
||||
counter uint32
|
||||
nonce [3]uint32
|
||||
|
||||
// The last len bytes of buf are leftover key stream bytes from the previous
|
||||
// XORKeyStream invocation. The size of buf depends on how many blocks are
|
||||
// computed at a time by xorKeyStreamBlocks.
|
||||
buf [bufSize]byte
|
||||
len int
|
||||
|
||||
// overflow is set when the counter overflowed, no more blocks can be
|
||||
// generated, and the next XORKeyStream call should panic.
|
||||
overflow bool
|
||||
|
||||
// The counter-independent results of the first round are cached after they
|
||||
// are computed the first time.
|
||||
precompDone bool
|
||||
p1, p5, p9, p13 uint32
|
||||
p2, p6, p10, p14 uint32
|
||||
p3, p7, p11, p15 uint32
|
||||
}
|
||||
|
||||
var _ cipher.Stream = (*Cipher)(nil)
|
||||
|
||||
// NewUnauthenticatedCipher creates a new ChaCha20 stream cipher with the given
|
||||
// 32 bytes key and a 12 or 24 bytes nonce. If a nonce of 24 bytes is provided,
|
||||
// the XChaCha20 construction will be used. It returns an error if key or nonce
|
||||
// have any other length.
|
||||
//
|
||||
// Note that ChaCha20, like all stream ciphers, is not authenticated and allows
|
||||
// attackers to silently tamper with the plaintext. For this reason, it is more
|
||||
// appropriate as a building block than as a standalone encryption mechanism.
|
||||
// Instead, consider using package golang.org/x/crypto/chacha20poly1305.
|
||||
func NewUnauthenticatedCipher(key, nonce []byte) (*Cipher, error) {
|
||||
// This function is split into a wrapper so that the Cipher allocation will
|
||||
// be inlined, and depending on how the caller uses the return value, won't
|
||||
// escape to the heap.
|
||||
c := &Cipher{}
|
||||
return newUnauthenticatedCipher(c, key, nonce)
|
||||
}
|
||||
|
||||
func newUnauthenticatedCipher(c *Cipher, key, nonce []byte) (*Cipher, error) {
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20: wrong key size")
|
||||
}
|
||||
if len(nonce) == NonceSizeX {
|
||||
// XChaCha20 uses the ChaCha20 core to mix 16 bytes of the nonce into a
|
||||
// derived key, allowing it to operate on a nonce of 24 bytes. See
|
||||
// draft-irtf-cfrg-xchacha-01, Section 2.3.
|
||||
key, _ = HChaCha20(key, nonce[0:16])
|
||||
cNonce := make([]byte, NonceSize)
|
||||
copy(cNonce[4:12], nonce[16:24])
|
||||
nonce = cNonce
|
||||
} else if len(nonce) != NonceSize {
|
||||
return nil, errors.New("chacha20: wrong nonce size")
|
||||
}
|
||||
|
||||
key, nonce = key[:KeySize], nonce[:NonceSize] // bounds check elimination hint
|
||||
c.key = [8]uint32{
|
||||
binary.LittleEndian.Uint32(key[0:4]),
|
||||
binary.LittleEndian.Uint32(key[4:8]),
|
||||
binary.LittleEndian.Uint32(key[8:12]),
|
||||
binary.LittleEndian.Uint32(key[12:16]),
|
||||
binary.LittleEndian.Uint32(key[16:20]),
|
||||
binary.LittleEndian.Uint32(key[20:24]),
|
||||
binary.LittleEndian.Uint32(key[24:28]),
|
||||
binary.LittleEndian.Uint32(key[28:32]),
|
||||
}
|
||||
c.nonce = [3]uint32{
|
||||
binary.LittleEndian.Uint32(nonce[0:4]),
|
||||
binary.LittleEndian.Uint32(nonce[4:8]),
|
||||
binary.LittleEndian.Uint32(nonce[8:12]),
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// The constant first 4 words of the ChaCha20 state.
|
||||
const (
|
||||
j0 uint32 = 0x61707865 // expa
|
||||
j1 uint32 = 0x3320646e // nd 3
|
||||
j2 uint32 = 0x79622d32 // 2-by
|
||||
j3 uint32 = 0x6b206574 // te k
|
||||
)
|
||||
|
||||
const blockSize = 64
|
||||
|
||||
// quarterRound is the core of ChaCha20. It shuffles the bits of 4 state words.
|
||||
// It's executed 4 times for each of the 20 ChaCha20 rounds, operating on all 16
|
||||
// words each round, in columnar or diagonal groups of 4 at a time.
|
||||
func quarterRound(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
|
||||
a += b
|
||||
d ^= a
|
||||
d = bits.RotateLeft32(d, 16)
|
||||
c += d
|
||||
b ^= c
|
||||
b = bits.RotateLeft32(b, 12)
|
||||
a += b
|
||||
d ^= a
|
||||
d = bits.RotateLeft32(d, 8)
|
||||
c += d
|
||||
b ^= c
|
||||
b = bits.RotateLeft32(b, 7)
|
||||
return a, b, c, d
|
||||
}
|
||||
|
||||
// SetCounter sets the Cipher counter. The next invocation of XORKeyStream will
|
||||
// behave as if (64 * counter) bytes had been encrypted so far.
|
||||
//
|
||||
// To prevent accidental counter reuse, SetCounter panics if counter is less
|
||||
// than the current value.
|
||||
//
|
||||
// Note that the execution time of XORKeyStream is not independent of the
|
||||
// counter value.
|
||||
func (s *Cipher) SetCounter(counter uint32) {
|
||||
// Internally, s may buffer multiple blocks, which complicates this
|
||||
// implementation slightly. When checking whether the counter has rolled
|
||||
// back, we must use both s.counter and s.len to determine how many blocks
|
||||
// we have already output.
|
||||
outputCounter := s.counter - uint32(s.len)/blockSize
|
||||
if s.overflow || counter < outputCounter {
|
||||
panic("chacha20: SetCounter attempted to rollback counter")
|
||||
}
|
||||
|
||||
// In the general case, we set the new counter value and reset s.len to 0,
|
||||
// causing the next call to XORKeyStream to refill the buffer. However, if
|
||||
// we're advancing within the existing buffer, we can save work by simply
|
||||
// setting s.len.
|
||||
if counter < s.counter {
|
||||
s.len = int(s.counter-counter) * blockSize
|
||||
} else {
|
||||
s.counter = counter
|
||||
s.len = 0
|
||||
}
|
||||
}
|
||||
|
||||
// XORKeyStream XORs each byte in the given slice with a byte from the
|
||||
// cipher's key stream. Dst and src must overlap entirely or not at all.
|
||||
//
|
||||
// If len(dst) < len(src), XORKeyStream will panic. It is acceptable
|
||||
// to pass a dst bigger than src, and in that case, XORKeyStream will
|
||||
// only update dst[:len(src)] and will not touch the rest of dst.
|
||||
//
|
||||
// Multiple calls to XORKeyStream behave as if the concatenation of
|
||||
// the src buffers was passed in a single run. That is, Cipher
|
||||
// maintains state and does not reset at each XORKeyStream call.
|
||||
func (s *Cipher) XORKeyStream(dst, src []byte) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("chacha20: output smaller than input")
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
if alias.InexactOverlap(dst, src) {
|
||||
panic("chacha20: invalid buffer overlap")
|
||||
}
|
||||
|
||||
// First, drain any remaining key stream from a previous XORKeyStream.
|
||||
if s.len != 0 {
|
||||
keyStream := s.buf[bufSize-s.len:]
|
||||
if len(src) < len(keyStream) {
|
||||
keyStream = keyStream[:len(src)]
|
||||
}
|
||||
_ = src[len(keyStream)-1] // bounds check elimination hint
|
||||
for i, b := range keyStream {
|
||||
dst[i] = src[i] ^ b
|
||||
}
|
||||
s.len -= len(keyStream)
|
||||
dst, src = dst[len(keyStream):], src[len(keyStream):]
|
||||
}
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If we'd need to let the counter overflow and keep generating output,
|
||||
// panic immediately. If instead we'd only reach the last block, remember
|
||||
// not to generate any more output after the buffer is drained.
|
||||
numBlocks := (uint64(len(src)) + blockSize - 1) / blockSize
|
||||
if s.overflow || uint64(s.counter)+numBlocks > 1<<32 {
|
||||
panic("chacha20: counter overflow")
|
||||
} else if uint64(s.counter)+numBlocks == 1<<32 {
|
||||
s.overflow = true
|
||||
}
|
||||
|
||||
// xorKeyStreamBlocks implementations expect input lengths that are a
|
||||
// multiple of bufSize. Platform-specific ones process multiple blocks at a
|
||||
// time, so have bufSizes that are a multiple of blockSize.
|
||||
|
||||
full := len(src) - len(src)%bufSize
|
||||
if full > 0 {
|
||||
s.xorKeyStreamBlocks(dst[:full], src[:full])
|
||||
}
|
||||
dst, src = dst[full:], src[full:]
|
||||
|
||||
// If using a multi-block xorKeyStreamBlocks would overflow, use the generic
|
||||
// one that does one block at a time.
|
||||
const blocksPerBuf = bufSize / blockSize
|
||||
if uint64(s.counter)+blocksPerBuf > 1<<32 {
|
||||
s.buf = [bufSize]byte{}
|
||||
numBlocks := (len(src) + blockSize - 1) / blockSize
|
||||
buf := s.buf[bufSize-numBlocks*blockSize:]
|
||||
copy(buf, src)
|
||||
s.xorKeyStreamBlocksGeneric(buf, buf)
|
||||
s.len = len(buf) - copy(dst, buf)
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a partial (multi-)block, pad it for xorKeyStreamBlocks, and
|
||||
// keep the leftover keystream for the next XORKeyStream invocation.
|
||||
if len(src) > 0 {
|
||||
s.buf = [bufSize]byte{}
|
||||
copy(s.buf[:], src)
|
||||
s.xorKeyStreamBlocks(s.buf[:], s.buf[:])
|
||||
s.len = bufSize - copy(dst, s.buf[:])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) {
|
||||
if len(dst) != len(src) || len(dst)%blockSize != 0 {
|
||||
panic("chacha20: internal error: wrong dst and/or src length")
|
||||
}
|
||||
|
||||
// To generate each block of key stream, the initial cipher state
|
||||
// (represented below) is passed through 20 rounds of shuffling,
|
||||
// alternatively applying quarterRounds by columns (like 1, 5, 9, 13)
|
||||
// or by diagonals (like 1, 6, 11, 12).
|
||||
//
|
||||
// 0:cccccccc 1:cccccccc 2:cccccccc 3:cccccccc
|
||||
// 4:kkkkkkkk 5:kkkkkkkk 6:kkkkkkkk 7:kkkkkkkk
|
||||
// 8:kkkkkkkk 9:kkkkkkkk 10:kkkkkkkk 11:kkkkkkkk
|
||||
// 12:bbbbbbbb 13:nnnnnnnn 14:nnnnnnnn 15:nnnnnnnn
|
||||
//
|
||||
// c=constant k=key b=blockcount n=nonce
|
||||
var (
|
||||
c0, c1, c2, c3 = j0, j1, j2, j3
|
||||
c4, c5, c6, c7 = s.key[0], s.key[1], s.key[2], s.key[3]
|
||||
c8, c9, c10, c11 = s.key[4], s.key[5], s.key[6], s.key[7]
|
||||
_, c13, c14, c15 = s.counter, s.nonce[0], s.nonce[1], s.nonce[2]
|
||||
)
|
||||
|
||||
// Three quarters of the first round don't depend on the counter, so we can
|
||||
// calculate them here, and reuse them for multiple blocks in the loop, and
|
||||
// for future XORKeyStream invocations.
|
||||
if !s.precompDone {
|
||||
s.p1, s.p5, s.p9, s.p13 = quarterRound(c1, c5, c9, c13)
|
||||
s.p2, s.p6, s.p10, s.p14 = quarterRound(c2, c6, c10, c14)
|
||||
s.p3, s.p7, s.p11, s.p15 = quarterRound(c3, c7, c11, c15)
|
||||
s.precompDone = true
|
||||
}
|
||||
|
||||
// A condition of len(src) > 0 would be sufficient, but this also
|
||||
// acts as a bounds check elimination hint.
|
||||
for len(src) >= 64 && len(dst) >= 64 {
|
||||
// The remainder of the first column round.
|
||||
fcr0, fcr4, fcr8, fcr12 := quarterRound(c0, c4, c8, s.counter)
|
||||
|
||||
// The second diagonal round.
|
||||
x0, x5, x10, x15 := quarterRound(fcr0, s.p5, s.p10, s.p15)
|
||||
x1, x6, x11, x12 := quarterRound(s.p1, s.p6, s.p11, fcr12)
|
||||
x2, x7, x8, x13 := quarterRound(s.p2, s.p7, fcr8, s.p13)
|
||||
x3, x4, x9, x14 := quarterRound(s.p3, fcr4, s.p9, s.p14)
|
||||
|
||||
// The remaining 18 rounds.
|
||||
for i := 0; i < 9; i++ {
|
||||
// Column round.
|
||||
x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12)
|
||||
x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13)
|
||||
x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14)
|
||||
x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15)
|
||||
|
||||
// Diagonal round.
|
||||
x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15)
|
||||
x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12)
|
||||
x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13)
|
||||
x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
// Add back the initial state to generate the key stream, then
|
||||
// XOR the key stream with the source and write out the result.
|
||||
addXor(dst[0:4], src[0:4], x0, c0)
|
||||
addXor(dst[4:8], src[4:8], x1, c1)
|
||||
addXor(dst[8:12], src[8:12], x2, c2)
|
||||
addXor(dst[12:16], src[12:16], x3, c3)
|
||||
addXor(dst[16:20], src[16:20], x4, c4)
|
||||
addXor(dst[20:24], src[20:24], x5, c5)
|
||||
addXor(dst[24:28], src[24:28], x6, c6)
|
||||
addXor(dst[28:32], src[28:32], x7, c7)
|
||||
addXor(dst[32:36], src[32:36], x8, c8)
|
||||
addXor(dst[36:40], src[36:40], x9, c9)
|
||||
addXor(dst[40:44], src[40:44], x10, c10)
|
||||
addXor(dst[44:48], src[44:48], x11, c11)
|
||||
addXor(dst[48:52], src[48:52], x12, s.counter)
|
||||
addXor(dst[52:56], src[52:56], x13, c13)
|
||||
addXor(dst[56:60], src[56:60], x14, c14)
|
||||
addXor(dst[60:64], src[60:64], x15, c15)
|
||||
|
||||
s.counter += 1
|
||||
|
||||
src, dst = src[blockSize:], dst[blockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
// HChaCha20 uses the ChaCha20 core to generate a derived key from a 32 bytes
|
||||
// key and a 16 bytes nonce. It returns an error if key or nonce have any other
|
||||
// length. It is used as part of the XChaCha20 construction.
|
||||
func HChaCha20(key, nonce []byte) ([]byte, error) {
|
||||
// This function is split into a wrapper so that the slice allocation will
|
||||
// be inlined, and depending on how the caller uses the return value, won't
|
||||
// escape to the heap.
|
||||
out := make([]byte, 32)
|
||||
return hChaCha20(out, key, nonce)
|
||||
}
|
||||
|
||||
func hChaCha20(out, key, nonce []byte) ([]byte, error) {
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20: wrong HChaCha20 key size")
|
||||
}
|
||||
if len(nonce) != 16 {
|
||||
return nil, errors.New("chacha20: wrong HChaCha20 nonce size")
|
||||
}
|
||||
|
||||
x0, x1, x2, x3 := j0, j1, j2, j3
|
||||
x4 := binary.LittleEndian.Uint32(key[0:4])
|
||||
x5 := binary.LittleEndian.Uint32(key[4:8])
|
||||
x6 := binary.LittleEndian.Uint32(key[8:12])
|
||||
x7 := binary.LittleEndian.Uint32(key[12:16])
|
||||
x8 := binary.LittleEndian.Uint32(key[16:20])
|
||||
x9 := binary.LittleEndian.Uint32(key[20:24])
|
||||
x10 := binary.LittleEndian.Uint32(key[24:28])
|
||||
x11 := binary.LittleEndian.Uint32(key[28:32])
|
||||
x12 := binary.LittleEndian.Uint32(nonce[0:4])
|
||||
x13 := binary.LittleEndian.Uint32(nonce[4:8])
|
||||
x14 := binary.LittleEndian.Uint32(nonce[8:12])
|
||||
x15 := binary.LittleEndian.Uint32(nonce[12:16])
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Diagonal round.
|
||||
x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12)
|
||||
x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13)
|
||||
x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14)
|
||||
x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15)
|
||||
|
||||
// Column round.
|
||||
x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15)
|
||||
x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12)
|
||||
x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13)
|
||||
x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
_ = out[31] // bounds check elimination hint
|
||||
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||
binary.LittleEndian.PutUint32(out[16:20], x12)
|
||||
binary.LittleEndian.PutUint32(out[20:24], x13)
|
||||
binary.LittleEndian.PutUint32(out[24:28], x14)
|
||||
binary.LittleEndian.PutUint32(out[28:32], x15)
|
||||
return out, nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright 2018 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 && !s390x && !ppc64le) || (arm64 && !go1.11) || !gc || purego
|
||||
// +build !arm64,!s390x,!ppc64le arm64,!go1.11 !gc purego
|
||||
|
||||
package chacha20
|
||||
|
||||
const bufSize = blockSize
|
||||
|
||||
func (s *Cipher) xorKeyStreamBlocks(dst, src []byte) {
|
||||
s.xorKeyStreamBlocksGeneric(dst, src)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
// +build gc,!purego
|
||||
|
||||
package chacha20
|
||||
|
||||
const bufSize = 256
|
||||
|
||||
//go:noescape
|
||||
func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||
|
||||
func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) {
|
||||
chaCha20_ctr32_vsx(&dst[0], &src[0], len(src), &c.key, &c.counter)
|
||||
}
|
||||
@@ -1,450 +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.
|
||||
|
||||
// Based on CRYPTOGAMS code with the following comment:
|
||||
// # ====================================================================
|
||||
// # Written by Andy Polyakov <appro@openssl.org> for the OpenSSL
|
||||
// # project. The module is, however, dual licensed under OpenSSL and
|
||||
// # CRYPTOGAMS licenses depending on where you obtain it. For further
|
||||
// # details see http://www.openssl.org/~appro/cryptogams/.
|
||||
// # ====================================================================
|
||||
|
||||
// Code for the perl script that generates the ppc64 assembler
|
||||
// can be found in the cryptogams repository at the link below. It is based on
|
||||
// the original from openssl.
|
||||
|
||||
// https://github.com/dot-asm/cryptogams/commit/a60f5b50ed908e91
|
||||
|
||||
// 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"
|
||||
|
||||
#define OUT R3
|
||||
#define INP R4
|
||||
#define LEN R5
|
||||
#define KEY R6
|
||||
#define CNT R7
|
||||
#define TMP R15
|
||||
|
||||
#define CONSTBASE R16
|
||||
#define BLOCKS R17
|
||||
|
||||
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
|
||||
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
|
||||
DATA consts<>+0x10(SB)/8, $0x0000000000000001
|
||||
DATA consts<>+0x18(SB)/8, $0x0000000000000000
|
||||
DATA consts<>+0x20(SB)/8, $0x0000000000000004
|
||||
DATA consts<>+0x28(SB)/8, $0x0000000000000000
|
||||
DATA consts<>+0x30(SB)/8, $0x0a0b08090e0f0c0d
|
||||
DATA consts<>+0x38(SB)/8, $0x0203000106070405
|
||||
DATA consts<>+0x40(SB)/8, $0x090a0b080d0e0f0c
|
||||
DATA consts<>+0x48(SB)/8, $0x0102030005060704
|
||||
DATA consts<>+0x50(SB)/8, $0x6170786561707865
|
||||
DATA consts<>+0x58(SB)/8, $0x6170786561707865
|
||||
DATA consts<>+0x60(SB)/8, $0x3320646e3320646e
|
||||
DATA consts<>+0x68(SB)/8, $0x3320646e3320646e
|
||||
DATA consts<>+0x70(SB)/8, $0x79622d3279622d32
|
||||
DATA consts<>+0x78(SB)/8, $0x79622d3279622d32
|
||||
DATA consts<>+0x80(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
|
||||
DATA consts<>+0x90(SB)/8, $0x0000000100000000
|
||||
DATA consts<>+0x98(SB)/8, $0x0000000300000002
|
||||
GLOBL consts<>(SB), RODATA, $0xa0
|
||||
|
||||
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
MOVD out+0(FP), OUT
|
||||
MOVD inp+8(FP), INP
|
||||
MOVD len+16(FP), LEN
|
||||
MOVD key+24(FP), KEY
|
||||
MOVD counter+32(FP), CNT
|
||||
|
||||
// Addressing for constants
|
||||
MOVD $consts<>+0x00(SB), CONSTBASE
|
||||
MOVD $16, R8
|
||||
MOVD $32, R9
|
||||
MOVD $48, R10
|
||||
MOVD $64, R11
|
||||
SRD $6, LEN, BLOCKS
|
||||
// V16
|
||||
LXVW4X (CONSTBASE)(R0), VS48
|
||||
ADD $80,CONSTBASE
|
||||
|
||||
// Load key into V17,V18
|
||||
LXVW4X (KEY)(R0), VS49
|
||||
LXVW4X (KEY)(R8), VS50
|
||||
|
||||
// Load CNT, NONCE into V19
|
||||
LXVW4X (CNT)(R0), VS51
|
||||
|
||||
// Clear V27
|
||||
VXOR V27, V27, V27
|
||||
|
||||
// V28
|
||||
LXVW4X (CONSTBASE)(R11), VS60
|
||||
|
||||
// splat slot from V19 -> V26
|
||||
VSPLTW $0, V19, V26
|
||||
|
||||
VSLDOI $4, V19, V27, V19
|
||||
VSLDOI $12, V27, V19, V19
|
||||
|
||||
VADDUWM V26, V28, V26
|
||||
|
||||
MOVD $10, R14
|
||||
MOVD R14, CTR
|
||||
|
||||
loop_outer_vsx:
|
||||
// V0, V1, V2, V3
|
||||
LXVW4X (R0)(CONSTBASE), VS32
|
||||
LXVW4X (R8)(CONSTBASE), VS33
|
||||
LXVW4X (R9)(CONSTBASE), VS34
|
||||
LXVW4X (R10)(CONSTBASE), VS35
|
||||
|
||||
// splat values from V17, V18 into V4-V11
|
||||
VSPLTW $0, V17, V4
|
||||
VSPLTW $1, V17, V5
|
||||
VSPLTW $2, V17, V6
|
||||
VSPLTW $3, V17, V7
|
||||
VSPLTW $0, V18, V8
|
||||
VSPLTW $1, V18, V9
|
||||
VSPLTW $2, V18, V10
|
||||
VSPLTW $3, V18, V11
|
||||
|
||||
// VOR
|
||||
VOR V26, V26, V12
|
||||
|
||||
// splat values from V19 -> V13, V14, V15
|
||||
VSPLTW $1, V19, V13
|
||||
VSPLTW $2, V19, V14
|
||||
VSPLTW $3, V19, V15
|
||||
|
||||
// splat const values
|
||||
VSPLTISW $-16, V27
|
||||
VSPLTISW $12, V28
|
||||
VSPLTISW $8, V29
|
||||
VSPLTISW $7, V30
|
||||
|
||||
loop_vsx:
|
||||
VADDUWM V0, V4, V0
|
||||
VADDUWM V1, V5, V1
|
||||
VADDUWM V2, V6, V2
|
||||
VADDUWM V3, V7, V3
|
||||
|
||||
VXOR V12, V0, V12
|
||||
VXOR V13, V1, V13
|
||||
VXOR V14, V2, V14
|
||||
VXOR V15, V3, V15
|
||||
|
||||
VRLW V12, V27, V12
|
||||
VRLW V13, V27, V13
|
||||
VRLW V14, V27, V14
|
||||
VRLW V15, V27, V15
|
||||
|
||||
VADDUWM V8, V12, V8
|
||||
VADDUWM V9, V13, V9
|
||||
VADDUWM V10, V14, V10
|
||||
VADDUWM V11, V15, V11
|
||||
|
||||
VXOR V4, V8, V4
|
||||
VXOR V5, V9, V5
|
||||
VXOR V6, V10, V6
|
||||
VXOR V7, V11, V7
|
||||
|
||||
VRLW V4, V28, V4
|
||||
VRLW V5, V28, V5
|
||||
VRLW V6, V28, V6
|
||||
VRLW V7, V28, V7
|
||||
|
||||
VADDUWM V0, V4, V0
|
||||
VADDUWM V1, V5, V1
|
||||
VADDUWM V2, V6, V2
|
||||
VADDUWM V3, V7, V3
|
||||
|
||||
VXOR V12, V0, V12
|
||||
VXOR V13, V1, V13
|
||||
VXOR V14, V2, V14
|
||||
VXOR V15, V3, V15
|
||||
|
||||
VRLW V12, V29, V12
|
||||
VRLW V13, V29, V13
|
||||
VRLW V14, V29, V14
|
||||
VRLW V15, V29, V15
|
||||
|
||||
VADDUWM V8, V12, V8
|
||||
VADDUWM V9, V13, V9
|
||||
VADDUWM V10, V14, V10
|
||||
VADDUWM V11, V15, V11
|
||||
|
||||
VXOR V4, V8, V4
|
||||
VXOR V5, V9, V5
|
||||
VXOR V6, V10, V6
|
||||
VXOR V7, V11, V7
|
||||
|
||||
VRLW V4, V30, V4
|
||||
VRLW V5, V30, V5
|
||||
VRLW V6, V30, V6
|
||||
VRLW V7, V30, V7
|
||||
|
||||
VADDUWM V0, V5, V0
|
||||
VADDUWM V1, V6, V1
|
||||
VADDUWM V2, V7, V2
|
||||
VADDUWM V3, V4, V3
|
||||
|
||||
VXOR V15, V0, V15
|
||||
VXOR V12, V1, V12
|
||||
VXOR V13, V2, V13
|
||||
VXOR V14, V3, V14
|
||||
|
||||
VRLW V15, V27, V15
|
||||
VRLW V12, V27, V12
|
||||
VRLW V13, V27, V13
|
||||
VRLW V14, V27, V14
|
||||
|
||||
VADDUWM V10, V15, V10
|
||||
VADDUWM V11, V12, V11
|
||||
VADDUWM V8, V13, V8
|
||||
VADDUWM V9, V14, V9
|
||||
|
||||
VXOR V5, V10, V5
|
||||
VXOR V6, V11, V6
|
||||
VXOR V7, V8, V7
|
||||
VXOR V4, V9, V4
|
||||
|
||||
VRLW V5, V28, V5
|
||||
VRLW V6, V28, V6
|
||||
VRLW V7, V28, V7
|
||||
VRLW V4, V28, V4
|
||||
|
||||
VADDUWM V0, V5, V0
|
||||
VADDUWM V1, V6, V1
|
||||
VADDUWM V2, V7, V2
|
||||
VADDUWM V3, V4, V3
|
||||
|
||||
VXOR V15, V0, V15
|
||||
VXOR V12, V1, V12
|
||||
VXOR V13, V2, V13
|
||||
VXOR V14, V3, V14
|
||||
|
||||
VRLW V15, V29, V15
|
||||
VRLW V12, V29, V12
|
||||
VRLW V13, V29, V13
|
||||
VRLW V14, V29, V14
|
||||
|
||||
VADDUWM V10, V15, V10
|
||||
VADDUWM V11, V12, V11
|
||||
VADDUWM V8, V13, V8
|
||||
VADDUWM V9, V14, V9
|
||||
|
||||
VXOR V5, V10, V5
|
||||
VXOR V6, V11, V6
|
||||
VXOR V7, V8, V7
|
||||
VXOR V4, V9, V4
|
||||
|
||||
VRLW V5, V30, V5
|
||||
VRLW V6, V30, V6
|
||||
VRLW V7, V30, V7
|
||||
VRLW V4, V30, V4
|
||||
BC 16, LT, loop_vsx
|
||||
|
||||
VADDUWM V12, V26, V12
|
||||
|
||||
WORD $0x13600F8C // VMRGEW V0, V1, V27
|
||||
WORD $0x13821F8C // VMRGEW V2, V3, V28
|
||||
|
||||
WORD $0x10000E8C // VMRGOW V0, V1, V0
|
||||
WORD $0x10421E8C // VMRGOW V2, V3, V2
|
||||
|
||||
WORD $0x13A42F8C // VMRGEW V4, V5, V29
|
||||
WORD $0x13C63F8C // VMRGEW V6, V7, V30
|
||||
|
||||
XXPERMDI VS32, VS34, $0, VS33
|
||||
XXPERMDI VS32, VS34, $3, VS35
|
||||
XXPERMDI VS59, VS60, $0, VS32
|
||||
XXPERMDI VS59, VS60, $3, VS34
|
||||
|
||||
WORD $0x10842E8C // VMRGOW V4, V5, V4
|
||||
WORD $0x10C63E8C // VMRGOW V6, V7, V6
|
||||
|
||||
WORD $0x13684F8C // VMRGEW V8, V9, V27
|
||||
WORD $0x138A5F8C // VMRGEW V10, V11, V28
|
||||
|
||||
XXPERMDI VS36, VS38, $0, VS37
|
||||
XXPERMDI VS36, VS38, $3, VS39
|
||||
XXPERMDI VS61, VS62, $0, VS36
|
||||
XXPERMDI VS61, VS62, $3, VS38
|
||||
|
||||
WORD $0x11084E8C // VMRGOW V8, V9, V8
|
||||
WORD $0x114A5E8C // VMRGOW V10, V11, V10
|
||||
|
||||
WORD $0x13AC6F8C // VMRGEW V12, V13, V29
|
||||
WORD $0x13CE7F8C // VMRGEW V14, V15, V30
|
||||
|
||||
XXPERMDI VS40, VS42, $0, VS41
|
||||
XXPERMDI VS40, VS42, $3, VS43
|
||||
XXPERMDI VS59, VS60, $0, VS40
|
||||
XXPERMDI VS59, VS60, $3, VS42
|
||||
|
||||
WORD $0x118C6E8C // VMRGOW V12, V13, V12
|
||||
WORD $0x11CE7E8C // VMRGOW V14, V15, V14
|
||||
|
||||
VSPLTISW $4, V27
|
||||
VADDUWM V26, V27, V26
|
||||
|
||||
XXPERMDI VS44, VS46, $0, VS45
|
||||
XXPERMDI VS44, VS46, $3, VS47
|
||||
XXPERMDI VS61, VS62, $0, VS44
|
||||
XXPERMDI VS61, VS62, $3, VS46
|
||||
|
||||
VADDUWM V0, V16, V0
|
||||
VADDUWM V4, V17, V4
|
||||
VADDUWM V8, V18, V8
|
||||
VADDUWM V12, V19, V12
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
// Bottom of loop
|
||||
LXVW4X (INP)(R0), VS59
|
||||
LXVW4X (INP)(R8), VS60
|
||||
LXVW4X (INP)(R9), VS61
|
||||
LXVW4X (INP)(R10), VS62
|
||||
|
||||
VXOR V27, V0, V27
|
||||
VXOR V28, V4, V28
|
||||
VXOR V29, V8, V29
|
||||
VXOR V30, V12, V30
|
||||
|
||||
STXVW4X VS59, (OUT)(R0)
|
||||
STXVW4X VS60, (OUT)(R8)
|
||||
ADD $64, INP
|
||||
STXVW4X VS61, (OUT)(R9)
|
||||
ADD $-64, LEN
|
||||
STXVW4X VS62, (OUT)(R10)
|
||||
ADD $64, OUT
|
||||
BEQ done_vsx
|
||||
|
||||
VADDUWM V1, V16, V0
|
||||
VADDUWM V5, V17, V4
|
||||
VADDUWM V9, V18, V8
|
||||
VADDUWM V13, V19, V12
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
LXVW4X (INP)(R0), VS59
|
||||
LXVW4X (INP)(R8), VS60
|
||||
LXVW4X (INP)(R9), VS61
|
||||
LXVW4X (INP)(R10), VS62
|
||||
VXOR V27, V0, V27
|
||||
|
||||
VXOR V28, V4, V28
|
||||
VXOR V29, V8, V29
|
||||
VXOR V30, V12, V30
|
||||
|
||||
STXVW4X VS59, (OUT)(R0)
|
||||
STXVW4X VS60, (OUT)(R8)
|
||||
ADD $64, INP
|
||||
STXVW4X VS61, (OUT)(R9)
|
||||
ADD $-64, LEN
|
||||
STXVW4X VS62, (OUT)(V10)
|
||||
ADD $64, OUT
|
||||
BEQ done_vsx
|
||||
|
||||
VADDUWM V2, V16, V0
|
||||
VADDUWM V6, V17, V4
|
||||
VADDUWM V10, V18, V8
|
||||
VADDUWM V14, V19, V12
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
LXVW4X (INP)(R0), VS59
|
||||
LXVW4X (INP)(R8), VS60
|
||||
LXVW4X (INP)(R9), VS61
|
||||
LXVW4X (INP)(R10), VS62
|
||||
|
||||
VXOR V27, V0, V27
|
||||
VXOR V28, V4, V28
|
||||
VXOR V29, V8, V29
|
||||
VXOR V30, V12, V30
|
||||
|
||||
STXVW4X VS59, (OUT)(R0)
|
||||
STXVW4X VS60, (OUT)(R8)
|
||||
ADD $64, INP
|
||||
STXVW4X VS61, (OUT)(R9)
|
||||
ADD $-64, LEN
|
||||
STXVW4X VS62, (OUT)(R10)
|
||||
ADD $64, OUT
|
||||
BEQ done_vsx
|
||||
|
||||
VADDUWM V3, V16, V0
|
||||
VADDUWM V7, V17, V4
|
||||
VADDUWM V11, V18, V8
|
||||
VADDUWM V15, V19, V12
|
||||
|
||||
CMPU LEN, $64
|
||||
BLT tail_vsx
|
||||
|
||||
LXVW4X (INP)(R0), VS59
|
||||
LXVW4X (INP)(R8), VS60
|
||||
LXVW4X (INP)(R9), VS61
|
||||
LXVW4X (INP)(R10), VS62
|
||||
|
||||
VXOR V27, V0, V27
|
||||
VXOR V28, V4, V28
|
||||
VXOR V29, V8, V29
|
||||
VXOR V30, V12, V30
|
||||
|
||||
STXVW4X VS59, (OUT)(R0)
|
||||
STXVW4X VS60, (OUT)(R8)
|
||||
ADD $64, INP
|
||||
STXVW4X VS61, (OUT)(R9)
|
||||
ADD $-64, LEN
|
||||
STXVW4X VS62, (OUT)(R10)
|
||||
ADD $64, OUT
|
||||
|
||||
MOVD $10, R14
|
||||
MOVD R14, CTR
|
||||
BNE loop_outer_vsx
|
||||
|
||||
done_vsx:
|
||||
// Increment counter by number of 64 byte blocks
|
||||
MOVD (CNT), R14
|
||||
ADD BLOCKS, R14
|
||||
MOVD R14, (CNT)
|
||||
RET
|
||||
|
||||
tail_vsx:
|
||||
ADD $32, R1, R11
|
||||
MOVD LEN, CTR
|
||||
|
||||
// Save values on stack to copy from
|
||||
STXVW4X VS32, (R11)(R0)
|
||||
STXVW4X VS36, (R11)(R8)
|
||||
STXVW4X VS40, (R11)(R9)
|
||||
STXVW4X VS44, (R11)(R10)
|
||||
ADD $-1, R11, R12
|
||||
ADD $-1, INP
|
||||
ADD $-1, OUT
|
||||
|
||||
looptail_vsx:
|
||||
// Copying the result to OUT
|
||||
// in bytes.
|
||||
MOVBZU 1(R12), KEY
|
||||
MOVBZU 1(INP), TMP
|
||||
XOR KEY, TMP, KEY
|
||||
MOVBU KEY, 1(OUT)
|
||||
BC 16, LT, looptail_vsx
|
||||
|
||||
// Clear the stack values
|
||||
STXVW4X VS48, (R11)(R0)
|
||||
STXVW4X VS48, (R11)(R8)
|
||||
STXVW4X VS48, (R11)(R9)
|
||||
STXVW4X VS48, (R11)(R10)
|
||||
BR done_vsx
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gc && !purego
|
||||
// +build gc,!purego
|
||||
|
||||
package chacha20
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
var haveAsm = cpu.S390X.HasVX
|
||||
|
||||
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)
|
||||
|
||||
func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) {
|
||||
if cpu.S390X.HasVX {
|
||||
xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter)
|
||||
} else {
|
||||
c.xorKeyStreamBlocksGeneric(dst, src)
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chacha20
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func _() {
|
||||
// Assert that bufSize is a multiple of blockSize.
|
||||
var b [1]byte
|
||||
_ = b[bufSize%blockSize]
|
||||
}
|
||||
|
||||
func hexDecode(s string) []byte {
|
||||
ss, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot decode input %#v: %v", s, err))
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// Run the test cases with the input and output in different buffers.
|
||||
func TestNoOverlap(t *testing.T) {
|
||||
for _, c := range testVectors {
|
||||
s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
|
||||
input := hexDecode(c.input)
|
||||
output := make([]byte, len(input))
|
||||
s.XORKeyStream(output, input)
|
||||
got := hex.EncodeToString(output)
|
||||
if got != c.output {
|
||||
t.Errorf("length=%v: got %#v, want %#v", len(input), got, c.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test cases with the input and output overlapping entirely.
|
||||
func TestOverlap(t *testing.T) {
|
||||
for _, c := range testVectors {
|
||||
s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
|
||||
data := hexDecode(c.input)
|
||||
s.XORKeyStream(data, data)
|
||||
got := hex.EncodeToString(data)
|
||||
if got != c.output {
|
||||
t.Errorf("length=%v: got %#v, want %#v", len(data), got, c.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test cases with various source and destination offsets.
|
||||
func TestUnaligned(t *testing.T) {
|
||||
const max = 8 // max offset (+1) to test
|
||||
for _, c := range testVectors {
|
||||
data := hexDecode(c.input)
|
||||
input := make([]byte, len(data)+max)
|
||||
output := make([]byte, len(data)+max)
|
||||
for i := 0; i < max; i++ { // input offsets
|
||||
for j := 0; j < max; j++ { // output offsets
|
||||
s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
|
||||
|
||||
input := input[i : i+len(data)]
|
||||
output := output[j : j+len(data)]
|
||||
|
||||
copy(input, data)
|
||||
s.XORKeyStream(output, input)
|
||||
got := hex.EncodeToString(output)
|
||||
if got != c.output {
|
||||
t.Errorf("length=%v: got %#v, want %#v", len(data), got, c.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test cases by calling XORKeyStream multiple times.
|
||||
func TestStep(t *testing.T) {
|
||||
// wide range of step sizes to try and hit edge cases
|
||||
steps := [...]int{1, 3, 4, 7, 8, 17, 24, 30, 64, 256}
|
||||
rnd := rand.New(rand.NewSource(123))
|
||||
for _, c := range testVectors {
|
||||
s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
|
||||
input := hexDecode(c.input)
|
||||
output := make([]byte, len(input))
|
||||
|
||||
// step through the buffers
|
||||
i, step := 0, steps[rnd.Intn(len(steps))]
|
||||
for i+step < len(input) {
|
||||
s.XORKeyStream(output[i:i+step], input[i:i+step])
|
||||
if i+step < len(input) && output[i+step] != 0 {
|
||||
t.Errorf("length=%v, i=%v, step=%v: output overwritten", len(input), i, step)
|
||||
}
|
||||
i += step
|
||||
step = steps[rnd.Intn(len(steps))]
|
||||
}
|
||||
// finish the encryption
|
||||
s.XORKeyStream(output[i:], input[i:])
|
||||
// ensure we tolerate a call with an empty input
|
||||
s.XORKeyStream(output[len(output):], input[len(input):])
|
||||
|
||||
got := hex.EncodeToString(output)
|
||||
if got != c.output {
|
||||
t.Errorf("length=%v: got %#v, want %#v", len(input), got, c.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCounter(t *testing.T) {
|
||||
newCipher := func() *Cipher {
|
||||
s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
return s
|
||||
}
|
||||
s := newCipher()
|
||||
src := bytes.Repeat([]byte("test"), 32) // two 64-byte blocks
|
||||
dst1 := make([]byte, len(src))
|
||||
s.XORKeyStream(dst1, src)
|
||||
// advance counter to 1 and xor second block
|
||||
s = newCipher()
|
||||
s.SetCounter(1)
|
||||
dst2 := make([]byte, len(src))
|
||||
s.XORKeyStream(dst2[64:], src[64:])
|
||||
if !bytes.Equal(dst1[64:], dst2[64:]) {
|
||||
t.Error("failed to produce identical output using SetCounter")
|
||||
}
|
||||
|
||||
// test again with unaligned blocks; SetCounter should reset the buffer
|
||||
s = newCipher()
|
||||
s.XORKeyStream(dst1[:70], src[:70])
|
||||
s = newCipher()
|
||||
s.XORKeyStream([]byte{0}, []byte{0})
|
||||
s.SetCounter(1)
|
||||
s.XORKeyStream(dst2[64:70], src[64:70])
|
||||
if !bytes.Equal(dst1[64:70], dst2[64:70]) {
|
||||
t.Error("SetCounter did not reset buffer")
|
||||
}
|
||||
|
||||
// advancing to a lower counter value should cause a panic
|
||||
panics := func(fn func()) (p bool) {
|
||||
defer func() { p = recover() != nil }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
if !panics(func() { s.SetCounter(0) }) {
|
||||
t.Error("counter decreasing should trigger a panic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastBlock(t *testing.T) {
|
||||
panics := func(fn func()) (p bool) {
|
||||
defer func() { p = recover() != nil }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
|
||||
checkLastBlock := func(b []byte) {
|
||||
t.Helper()
|
||||
// Hardcoded result to check all implementations generate the same output.
|
||||
lastBlock := "ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d" +
|
||||
"92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d"
|
||||
if got := hex.EncodeToString(b); got != lastBlock {
|
||||
t.Errorf("wrong output for the last block, got %q, want %q", got, lastBlock)
|
||||
}
|
||||
}
|
||||
|
||||
// setting the counter to 0xffffffff and crypting multiple blocks should
|
||||
// trigger a panic
|
||||
s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff)
|
||||
blocks := make([]byte, blockSize*2)
|
||||
if !panics(func() { s.XORKeyStream(blocks, blocks) }) {
|
||||
t.Error("crypting multiple blocks should trigger a panic")
|
||||
}
|
||||
|
||||
// setting the counter to 0xffffffff - 1 and crypting two blocks should not
|
||||
// trigger a panic
|
||||
s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff - 1)
|
||||
if panics(func() { s.XORKeyStream(blocks, blocks) }) {
|
||||
t.Error("crypting the last blocks should not trigger a panic")
|
||||
}
|
||||
checkLastBlock(blocks[blockSize:])
|
||||
// once all the keystream is spent, setting the counter should panic
|
||||
if !panics(func() { s.SetCounter(0xffffffff) }) {
|
||||
t.Error("setting the counter after overflow should trigger a panic")
|
||||
}
|
||||
// crypting a subsequent block *should* panic
|
||||
block := make([]byte, blockSize)
|
||||
if !panics(func() { s.XORKeyStream(block, block) }) {
|
||||
t.Error("crypting after overflow should trigger a panic")
|
||||
}
|
||||
|
||||
// if we crypt less than a full block, we should be able to crypt the rest
|
||||
// in a subsequent call without panicking
|
||||
s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
|
||||
s.SetCounter(0xffffffff)
|
||||
if panics(func() { s.XORKeyStream(block[:7], block[:7]) }) {
|
||||
t.Error("crypting part of the last block should not trigger a panic")
|
||||
}
|
||||
if panics(func() { s.XORKeyStream(block[7:], block[7:]) }) {
|
||||
t.Error("crypting part of the last block should not trigger a panic")
|
||||
}
|
||||
checkLastBlock(block)
|
||||
// as before, a third call should trigger a panic because all keystream is spent
|
||||
if !panics(func() { s.XORKeyStream(block[:1], block[:1]) }) {
|
||||
t.Error("crypting after overflow should trigger a panic")
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkChaCha20(b *testing.B, step, count int) {
|
||||
tot := step * count
|
||||
src := make([]byte, tot)
|
||||
dst := make([]byte, tot)
|
||||
key := make([]byte, KeySize)
|
||||
nonce := make([]byte, NonceSize)
|
||||
b.SetBytes(int64(tot))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c, _ := NewUnauthenticatedCipher(key, nonce)
|
||||
for i := 0; i < tot; i += step {
|
||||
c.XORKeyStream(dst[i:], src[i:i+step])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChaCha20(b *testing.B) {
|
||||
b.Run("64", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 64, 1)
|
||||
})
|
||||
b.Run("256", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 256, 1)
|
||||
})
|
||||
b.Run("10x25", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 10, 25)
|
||||
})
|
||||
b.Run("4096", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 4096, 1)
|
||||
})
|
||||
b.Run("100x40", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 100, 40)
|
||||
})
|
||||
b.Run("65536", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 65536, 1)
|
||||
})
|
||||
b.Run("1000x65", func(b *testing.B) {
|
||||
benchmarkChaCha20(b, 1000, 65)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHChaCha20(t *testing.T) {
|
||||
// See draft-irtf-cfrg-xchacha-00, Section 2.2.1.
|
||||
key := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
|
||||
nonce := []byte{0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a,
|
||||
0x00, 0x00, 0x00, 0x00, 0x31, 0x41, 0x59, 0x27}
|
||||
expected := []byte{0x82, 0x41, 0x3b, 0x42, 0x27, 0xb2, 0x7b, 0xfe,
|
||||
0xd3, 0x0e, 0x42, 0x50, 0x8a, 0x87, 0x7d, 0x73,
|
||||
0xa0, 0xf9, 0xe4, 0xd5, 0x8a, 0x74, 0xa8, 0x53,
|
||||
0xc1, 0x2e, 0xc4, 0x13, 0x26, 0xd3, 0xec, 0xdc,
|
||||
}
|
||||
result, err := HChaCha20(key[:], nonce[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(expected, result) {
|
||||
t.Errorf("want %x, got %x", expected, result)
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its
|
||||
// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and
|
||||
// draft-irtf-cfrg-xchacha-01.
|
||||
// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD as specified in RFC 7539,
|
||||
// and its extended nonce variant XChaCha20-Poly1305.
|
||||
package chacha20poly1305 // import "golang.org/x/crypto/chacha20poly1305"
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
@@ -26,14 +26,10 @@ const (
|
||||
// NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305
|
||||
// variant of this AEAD, in bytes.
|
||||
NonceSizeX = 24
|
||||
|
||||
// Overhead is the size of the Poly1305 authentication tag, and the
|
||||
// difference between a ciphertext length and its plaintext.
|
||||
Overhead = 16
|
||||
)
|
||||
|
||||
type chacha20poly1305 struct {
|
||||
key [KeySize]byte
|
||||
key [8]uint32
|
||||
}
|
||||
|
||||
// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||
@@ -42,7 +38,14 @@ func New(key []byte) (cipher.AEAD, error) {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
ret := new(chacha20poly1305)
|
||||
copy(ret.key[:], key)
|
||||
ret.key[0] = binary.LittleEndian.Uint32(key[0:4])
|
||||
ret.key[1] = binary.LittleEndian.Uint32(key[4:8])
|
||||
ret.key[2] = binary.LittleEndian.Uint32(key[8:12])
|
||||
ret.key[3] = binary.LittleEndian.Uint32(key[12:16])
|
||||
ret.key[4] = binary.LittleEndian.Uint32(key[16:20])
|
||||
ret.key[5] = binary.LittleEndian.Uint32(key[20:24])
|
||||
ret.key[6] = binary.LittleEndian.Uint32(key[24:28])
|
||||
ret.key[7] = binary.LittleEndian.Uint32(key[28:32])
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -51,7 +54,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 {
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
// 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
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/crypto/internal/alias"
|
||||
"golang.org/x/crypto/internal/subtle"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
@@ -26,23 +25,23 @@ var (
|
||||
|
||||
// setupState writes a ChaCha20 input matrix to state. See
|
||||
// https://tools.ietf.org/html/rfc7539#section-2.3.
|
||||
func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
|
||||
func setupState(state *[16]uint32, key *[8]uint32, nonce []byte) {
|
||||
state[0] = 0x61707865
|
||||
state[1] = 0x3320646e
|
||||
state[2] = 0x79622d32
|
||||
state[3] = 0x6b206574
|
||||
|
||||
state[4] = binary.LittleEndian.Uint32(key[0:4])
|
||||
state[5] = binary.LittleEndian.Uint32(key[4:8])
|
||||
state[6] = binary.LittleEndian.Uint32(key[8:12])
|
||||
state[7] = binary.LittleEndian.Uint32(key[12:16])
|
||||
state[8] = binary.LittleEndian.Uint32(key[16:20])
|
||||
state[9] = binary.LittleEndian.Uint32(key[20:24])
|
||||
state[10] = binary.LittleEndian.Uint32(key[24:28])
|
||||
state[11] = binary.LittleEndian.Uint32(key[28:32])
|
||||
state[4] = key[0]
|
||||
state[5] = key[1]
|
||||
state[6] = key[2]
|
||||
state[7] = key[3]
|
||||
state[8] = key[4]
|
||||
state[9] = key[5]
|
||||
state[10] = key[6]
|
||||
state[11] = key[7]
|
||||
|
||||
state[12] = 0
|
||||
state[13] = binary.LittleEndian.Uint32(nonce[0:4])
|
||||
state[13] = binary.LittleEndian.Uint32(nonce[:4])
|
||||
state[14] = binary.LittleEndian.Uint32(nonce[4:8])
|
||||
state[15] = binary.LittleEndian.Uint32(nonce[8:12])
|
||||
}
|
||||
@@ -56,7 +55,7 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
setupState(&state, &c.key, nonce)
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
if subtle.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
||||
@@ -73,7 +72,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) {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
// This file was originally from https://golang.org/cl/24717 by Vlad Krasnov of CloudFlare.
|
||||
|
||||
//go:build gc && !purego
|
||||
// +build gc,!purego
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
// General register allocation
|
||||
|
||||
@@ -7,69 +7,69 @@ package chacha20poly1305
|
||||
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/chacha20"
|
||||
"golang.org/x/crypto/internal/subtle"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
)
|
||||
|
||||
func writeWithPadding(p *poly1305.MAC, b []byte) {
|
||||
p.Write(b)
|
||||
if rem := len(b) % 16; rem != 0 {
|
||||
var buf [16]byte
|
||||
padLen := 16 - rem
|
||||
p.Write(buf[:padLen])
|
||||
}
|
||||
}
|
||||
|
||||
func writeUint64(p *poly1305.MAC, n int) {
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], uint64(n))
|
||||
p.Write(buf[:])
|
||||
func roundTo16(n int) int {
|
||||
return 16 * ((n + 15) / 16)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
var polyKey [32]byte
|
||||
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
|
||||
s := chacha20.New(c.key, [3]uint32{
|
||||
binary.LittleEndian.Uint32(nonce[0:4]),
|
||||
binary.LittleEndian.Uint32(nonce[4:8]),
|
||||
binary.LittleEndian.Uint32(nonce[8:12]),
|
||||
})
|
||||
s.XORKeyStream(polyKey[:], polyKey[:])
|
||||
s.SetCounter(1) // set the counter to 1, skipping 32 bytes
|
||||
s.XORKeyStream(ciphertext, plaintext)
|
||||
s.Advance() // skip the next 32 bytes
|
||||
s.XORKeyStream(out, plaintext)
|
||||
|
||||
p := poly1305.New(&polyKey)
|
||||
writeWithPadding(p, additionalData)
|
||||
writeWithPadding(p, ciphertext)
|
||||
writeUint64(p, len(additionalData))
|
||||
writeUint64(p, len(plaintext))
|
||||
p.Sum(tag[:0])
|
||||
polyInput := make([]byte, roundTo16(len(additionalData))+roundTo16(len(plaintext))+8+8)
|
||||
copy(polyInput, additionalData)
|
||||
copy(polyInput[roundTo16(len(additionalData)):], out[:len(plaintext)])
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-16:], uint64(len(additionalData)))
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-8:], uint64(len(plaintext)))
|
||||
|
||||
var tag [poly1305.TagSize]byte
|
||||
poly1305.Sum(&tag, polyInput, &polyKey)
|
||||
copy(out[len(plaintext):], tag[:])
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
tag := ciphertext[len(ciphertext)-16:]
|
||||
var tag [poly1305.TagSize]byte
|
||||
copy(tag[:], ciphertext[len(ciphertext)-16:])
|
||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||
|
||||
var polyKey [32]byte
|
||||
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
|
||||
s := chacha20.New(c.key, [3]uint32{
|
||||
binary.LittleEndian.Uint32(nonce[0:4]),
|
||||
binary.LittleEndian.Uint32(nonce[4:8]),
|
||||
binary.LittleEndian.Uint32(nonce[8:12]),
|
||||
})
|
||||
s.XORKeyStream(polyKey[:], polyKey[:])
|
||||
s.SetCounter(1) // set the counter to 1, skipping 32 bytes
|
||||
s.Advance() // skip the next 32 bytes
|
||||
|
||||
p := poly1305.New(&polyKey)
|
||||
writeWithPadding(p, additionalData)
|
||||
writeWithPadding(p, ciphertext)
|
||||
writeUint64(p, len(additionalData))
|
||||
writeUint64(p, len(ciphertext))
|
||||
polyInput := make([]byte, roundTo16(len(additionalData))+roundTo16(len(ciphertext))+8+8)
|
||||
copy(polyInput, additionalData)
|
||||
copy(polyInput[roundTo16(len(additionalData)):], ciphertext)
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-16:], uint64(len(additionalData)))
|
||||
binary.LittleEndian.PutUint64(polyInput[len(polyInput)-8:], uint64(len(ciphertext)))
|
||||
|
||||
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) {
|
||||
if !poly1305.Verify(&tag, polyInput, &polyKey) {
|
||||
for i := range out {
|
||||
out[i] = 0
|
||||
}
|
||||
|
||||
@@ -2,8 +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
|
||||
// +build !amd64 !go1.7 gccgo appengine
|
||||
|
||||
package chacha20poly1305
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
mathrand "math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
@@ -219,50 +220,36 @@ func BenchmarkChacha20Poly1305(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleNewX() {
|
||||
// key should be randomly generated or derived from a function like Argon2.
|
||||
key := make([]byte, KeySize)
|
||||
if _, err := cryptorand.Read(key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var key = make([]byte, KeySize)
|
||||
|
||||
func ExampleNewX() {
|
||||
aead, err := NewX(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalln("Failed to instantiate XChaCha20-Poly1305:", err)
|
||||
}
|
||||
|
||||
// Encryption.
|
||||
var encryptedMsg []byte
|
||||
{
|
||||
msg := []byte("Gophers, gophers, gophers everywhere!")
|
||||
|
||||
// Select a random nonce, and leave capacity for the ciphertext.
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())
|
||||
for _, msg := range []string{
|
||||
"Attack at dawn.",
|
||||
"The eagle has landed.",
|
||||
"Gophers, gophers, gophers everywhere!",
|
||||
} {
|
||||
// Encryption.
|
||||
nonce := make([]byte, NonceSizeX)
|
||||
if _, err := cryptorand.Read(nonce); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ciphertext := aead.Seal(nil, nonce, []byte(msg), nil)
|
||||
|
||||
// Encrypt the message and append the ciphertext to the nonce.
|
||||
encryptedMsg = aead.Seal(nonce, nonce, msg, nil)
|
||||
}
|
||||
|
||||
// Decryption.
|
||||
{
|
||||
if len(encryptedMsg) < aead.NonceSize() {
|
||||
panic("ciphertext too short")
|
||||
}
|
||||
|
||||
// Split nonce and ciphertext.
|
||||
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
|
||||
|
||||
// Decrypt the message and check it wasn't tampered with.
|
||||
// Decryption.
|
||||
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalln("Failed to decrypt or authenticate message:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", plaintext)
|
||||
}
|
||||
|
||||
// Output: Gophers, gophers, gophers everywhere!
|
||||
// Output: Attack at dawn.
|
||||
// The eagle has landed.
|
||||
// Gophers, gophers, gophers everywhere!
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ var chacha20Poly1305Tests = []struct {
|
||||
"a0784d7a4716f3feb4f64e7f4b39bf04",
|
||||
},
|
||||
{
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-01#appendix-A.3.1
|
||||
"4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
|
||||
"50515253c0c1c2c3c4c5c6c7",
|
||||
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||
"404142434445464748494a4b4c4d4e4f5051525354555657",
|
||||
"bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff921f9664c97637da9768812f615c68b13b52ec0875924c1c7987947deafd8780acf49",
|
||||
"070000004041424344454647",
|
||||
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691",
|
||||
},
|
||||
{
|
||||
"1400000cebccee3bf561b292340fec60",
|
||||
|
||||
@@ -6,13 +6,14 @@ package chacha20poly1305
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/internal/chacha20"
|
||||
)
|
||||
|
||||
type xchacha20poly1305 struct {
|
||||
key [KeySize]byte
|
||||
key [8]uint32
|
||||
}
|
||||
|
||||
// NewX returns a XChaCha20-Poly1305 AEAD that uses the given 256-bit key.
|
||||
@@ -26,7 +27,14 @@ func NewX(key []byte) (cipher.AEAD, error) {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
ret := new(xchacha20poly1305)
|
||||
copy(ret.key[:], key)
|
||||
ret.key[0] = binary.LittleEndian.Uint32(key[0:4])
|
||||
ret.key[1] = binary.LittleEndian.Uint32(key[4:8])
|
||||
ret.key[2] = binary.LittleEndian.Uint32(key[8:12])
|
||||
ret.key[3] = binary.LittleEndian.Uint32(key[12:16])
|
||||
ret.key[4] = binary.LittleEndian.Uint32(key[16:20])
|
||||
ret.key[5] = binary.LittleEndian.Uint32(key[20:24])
|
||||
ret.key[6] = binary.LittleEndian.Uint32(key[24:28])
|
||||
ret.key[7] = binary.LittleEndian.Uint32(key[28:32])
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -35,7 +43,7 @@ func (*xchacha20poly1305) NonceSize() int {
|
||||
}
|
||||
|
||||
func (*xchacha20poly1305) Overhead() int {
|
||||
return Overhead
|
||||
return 16
|
||||
}
|
||||
|
||||
func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
@@ -52,10 +60,15 @@ func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) [
|
||||
panic("chacha20poly1305: plaintext too large")
|
||||
}
|
||||
|
||||
c := new(chacha20poly1305)
|
||||
hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
|
||||
copy(c.key[:], hKey)
|
||||
|
||||
hNonce := [4]uint32{
|
||||
binary.LittleEndian.Uint32(nonce[0:4]),
|
||||
binary.LittleEndian.Uint32(nonce[4:8]),
|
||||
binary.LittleEndian.Uint32(nonce[8:12]),
|
||||
binary.LittleEndian.Uint32(nonce[12:16]),
|
||||
}
|
||||
c := &chacha20poly1305{
|
||||
key: chacha20.HChaCha20(&x.key, &hNonce),
|
||||
}
|
||||
// The first 4 bytes of the final nonce are unused counter space.
|
||||
cNonce := make([]byte, NonceSize)
|
||||
copy(cNonce[4:12], nonce[16:24])
|
||||
@@ -74,10 +87,15 @@ func (x *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte)
|
||||
panic("chacha20poly1305: ciphertext too large")
|
||||
}
|
||||
|
||||
c := new(chacha20poly1305)
|
||||
hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
|
||||
copy(c.key[:], hKey)
|
||||
|
||||
hNonce := [4]uint32{
|
||||
binary.LittleEndian.Uint32(nonce[0:4]),
|
||||
binary.LittleEndian.Uint32(nonce[4:8]),
|
||||
binary.LittleEndian.Uint32(nonce[8:12]),
|
||||
binary.LittleEndian.Uint32(nonce[12:16]),
|
||||
}
|
||||
c := &chacha20poly1305{
|
||||
key: chacha20.HChaCha20(&x.key, &hNonce),
|
||||
}
|
||||
// The first 4 bytes of the final nonce are unused counter space.
|
||||
cNonce := make([]byte, NonceSize)
|
||||
copy(cNonce[4:12], nonce[16:24])
|
||||
|
||||
+40
-104
@@ -81,7 +81,7 @@ func (b *Builder) AddASN1BigInt(n *big.Int) {
|
||||
for i := range bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||
if bytes[0]&0x80 == 0 {
|
||||
c.add(0xff)
|
||||
}
|
||||
c.add(bytes...)
|
||||
@@ -117,19 +117,6 @@ func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
|
||||
})
|
||||
}
|
||||
|
||||
// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
|
||||
func (b *Builder) AddASN1UTCTime(t time.Time) {
|
||||
b.AddASN1(asn1.UTCTime, func(c *Builder) {
|
||||
// As utilized by the X.509 profile, UTCTime can only
|
||||
// represent the years 1950 through 2049.
|
||||
if t.Year() < 1950 || t.Year() >= 2050 {
|
||||
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
|
||||
return
|
||||
}
|
||||
c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
|
||||
})
|
||||
}
|
||||
|
||||
// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
|
||||
// support BIT STRINGs that are not a whole number of bytes.
|
||||
func (b *Builder) AddASN1BitString(data []byte) {
|
||||
@@ -243,12 +230,12 @@ func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
|
||||
|
||||
// String
|
||||
|
||||
// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean
|
||||
// ReadASN1Boolean decodes an ASN.1 INTEGER and converts it to a boolean
|
||||
// representation into out and advances. It reports whether the read
|
||||
// was successful.
|
||||
func (s *String) ReadASN1Boolean(out *bool) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.BOOLEAN) || len(bytes) != 1 {
|
||||
if !s.ReadASN1(&bytes, asn1.INTEGER) || len(bytes) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -264,35 +251,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 +320,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) {
|
||||
@@ -421,12 +394,7 @@ func (s *String) ReadASN1Enum(out *int) bool {
|
||||
func (s *String) readBase128Int(out *int) bool {
|
||||
ret := 0
|
||||
for i := 0; len(*s) > 0; i++ {
|
||||
if i == 5 {
|
||||
return false
|
||||
}
|
||||
// Avoid overflowing int on a 32-bit platform.
|
||||
// We don't want different behavior based on the architecture.
|
||||
if ret >= 1<<(31-7) {
|
||||
if i == 4 {
|
||||
return false
|
||||
}
|
||||
ret <<= 7
|
||||
@@ -498,45 +466,6 @@ func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
const defaultUTCTimeFormatStr = "060102150405Z0700"
|
||||
|
||||
// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadASN1UTCTime(out *time.Time) bool {
|
||||
var bytes String
|
||||
if !s.ReadASN1(&bytes, asn1.UTCTime) {
|
||||
return false
|
||||
}
|
||||
t := string(bytes)
|
||||
|
||||
formatStr := defaultUTCTimeFormatStr
|
||||
var err error
|
||||
res, err := time.Parse(formatStr, t)
|
||||
if err != nil {
|
||||
// Fallback to minute precision if we can't parse second
|
||||
// precision. If we are following X.509 or X.690 we shouldn't
|
||||
// support this, but we do.
|
||||
formatStr = "0601021504Z0700"
|
||||
res, err = time.Parse(formatStr, t)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if serialized := res.Format(formatStr); serialized != t {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.Year() >= 2050 {
|
||||
// UTCTime interprets the low order digits 50-99 as 1950-99.
|
||||
// This only applies to its use in the X.509 profile.
|
||||
// See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
|
||||
res = res.AddDate(-100, 0, 0)
|
||||
}
|
||||
*out = res
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||
@@ -546,7 +475,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 ||
|
||||
@@ -559,7 +488,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 {
|
||||
@@ -568,7 +497,7 @@ func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
paddingBits := bytes[0]
|
||||
paddingBits := uint8(bytes[0])
|
||||
if paddingBits != 0 {
|
||||
return false
|
||||
}
|
||||
@@ -668,27 +597,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")
|
||||
}
|
||||
|
||||
@@ -154,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 {
|
||||
@@ -273,9 +247,6 @@ func TestASN1ObjectIdentifier(t *testing.T) {
|
||||
{[]byte{6, 4, 85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}},
|
||||
{[]byte{6, 3, 0x81, 0x34, 0x03}, true, []int{2, 100, 3}},
|
||||
{[]byte{6, 7, 85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}},
|
||||
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
|
||||
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
|
||||
}
|
||||
|
||||
for i, test := range testData {
|
||||
@@ -340,37 +311,6 @@ func TestReadASN1GeneralizedTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1UTCTime(t *testing.T) {
|
||||
testData := []struct {
|
||||
in string
|
||||
ok bool
|
||||
out time.Time
|
||||
}{
|
||||
{"000102030405Z", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"500102030405Z", true, time.Date(1950, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"490102030405Z", true, time.Date(2049, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"990102030405Z", true, time.Date(1999, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"250102030405Z", true, time.Date(2025, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"750102030405Z", true, time.Date(1975, 01, 02, 03, 04, 05, 0, time.UTC)},
|
||||
{"000102030405+0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", 9*60*60+5*60))},
|
||||
{"000102030405-0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", -9*60*60-5*60))},
|
||||
{"0001020304Z", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.UTC)},
|
||||
{"5001020304Z", true, time.Date(1950, 01, 02, 03, 04, 00, 0, time.UTC)},
|
||||
{"0001020304+0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", 9*60*60+5*60))},
|
||||
{"0001020304-0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", -9*60*60-5*60))},
|
||||
{"000102030405Z0700", false, time.Time{}},
|
||||
{"000102030405", false, time.Time{}},
|
||||
}
|
||||
for i, test := range testData {
|
||||
in := String(append([]byte{byte(asn1.UTCTime), byte(len(test.in))}, test.in...))
|
||||
var out time.Time
|
||||
ok := in.ReadASN1UTCTime(&out)
|
||||
if ok != test.ok || ok && !reflect.DeepEqual(out, test.out) {
|
||||
t.Errorf("#%d: in.ReadASN1UTCTime() = %v, want %v; out = %q, want %q", i, ok, test.ok, out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1BitString(t *testing.T) {
|
||||
testData := []struct {
|
||||
in []byte
|
||||
@@ -395,40 +335,3 @@ func TestReadASN1BitString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddASN1BigInt(t *testing.T) {
|
||||
x := big.NewInt(-1)
|
||||
var b Builder
|
||||
b.AddASN1BigInt(x)
|
||||
got, err := b.Bytes()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error adding -1: %v", err)
|
||||
}
|
||||
s := String(got)
|
||||
var y big.Int
|
||||
ok := s.ReadASN1Integer(&y)
|
||||
if !ok || x.Cmp(&y) != 0 {
|
||||
t.Errorf("unexpected bytes %v, want %v", &y, x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1Boolean(t *testing.T) {
|
||||
testData := []struct {
|
||||
in []byte
|
||||
ok bool
|
||||
out bool
|
||||
}{
|
||||
{[]byte{}, false, false},
|
||||
{[]byte{0x01, 0x01, 0x00}, true, false},
|
||||
{[]byte{0x01, 0x01, 0xff}, true, true},
|
||||
{[]byte{0x01, 0x01, 0x01}, false, false},
|
||||
}
|
||||
for i, test := range testData {
|
||||
in := String(test.in)
|
||||
var out bool
|
||||
ok := in.ReadASN1Boolean(&out)
|
||||
if ok != test.ok || ok && (out != test.out) {
|
||||
t.Errorf("#%d: in.ReadASN1Boolean() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-18
@@ -95,11 +95,6 @@ func (b *Builder) AddUint32(v uint32) {
|
||||
b.add(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...)
|
||||
@@ -111,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
|
||||
@@ -303,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
|
||||
@@ -317,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")
|
||||
}
|
||||
|
||||
@@ -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,26 +179,6 @@ func TestUint32(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint64(0xf2fefefcff3cfdfc)
|
||||
if err := builderBytesEq(&b, 242, 254, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint64(&v) {
|
||||
t.Error("ReadUint64() = false, want true")
|
||||
}
|
||||
if v != 0xf2fefefcff3cfdfc {
|
||||
t.Errorf("v = %x, want f2fefefcff3cfdfc", v)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUMultiple(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint8(23)
|
||||
|
||||
@@ -81,17 +81,6 @@ func (s *String) ReadUint32(out *uint32) bool {
|
||||
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 {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// 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.
|
||||
|
||||
// This code was translated into a form compatible with 6a from the public
|
||||
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||
|
||||
#define REDMASK51 0x0007FFFFFFFFFFFF
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
// This code was translated into a form compatible with 6a from the public
|
||||
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
// These constants cannot be encoded in non-MOVQ immediates.
|
||||
// We access them directly from memory instead.
|
||||
|
||||
DATA ·_121666_213(SB)/8, $996687872
|
||||
GLOBL ·_121666_213(SB), 8, $8
|
||||
|
||||
DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA
|
||||
GLOBL ·_2P0(SB), 8, $8
|
||||
|
||||
DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE
|
||||
GLOBL ·_2P1234(SB), 8, $8
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
// func cswap(inout *[4][5]uint64, v uint64)
|
||||
TEXT ·cswap(SB),7,$0
|
||||
MOVQ inout+0(FP),DI
|
||||
MOVQ v+8(FP),SI
|
||||
|
||||
SUBQ $1, SI
|
||||
NOTQ SI
|
||||
MOVQ SI, X15
|
||||
PSHUFD $0x44, X15, X15
|
||||
|
||||
MOVOU 0(DI), X0
|
||||
MOVOU 16(DI), X2
|
||||
MOVOU 32(DI), X4
|
||||
MOVOU 48(DI), X6
|
||||
MOVOU 64(DI), X8
|
||||
MOVOU 80(DI), X1
|
||||
MOVOU 96(DI), X3
|
||||
MOVOU 112(DI), X5
|
||||
MOVOU 128(DI), X7
|
||||
MOVOU 144(DI), X9
|
||||
|
||||
MOVO X1, X10
|
||||
MOVO X3, X11
|
||||
MOVO X5, X12
|
||||
MOVO X7, X13
|
||||
MOVO X9, X14
|
||||
|
||||
PXOR X0, X10
|
||||
PXOR X2, X11
|
||||
PXOR X4, X12
|
||||
PXOR X6, X13
|
||||
PXOR X8, X14
|
||||
PAND X15, X10
|
||||
PAND X15, X11
|
||||
PAND X15, X12
|
||||
PAND X15, X13
|
||||
PAND X15, X14
|
||||
PXOR X10, X0
|
||||
PXOR X10, X1
|
||||
PXOR X11, X2
|
||||
PXOR X11, X3
|
||||
PXOR X12, X4
|
||||
PXOR X12, X5
|
||||
PXOR X13, X6
|
||||
PXOR X13, X7
|
||||
PXOR X14, X8
|
||||
PXOR X14, X9
|
||||
|
||||
MOVOU X0, 0(DI)
|
||||
MOVOU X2, 16(DI)
|
||||
MOVOU X4, 32(DI)
|
||||
MOVOU X6, 48(DI)
|
||||
MOVOU X8, 64(DI)
|
||||
MOVOU X1, 80(DI)
|
||||
MOVOU X3, 96(DI)
|
||||
MOVOU X5, 112(DI)
|
||||
MOVOU X7, 128(DI)
|
||||
MOVOU X9, 144(DI)
|
||||
RET
|
||||
+811
-123
@@ -1,146 +1,834 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// 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 provides an implementation of the X25519 function, which
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||
// See RFC 7748.
|
||||
package curve25519 // import "golang.org/x/crypto/curve25519"
|
||||
// We have an implementation in amd64 assembly so this code is only run on
|
||||
// non-amd64 platforms. The amd64 assembly does not support gccgo.
|
||||
// +build !amd64 gccgo appengine
|
||||
|
||||
package curve25519
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/curve25519/internal/field"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// ScalarMult sets dst to the product scalar * point.
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: when provided a low-order point, ScalarMult will set dst to all
|
||||
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
|
||||
// will return an error.
|
||||
func ScalarMult(dst, scalar, point *[32]byte) {
|
||||
// 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 scalarMult(out, in, base *[32]byte) {
|
||||
var e [32]byte
|
||||
|
||||
copy(e[:], scalar[:])
|
||||
copy(e[:], in[:])
|
||||
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()
|
||||
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
|
||||
feFromBytes(&x1, base)
|
||||
feOne(&x2)
|
||||
feCopy(&x3, &x1)
|
||||
feOne(&z3)
|
||||
|
||||
swap := 0
|
||||
swap := int32(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)
|
||||
swap ^= int32(b)
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
swap = int32(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)
|
||||
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)
|
||||
}
|
||||
|
||||
x2.Swap(&x3, swap)
|
||||
z2.Swap(&z3, swap)
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
|
||||
z2.Invert(&z2)
|
||||
x2.Multiply(&x2, &z2)
|
||||
copy(dst[:], x2.Bytes())
|
||||
}
|
||||
|
||||
// ScalarBaseMult sets dst to the product scalar * base where base is the
|
||||
// standard generator.
|
||||
//
|
||||
// It is recommended to use the X25519 function with Basepoint instead, as
|
||||
// copying into fixed size arrays can lead to unexpected bugs.
|
||||
func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||
ScalarMult(dst, scalar, &basePoint)
|
||||
}
|
||||
|
||||
const (
|
||||
// ScalarSize is the size of the scalar input to X25519.
|
||||
ScalarSize = 32
|
||||
// PointSize is the size of the point input to X25519.
|
||||
PointSize = 32
|
||||
)
|
||||
|
||||
// Basepoint is the canonical Curve25519 generator.
|
||||
var Basepoint []byte
|
||||
|
||||
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
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.
|
||||
//
|
||||
// scalar can be generated at random, for example with crypto/rand. point should
|
||||
// be either Basepoint or the output of another X25519 call.
|
||||
//
|
||||
// If point is Basepoint (but not if it's a different slice with the same
|
||||
// contents) a precomputed implementation might be used for performance.
|
||||
func X25519(scalar, point []byte) ([]byte, error) {
|
||||
// Outline the body of function, to let the allocation be inlined in the
|
||||
// caller, and possibly avoid escaping to the heap.
|
||||
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, 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] {
|
||||
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, errors.New("bad input point: low order point")
|
||||
}
|
||||
}
|
||||
return dst[:], nil
|
||||
feInvert(&z2, &z2)
|
||||
feMul(&x2, &x2, &z2)
|
||||
feToBytes(out, &x2)
|
||||
}
|
||||
|
||||
@@ -7,63 +7,33 @@ package curve25519
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||
|
||||
func TestX25519Basepoint(t *testing.T) {
|
||||
x := make([]byte, 32)
|
||||
x[0] = 1
|
||||
func TestBaseScalarMult(t *testing.T) {
|
||||
var a, b [32]byte
|
||||
in := &a
|
||||
out := &b
|
||||
a[0] = 1
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
var err error
|
||||
x, err = X25519(x, Basepoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ScalarBaseMult(out, in)
|
||||
in, out = out, in
|
||||
}
|
||||
|
||||
result := hex.EncodeToString(x)
|
||||
result := fmt.Sprintf("%x", in[:])
|
||||
if result != expectedHex {
|
||||
t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowOrderPoints(t *testing.T) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, p := range lowOrderPoints {
|
||||
out, err := X25519(scalar, p)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error, got nil", i)
|
||||
}
|
||||
if out != nil {
|
||||
t.Errorf("%d: expected nil output, got %x", i, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestVectors(t *testing.T) {
|
||||
t.Run("Legacy", func(t *testing.T) { testTestVectors(t, ScalarMult) })
|
||||
t.Run("X25519", func(t *testing.T) {
|
||||
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
||||
out, err := X25519(scalar[:], point[:])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
copy(dst[:], out)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testTestVectors(t *testing.T, scalarMult func(dst, scalar, point *[32]byte)) {
|
||||
for _, tv := range testVectors {
|
||||
var got [32]byte
|
||||
scalarMult(&got, &tv.In, &tv.Base)
|
||||
ScalarMult(&got, &tv.In, &tv.Base)
|
||||
if !bytes.Equal(got[:], tv.Expect[:]) {
|
||||
t.Logf(" in = %x", tv.In)
|
||||
t.Logf(" base = %x", tv.Base)
|
||||
@@ -98,43 +68,12 @@ func TestHighBitIgnored(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var benchmarkSink byte
|
||||
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||
var in, out [32]byte
|
||||
in[0] = 1
|
||||
|
||||
func BenchmarkX25519Basepoint(b *testing.B) {
|
||||
scalar := make([]byte, 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 := X25519(scalar, Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkX25519(b *testing.B) {
|
||||
scalar := make([]byte, ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
point, err := X25519(scalar, 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 := X25519(scalar, point)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
ScalarBaseMult(&out, &in)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
// Package curve25519 provides an implementation of scalar multiplication on
|
||||
// the elliptic curve known as curve25519. See https://cr.yp.to/ecdh.html
|
||||
package curve25519 // import "golang.org/x/crypto/curve25519"
|
||||
|
||||
// basePoint is the x coordinate of the generator of the curve.
|
||||
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}
|
||||
|
||||
// ScalarMult sets dst to the product in*base where dst and base are the x
|
||||
// coordinates of group points and all values are in little-endian form.
|
||||
func ScalarMult(dst, in, base *[32]byte) {
|
||||
scalarMult(dst, in, base)
|
||||
}
|
||||
|
||||
// ScalarBaseMult sets dst to the product in*base where dst and base are the x
|
||||
// coordinates of group points, base is the standard generator and all values
|
||||
// are in little-endian form.
|
||||
func ScalarBaseMult(dst, in *[32]byte) {
|
||||
ScalarMult(dst, in, &basePoint)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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.
|
||||
|
||||
// This code was translated into a form compatible with 6a from the public
|
||||
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "const_amd64.h"
|
||||
|
||||
// func freeze(inout *[5]uint64)
|
||||
TEXT ·freeze(SB),7,$0-8
|
||||
MOVQ inout+0(FP), DI
|
||||
|
||||
MOVQ 0(DI),SI
|
||||
MOVQ 8(DI),DX
|
||||
MOVQ 16(DI),CX
|
||||
MOVQ 24(DI),R8
|
||||
MOVQ 32(DI),R9
|
||||
MOVQ $REDMASK51,AX
|
||||
MOVQ AX,R10
|
||||
SUBQ $18,R10
|
||||
MOVQ $3,R11
|
||||
REDUCELOOP:
|
||||
MOVQ SI,R12
|
||||
SHRQ $51,R12
|
||||
ANDQ AX,SI
|
||||
ADDQ R12,DX
|
||||
MOVQ DX,R12
|
||||
SHRQ $51,R12
|
||||
ANDQ AX,DX
|
||||
ADDQ R12,CX
|
||||
MOVQ CX,R12
|
||||
SHRQ $51,R12
|
||||
ANDQ AX,CX
|
||||
ADDQ R12,R8
|
||||
MOVQ R8,R12
|
||||
SHRQ $51,R12
|
||||
ANDQ AX,R8
|
||||
ADDQ R12,R9
|
||||
MOVQ R9,R12
|
||||
SHRQ $51,R12
|
||||
ANDQ AX,R9
|
||||
IMUL3Q $19,R12,R12
|
||||
ADDQ R12,SI
|
||||
SUBQ $1,R11
|
||||
JA REDUCELOOP
|
||||
MOVQ $1,R12
|
||||
CMPQ R10,SI
|
||||
CMOVQLT R11,R12
|
||||
CMPQ AX,DX
|
||||
CMOVQNE R11,R12
|
||||
CMPQ AX,CX
|
||||
CMOVQNE R11,R12
|
||||
CMPQ AX,R8
|
||||
CMOVQNE R11,R12
|
||||
CMPQ AX,R9
|
||||
CMOVQNE R11,R12
|
||||
NEGQ R12
|
||||
ANDQ R12,AX
|
||||
ANDQ R12,R10
|
||||
SUBQ R10,SI
|
||||
SUBQ AX,DX
|
||||
SUBQ AX,CX
|
||||
SUBQ AX,R8
|
||||
SUBQ AX,R9
|
||||
MOVQ SI,0(DI)
|
||||
MOVQ DX,8(DI)
|
||||
MOVQ CX,16(DI)
|
||||
MOVQ R8,24(DI)
|
||||
MOVQ R9,32(DI)
|
||||
RET
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
module asm
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.4.0
|
||||
golang.org/x/crypto v0.0.0
|
||||
)
|
||||
|
||||
replace golang.org/x/crypto v0.0.0 => ../../../..
|
||||
@@ -1,34 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.4.0 h1:jeHDRktVD+578ULxWpQHkilor6pkdLF7u7EiTzDbfcU=
|
||||
github.com/mmcloughlin/avo v0.4.0/go.mod h1:RW9BfYA3TgO9uCdNrKU2h6J8cPD8ZLznvfgHAeszb1s=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +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
|
||||
// +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)
|
||||
@@ -1,379 +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
|
||||
// +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,12 +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
|
||||
// +build !amd64 !gc purego
|
||||
|
||||
package field
|
||||
|
||||
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
|
||||
|
||||
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
|
||||
@@ -1,16 +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
|
||||
// +build arm64,gc,!purego
|
||||
|
||||
package field
|
||||
|
||||
//go:noescape
|
||||
func carryPropagate(v *Element)
|
||||
|
||||
func (v *Element) carryPropagate() *Element {
|
||||
carryPropagate(v)
|
||||
return v
|
||||
}
|
||||
@@ -1,43 +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
|
||||
// +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,12 +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
|
||||
// +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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,240 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
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 */
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// 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.
|
||||
|
||||
// This code was translated into a form compatible with 6a from the public
|
||||
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "const_amd64.h"
|
||||
|
||||
// func mul(dest, a, b *[5]uint64)
|
||||
TEXT ·mul(SB),0,$16-24
|
||||
MOVQ dest+0(FP), DI
|
||||
MOVQ a+8(FP), SI
|
||||
MOVQ b+16(FP), DX
|
||||
|
||||
MOVQ DX,CX
|
||||
MOVQ 24(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MOVQ AX,0(SP)
|
||||
MULQ 16(CX)
|
||||
MOVQ AX,R8
|
||||
MOVQ DX,R9
|
||||
MOVQ 32(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MOVQ AX,8(SP)
|
||||
MULQ 8(CX)
|
||||
ADDQ AX,R8
|
||||
ADCQ DX,R9
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 0(CX)
|
||||
ADDQ AX,R8
|
||||
ADCQ DX,R9
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 8(CX)
|
||||
MOVQ AX,R10
|
||||
MOVQ DX,R11
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 16(CX)
|
||||
MOVQ AX,R12
|
||||
MOVQ DX,R13
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 24(CX)
|
||||
MOVQ AX,R14
|
||||
MOVQ DX,R15
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 32(CX)
|
||||
MOVQ AX,BX
|
||||
MOVQ DX,BP
|
||||
MOVQ 8(SI),AX
|
||||
MULQ 0(CX)
|
||||
ADDQ AX,R10
|
||||
ADCQ DX,R11
|
||||
MOVQ 8(SI),AX
|
||||
MULQ 8(CX)
|
||||
ADDQ AX,R12
|
||||
ADCQ DX,R13
|
||||
MOVQ 8(SI),AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX,R14
|
||||
ADCQ DX,R15
|
||||
MOVQ 8(SI),AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX,BX
|
||||
ADCQ DX,BP
|
||||
MOVQ 8(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX,R8
|
||||
ADCQ DX,R9
|
||||
MOVQ 16(SI),AX
|
||||
MULQ 0(CX)
|
||||
ADDQ AX,R12
|
||||
ADCQ DX,R13
|
||||
MOVQ 16(SI),AX
|
||||
MULQ 8(CX)
|
||||
ADDQ AX,R14
|
||||
ADCQ DX,R15
|
||||
MOVQ 16(SI),AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX,BX
|
||||
ADCQ DX,BP
|
||||
MOVQ 16(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX,R8
|
||||
ADCQ DX,R9
|
||||
MOVQ 16(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX,R10
|
||||
ADCQ DX,R11
|
||||
MOVQ 24(SI),AX
|
||||
MULQ 0(CX)
|
||||
ADDQ AX,R14
|
||||
ADCQ DX,R15
|
||||
MOVQ 24(SI),AX
|
||||
MULQ 8(CX)
|
||||
ADDQ AX,BX
|
||||
ADCQ DX,BP
|
||||
MOVQ 0(SP),AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX,R10
|
||||
ADCQ DX,R11
|
||||
MOVQ 0(SP),AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX,R12
|
||||
ADCQ DX,R13
|
||||
MOVQ 32(SI),AX
|
||||
MULQ 0(CX)
|
||||
ADDQ AX,BX
|
||||
ADCQ DX,BP
|
||||
MOVQ 8(SP),AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX,R10
|
||||
ADCQ DX,R11
|
||||
MOVQ 8(SP),AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX,R12
|
||||
ADCQ DX,R13
|
||||
MOVQ 8(SP),AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX,R14
|
||||
ADCQ DX,R15
|
||||
MOVQ $REDMASK51,SI
|
||||
SHLQ $13,R8,R9
|
||||
ANDQ SI,R8
|
||||
SHLQ $13,R10,R11
|
||||
ANDQ SI,R10
|
||||
ADDQ R9,R10
|
||||
SHLQ $13,R12,R13
|
||||
ANDQ SI,R12
|
||||
ADDQ R11,R12
|
||||
SHLQ $13,R14,R15
|
||||
ANDQ SI,R14
|
||||
ADDQ R13,R14
|
||||
SHLQ $13,BX,BP
|
||||
ANDQ SI,BX
|
||||
ADDQ R15,BX
|
||||
IMUL3Q $19,BP,DX
|
||||
ADDQ DX,R8
|
||||
MOVQ R8,DX
|
||||
SHRQ $51,DX
|
||||
ADDQ R10,DX
|
||||
MOVQ DX,CX
|
||||
SHRQ $51,DX
|
||||
ANDQ SI,R8
|
||||
ADDQ R12,DX
|
||||
MOVQ DX,R9
|
||||
SHRQ $51,DX
|
||||
ANDQ SI,CX
|
||||
ADDQ R14,DX
|
||||
MOVQ DX,AX
|
||||
SHRQ $51,DX
|
||||
ANDQ SI,R9
|
||||
ADDQ BX,DX
|
||||
MOVQ DX,R10
|
||||
SHRQ $51,DX
|
||||
ANDQ SI,AX
|
||||
IMUL3Q $19,DX,DX
|
||||
ADDQ DX,R8
|
||||
ANDQ SI,R10
|
||||
MOVQ R8,0(DI)
|
||||
MOVQ CX,8(DI)
|
||||
MOVQ R9,16(DI)
|
||||
MOVQ AX,24(DI)
|
||||
MOVQ R10,32(DI)
|
||||
RET
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
// This code was translated into a form compatible with 6a from the public
|
||||
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "const_amd64.h"
|
||||
|
||||
// func square(out, in *[5]uint64)
|
||||
TEXT ·square(SB),7,$0-16
|
||||
MOVQ out+0(FP), DI
|
||||
MOVQ in+8(FP), SI
|
||||
|
||||
MOVQ 0(SI),AX
|
||||
MULQ 0(SI)
|
||||
MOVQ AX,CX
|
||||
MOVQ DX,R8
|
||||
MOVQ 0(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 8(SI)
|
||||
MOVQ AX,R9
|
||||
MOVQ DX,R10
|
||||
MOVQ 0(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 16(SI)
|
||||
MOVQ AX,R11
|
||||
MOVQ DX,R12
|
||||
MOVQ 0(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 24(SI)
|
||||
MOVQ AX,R13
|
||||
MOVQ DX,R14
|
||||
MOVQ 0(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 32(SI)
|
||||
MOVQ AX,R15
|
||||
MOVQ DX,BX
|
||||
MOVQ 8(SI),AX
|
||||
MULQ 8(SI)
|
||||
ADDQ AX,R11
|
||||
ADCQ DX,R12
|
||||
MOVQ 8(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 16(SI)
|
||||
ADDQ AX,R13
|
||||
ADCQ DX,R14
|
||||
MOVQ 8(SI),AX
|
||||
SHLQ $1,AX
|
||||
MULQ 24(SI)
|
||||
ADDQ AX,R15
|
||||
ADCQ DX,BX
|
||||
MOVQ 8(SI),DX
|
||||
IMUL3Q $38,DX,AX
|
||||
MULQ 32(SI)
|
||||
ADDQ AX,CX
|
||||
ADCQ DX,R8
|
||||
MOVQ 16(SI),AX
|
||||
MULQ 16(SI)
|
||||
ADDQ AX,R15
|
||||
ADCQ DX,BX
|
||||
MOVQ 16(SI),DX
|
||||
IMUL3Q $38,DX,AX
|
||||
MULQ 24(SI)
|
||||
ADDQ AX,CX
|
||||
ADCQ DX,R8
|
||||
MOVQ 16(SI),DX
|
||||
IMUL3Q $38,DX,AX
|
||||
MULQ 32(SI)
|
||||
ADDQ AX,R9
|
||||
ADCQ DX,R10
|
||||
MOVQ 24(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MULQ 24(SI)
|
||||
ADDQ AX,R9
|
||||
ADCQ DX,R10
|
||||
MOVQ 24(SI),DX
|
||||
IMUL3Q $38,DX,AX
|
||||
MULQ 32(SI)
|
||||
ADDQ AX,R11
|
||||
ADCQ DX,R12
|
||||
MOVQ 32(SI),DX
|
||||
IMUL3Q $19,DX,AX
|
||||
MULQ 32(SI)
|
||||
ADDQ AX,R13
|
||||
ADCQ DX,R14
|
||||
MOVQ $REDMASK51,SI
|
||||
SHLQ $13,CX,R8
|
||||
ANDQ SI,CX
|
||||
SHLQ $13,R9,R10
|
||||
ANDQ SI,R9
|
||||
ADDQ R8,R9
|
||||
SHLQ $13,R11,R12
|
||||
ANDQ SI,R11
|
||||
ADDQ R10,R11
|
||||
SHLQ $13,R13,R14
|
||||
ANDQ SI,R13
|
||||
ADDQ R12,R13
|
||||
SHLQ $13,R15,BX
|
||||
ANDQ SI,R15
|
||||
ADDQ R14,R15
|
||||
IMUL3Q $19,BX,DX
|
||||
ADDQ DX,CX
|
||||
MOVQ CX,DX
|
||||
SHRQ $51,DX
|
||||
ADDQ R9,DX
|
||||
ANDQ SI,CX
|
||||
MOVQ DX,R8
|
||||
SHRQ $51,DX
|
||||
ADDQ R11,DX
|
||||
ANDQ SI,R8
|
||||
MOVQ DX,R9
|
||||
SHRQ $51,DX
|
||||
ADDQ R13,DX
|
||||
ANDQ SI,R9
|
||||
MOVQ DX,AX
|
||||
SHRQ $51,DX
|
||||
ADDQ R15,DX
|
||||
ANDQ SI,AX
|
||||
MOVQ DX,R10
|
||||
SHRQ $51,DX
|
||||
IMUL3Q $19,DX,DX
|
||||
ADDQ DX,CX
|
||||
ANDQ SI,R10
|
||||
MOVQ CX,0(DI)
|
||||
MOVQ R8,8(DI)
|
||||
MOVQ R9,16(DI)
|
||||
MOVQ AX,24(DI)
|
||||
MOVQ R10,32(DI)
|
||||
RET
|
||||
@@ -4,18 +4,6 @@
|
||||
|
||||
package curve25519
|
||||
|
||||
// lowOrderPoints from libsodium.
|
||||
// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
|
||||
var lowOrderPoints = [][]byte{
|
||||
{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, 0x00},
|
||||
{0x01, 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},
|
||||
{0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00},
|
||||
{0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57},
|
||||
{0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
|
||||
{0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
|
||||
{0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
|
||||
}
|
||||
|
||||
// testVectors generated with BoringSSL.
|
||||
var testVectors = []struct {
|
||||
In [32]byte
|
||||
+164
-18
@@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
@@ -10,15 +10,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 +39,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 +97,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[:])
|
||||
}
|
||||
|
||||
+208
-10
@@ -1,22 +1,220 @@
|
||||
// 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
|
||||
package ed25519
|
||||
|
||||
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{}
|
||||
|
||||
message := []byte("test message")
|
||||
sig := ed25519.Sign(private, message)
|
||||
if !ed25519.Verify(public, message, sig) {
|
||||
t.Errorf("valid signature rejected")
|
||||
func (zeroReader) Read(buf []byte) (int, error) {
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestUnmarshalMarshal(t *testing.T) {
|
||||
pub, _, _ := 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, _ := GenerateKey(zero)
|
||||
|
||||
message := []byte("test message")
|
||||
sig := Sign(private, message)
|
||||
if !Verify(public, message, sig) {
|
||||
t.Errorf("valid signature rejected")
|
||||
}
|
||||
|
||||
wrongMessage := []byte("wrong message")
|
||||
if Verify(public, wrongMessage, sig) {
|
||||
t.Errorf("signature of different message accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptoSigner(t *testing.T) {
|
||||
var zero zeroReader
|
||||
public, private, _ := GenerateKey(zero)
|
||||
|
||||
signer := crypto.Signer(private)
|
||||
|
||||
publicInterface := signer.Public()
|
||||
public2, ok := publicInterface.(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 !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[:SignatureSize]
|
||||
|
||||
if l := len(pubKey); l != PublicKeySize {
|
||||
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
|
||||
}
|
||||
|
||||
var priv [PrivateKeySize]byte
|
||||
copy(priv[:], privBytes)
|
||||
copy(priv[32:], pubKey)
|
||||
|
||||
sig2 := Sign(priv[:], msg)
|
||||
if !bytes.Equal(sig, sig2[:]) {
|
||||
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
|
||||
}
|
||||
|
||||
if !Verify(pubKey, msg, sig2) {
|
||||
t.Errorf("signature failed to verify on line %d", lineNo)
|
||||
}
|
||||
|
||||
priv2 := 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().(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 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 := GenerateKey(zero); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSigning(b *testing.B) {
|
||||
var zero zeroReader
|
||||
_, priv, err := GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Sign(priv, message)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVerification(b *testing.B) {
|
||||
var zero zeroReader
|
||||
pub, priv, err := GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
signature := Sign(priv, message)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Verify(pub, message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user