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,4 +1,4 @@
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# Go Cryptography
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/crypto)
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
This repository holds supplementary Go cryptography packages.
|
||||
## Download/Install
|
||||
|
||||
The easiest way to install is to run `go get -u golang.org/x/crypto/...`. You
|
||||
can also manually git clone the repository to `$GOPATH/src/golang.org/x/crypto`.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://go.dev/doc/contribute.
|
||||
|
||||
The git repository is https://go.googlesource.com/crypto.
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
|
||||
The main issue tracker for the crypto repository is located at
|
||||
https://go.dev/issues. Prefix your issue with "x/crypto:" in the
|
||||
https://github.com/golang/go/issues. Prefix your issue with "x/crypto:" in the
|
||||
subject line, so it is easy to find.
|
||||
|
||||
Note that contributions to the cryptography package receive additional scrutiny
|
||||
|
||||
+310
-183
@@ -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,15 +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
|
||||
}
|
||||
|
||||
payload := json.RawMessage("{}")
|
||||
if len(chal.Payload) != 0 {
|
||||
payload = chal.Payload
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"type"`
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}{
|
||||
Resource: "challenge",
|
||||
Type: chal.Type,
|
||||
Auth: auth,
|
||||
}
|
||||
res, err := c.post(ctx, nil, chal.URI, payload, wantStatus(
|
||||
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)
|
||||
))
|
||||
@@ -568,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 {
|
||||
@@ -586,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[:])
|
||||
@@ -632,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,
|
||||
}
|
||||
@@ -654,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 {
|
||||
@@ -728,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.
|
||||
@@ -818,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
|
||||
|
||||
+651
-105
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -757,7 +1302,7 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
key, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+167
-210
@@ -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
|
||||
}
|
||||
@@ -292,10 +287,6 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// regular domain
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ck := certKey{
|
||||
domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
|
||||
isRSA: !supportsECDSA(hello),
|
||||
@@ -309,6 +300,9 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// first-time
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err = m.createCert(ctx, ck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -324,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 {
|
||||
@@ -389,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 {
|
||||
@@ -464,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
|
||||
}
|
||||
|
||||
@@ -590,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
|
||||
@@ -610,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()
|
||||
}
|
||||
|
||||
@@ -654,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,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 {
|
||||
@@ -826,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)
|
||||
}
|
||||
@@ -841,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}
|
||||
@@ -853,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
|
||||
}
|
||||
@@ -869,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)
|
||||
}
|
||||
@@ -886,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))
|
||||
@@ -900,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)
|
||||
@@ -908,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 {
|
||||
@@ -986,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
|
||||
@@ -995,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
|
||||
@@ -1067,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)
|
||||
@@ -1134,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:
|
||||
@@ -1168,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
|
||||
|
||||
+630
-414
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, "%v", err)
|
||||
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, "%v", err)
|
||||
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, "%v", err)
|
||||
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, "%v", err)
|
||||
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, "%v", err)
|
||||
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, "%v", err)
|
||||
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
-68
@@ -10,12 +10,10 @@ import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -157,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
|
||||
@@ -203,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
|
||||
}
|
||||
@@ -246,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 {
|
||||
@@ -271,41 +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.
|
||||
var packageVersion string
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -329,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
+50
-346
@@ -7,7 +7,6 @@ package acme
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -15,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.
|
||||
@@ -46,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.
|
||||
@@ -94,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
|
||||
@@ -140,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
|
||||
@@ -185,111 +108,50 @@ 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
|
||||
|
||||
// Terms is a URI identifying the current terms of service.
|
||||
// Term is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
// Website is an HTTP or HTTPS URL locating a website
|
||||
@@ -298,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
|
||||
@@ -419,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 {
|
||||
@@ -472,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()
|
||||
@@ -488,79 +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
|
||||
|
||||
// Payload is the JSON-formatted payload that the client sends
|
||||
// to the server to indicate it is ready to respond to the challenge.
|
||||
// When unset, it defaults to an empty JSON object: {}.
|
||||
// For most challenges, the client must not set Payload,
|
||||
// see https://tools.ietf.org/html/rfc8555#section-7.5.1.
|
||||
// Payload is used only for newer challenges (such as "device-attest-01")
|
||||
// where the client must send additional data for the server to validate
|
||||
// the challenge.
|
||||
Payload json.RawMessage
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
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
|
||||
}
|
||||
@@ -573,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,287 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
. "github.com/mmcloughlin/avo/reg"
|
||||
_ "golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
//go:generate go run . -out ../blamka_amd64.s -pkg argon2
|
||||
|
||||
func main() {
|
||||
Package("golang.org/x/crypto/argon2")
|
||||
ConstraintExpr("amd64,gc,!purego")
|
||||
|
||||
blamkaSSE4()
|
||||
mixBlocksSSE2()
|
||||
xorBlocksSSE2()
|
||||
Generate()
|
||||
}
|
||||
|
||||
func blamkaSSE4() {
|
||||
Implement("blamkaSSE4")
|
||||
Attributes(NOSPLIT)
|
||||
AllocLocal(0)
|
||||
|
||||
Load(Param("b"), RAX)
|
||||
|
||||
c40 := c40_DATA()
|
||||
c48 := c48_DATA()
|
||||
|
||||
MOVOU(c40, X10)
|
||||
MOVOU(c48, X11)
|
||||
|
||||
BLAMKA_ROUND_0(AX, 0, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 16, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 32, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 48, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 64, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 80, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 96, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 112, X8, X9, X10, X11)
|
||||
|
||||
BLAMKA_ROUND_1(AX, 0, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 2, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 4, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 6, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 8, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 10, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 12, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 14, X8, X9, X10, X11)
|
||||
RET()
|
||||
}
|
||||
|
||||
func mixBlocksSSE2() {
|
||||
Implement("mixBlocksSSE2")
|
||||
Attributes(NOSPLIT)
|
||||
AllocLocal(0)
|
||||
|
||||
Load(Param("out"), RDX)
|
||||
Load(Param("a"), RAX)
|
||||
Load(Param("b"), RBX)
|
||||
Load(Param("c"), RCX)
|
||||
MOVQ(U32(128), RDI)
|
||||
|
||||
Label("loop")
|
||||
MOVOU(Mem{Base: AX}.Offset(0), X0)
|
||||
MOVOU(Mem{Base: BX}.Offset(0), X1)
|
||||
MOVOU(Mem{Base: CX}.Offset(0), X2)
|
||||
PXOR(X1, X0)
|
||||
PXOR(X2, X0)
|
||||
MOVOU(X0, Mem{Base: DX}.Offset(0))
|
||||
ADDQ(Imm(16), RAX)
|
||||
ADDQ(Imm(16), RBX)
|
||||
ADDQ(Imm(16), RCX)
|
||||
ADDQ(Imm(16), RDX)
|
||||
SUBQ(Imm(2), RDI)
|
||||
JA(LabelRef("loop"))
|
||||
RET()
|
||||
}
|
||||
|
||||
func xorBlocksSSE2() {
|
||||
Implement("xorBlocksSSE2")
|
||||
Attributes(NOSPLIT)
|
||||
AllocLocal(0)
|
||||
|
||||
Load(Param("out"), RDX)
|
||||
Load(Param("a"), RAX)
|
||||
Load(Param("b"), RBX)
|
||||
Load(Param("c"), RCX)
|
||||
MOVQ(U32(128), RDI)
|
||||
|
||||
Label("loop")
|
||||
MOVOU(Mem{Base: AX}.Offset(0), X0)
|
||||
MOVOU(Mem{Base: BX}.Offset(0), X1)
|
||||
MOVOU(Mem{Base: CX}.Offset(0), X2)
|
||||
MOVOU(Mem{Base: DX}.Offset(0), X3)
|
||||
PXOR(X1, X0)
|
||||
PXOR(X2, X0)
|
||||
PXOR(X3, X0)
|
||||
MOVOU(X0, Mem{Base: DX}.Offset(0))
|
||||
ADDQ(Imm(16), RAX)
|
||||
ADDQ(Imm(16), RBX)
|
||||
ADDQ(Imm(16), RCX)
|
||||
ADDQ(Imm(16), RDX)
|
||||
SUBQ(Imm(2), RDI)
|
||||
JA(LabelRef("loop"))
|
||||
RET()
|
||||
}
|
||||
|
||||
func SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2 VecPhysical) {
|
||||
MOVO(v4, t1)
|
||||
MOVO(v5, v4)
|
||||
MOVO(t1, v5)
|
||||
MOVO(v6, t1)
|
||||
PUNPCKLQDQ(v6, t2)
|
||||
PUNPCKHQDQ(v7, v6)
|
||||
PUNPCKHQDQ(t2, v6)
|
||||
PUNPCKLQDQ(v7, t2)
|
||||
MOVO(t1, v7)
|
||||
MOVO(v2, t1)
|
||||
PUNPCKHQDQ(t2, v7)
|
||||
PUNPCKLQDQ(v3, t2)
|
||||
PUNPCKHQDQ(t2, v2)
|
||||
PUNPCKLQDQ(t1, t2)
|
||||
PUNPCKHQDQ(t2, v3)
|
||||
}
|
||||
|
||||
func SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2 VecPhysical) {
|
||||
MOVO(v4, t1)
|
||||
MOVO(v5, v4)
|
||||
MOVO(t1, v5)
|
||||
MOVO(v2, t1)
|
||||
PUNPCKLQDQ(v2, t2)
|
||||
PUNPCKHQDQ(v3, v2)
|
||||
PUNPCKHQDQ(t2, v2)
|
||||
PUNPCKLQDQ(v3, t2)
|
||||
MOVO(t1, v3)
|
||||
MOVO(v6, t1)
|
||||
PUNPCKHQDQ(t2, v3)
|
||||
PUNPCKLQDQ(v7, t2)
|
||||
PUNPCKHQDQ(t2, v6)
|
||||
PUNPCKLQDQ(t1, t2)
|
||||
PUNPCKHQDQ(t2, v7)
|
||||
}
|
||||
|
||||
func HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, c40, c48 VecPhysical) {
|
||||
MOVO(v0, t0)
|
||||
PMULULQ(v2, t0)
|
||||
PADDQ(v2, v0)
|
||||
PADDQ(t0, v0)
|
||||
PADDQ(t0, v0)
|
||||
PXOR(v0, v6)
|
||||
PSHUFD(Imm(0xB1), v6, v6)
|
||||
MOVO(v4, t0)
|
||||
PMULULQ(v6, t0)
|
||||
PADDQ(v6, v4)
|
||||
PADDQ(t0, v4)
|
||||
PADDQ(t0, v4)
|
||||
PXOR(v4, v2)
|
||||
PSHUFB(c40, v2)
|
||||
MOVO(v0, t0)
|
||||
PMULULQ(v2, t0)
|
||||
PADDQ(v2, v0)
|
||||
PADDQ(t0, v0)
|
||||
PADDQ(t0, v0)
|
||||
PXOR(v0, v6)
|
||||
PSHUFB(c48, v6)
|
||||
MOVO(v4, t0)
|
||||
PMULULQ(v6, t0)
|
||||
PADDQ(v6, v4)
|
||||
PADDQ(t0, v4)
|
||||
PADDQ(t0, v4)
|
||||
PXOR(v4, v2)
|
||||
MOVO(v2, t0)
|
||||
PADDQ(v2, t0)
|
||||
PSRLQ(Imm(63), v2)
|
||||
PXOR(t0, v2)
|
||||
MOVO(v1, t0)
|
||||
PMULULQ(v3, t0)
|
||||
PADDQ(v3, v1)
|
||||
PADDQ(t0, v1)
|
||||
PADDQ(t0, v1)
|
||||
PXOR(v1, v7)
|
||||
PSHUFD(Imm(0xB1), v7, v7)
|
||||
MOVO(v5, t0)
|
||||
PMULULQ(v7, t0)
|
||||
PADDQ(v7, v5)
|
||||
PADDQ(t0, v5)
|
||||
PADDQ(t0, v5)
|
||||
PXOR(v5, v3)
|
||||
PSHUFB(c40, v3)
|
||||
MOVO(v1, t0)
|
||||
PMULULQ(v3, t0)
|
||||
PADDQ(v3, v1)
|
||||
PADDQ(t0, v1)
|
||||
PADDQ(t0, v1)
|
||||
PXOR(v1, v7)
|
||||
PSHUFB(c48, v7)
|
||||
MOVO(v5, t0)
|
||||
PMULULQ(v7, t0)
|
||||
PADDQ(v7, v5)
|
||||
PADDQ(t0, v5)
|
||||
PADDQ(t0, v5)
|
||||
PXOR(v5, v3)
|
||||
MOVO(v3, t0)
|
||||
PADDQ(v3, t0)
|
||||
PSRLQ(Imm(63), v3)
|
||||
PXOR(t0, v3)
|
||||
}
|
||||
|
||||
func LOAD_MSG_0(block GPPhysical, off int) {
|
||||
var registers = []VecPhysical{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
for i, r := range registers {
|
||||
MOVOU(Mem{Base: block}.Offset(8*(off+(i*2))), r)
|
||||
}
|
||||
}
|
||||
|
||||
func STORE_MSG_0(block GPPhysical, off int) {
|
||||
var registers = []VecPhysical{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
for i, r := range registers {
|
||||
MOVOU(r, Mem{Base: block}.Offset(8*(off+(i*2))))
|
||||
}
|
||||
}
|
||||
|
||||
func LOAD_MSG_1(block GPPhysical, off int) {
|
||||
var registers = []VecPhysical{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
for i, r := range registers {
|
||||
MOVOU(Mem{Base: block}.Offset(8*off+i*16*8), r)
|
||||
}
|
||||
}
|
||||
|
||||
func STORE_MSG_1(block GPPhysical, off int) {
|
||||
var registers = []VecPhysical{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
for i, r := range registers {
|
||||
MOVOU(r, Mem{Base: block}.Offset(8*off+i*16*8))
|
||||
}
|
||||
}
|
||||
|
||||
func BLAMKA_ROUND_0(block GPPhysical, off int, t0, t1, c40, c48 VecPhysical) {
|
||||
LOAD_MSG_0(block, off)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1)
|
||||
STORE_MSG_0(block, off)
|
||||
}
|
||||
|
||||
func BLAMKA_ROUND_1(block GPPhysical, off int, t0, t1, c40, c48 VecPhysical) {
|
||||
LOAD_MSG_1(block, off)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1)
|
||||
STORE_MSG_1(block, off)
|
||||
}
|
||||
|
||||
// ##------------------DATA SECTION-------------------##
|
||||
|
||||
var c40_DATA_ptr, c48_DATA_ptr *Mem
|
||||
|
||||
func c40_DATA() Mem {
|
||||
if c40_DATA_ptr != nil {
|
||||
return *c40_DATA_ptr
|
||||
}
|
||||
|
||||
c40_DATA := GLOBL("·c40", NOPTR|RODATA)
|
||||
c40_DATA_ptr = &c40_DATA
|
||||
DATA(0x00, U64(0x0201000706050403))
|
||||
DATA(0x08, U64(0x0a09080f0e0d0c0b))
|
||||
return c40_DATA
|
||||
}
|
||||
func c48_DATA() Mem {
|
||||
if c48_DATA_ptr != nil {
|
||||
return *c48_DATA_ptr
|
||||
}
|
||||
|
||||
c48_DATA := GLOBL("·c48", NOPTR|RODATA)
|
||||
c48_DATA_ptr = &c48_DATA
|
||||
DATA(0x00, U64(0x0100070605040302))
|
||||
DATA(0x08, U64(0x09080f0e0d0c0b0a))
|
||||
return c48_DATA
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module argon2/_asm
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
+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,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
+216
-2764
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !amd64 || purego || !gc
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package argon2
|
||||
|
||||
|
||||
+2
-11
@@ -4,7 +4,7 @@
|
||||
|
||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||
package bcrypt
|
||||
package bcrypt // import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
// The code is a port of Provos and Mazières's C implementation.
|
||||
import (
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
module blake2b/_asm/AVX2
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
@@ -1,361 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
. "github.com/mmcloughlin/avo/reg"
|
||||
_ "golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
//go:generate go run . -out ../../blake2b_amd64.s -pkg blake2b
|
||||
|
||||
const ThatPeskyUnicodeDot = "\u00b7"
|
||||
|
||||
var iv0_DATA_ptr, iv1_DATA_ptr, iv2_DATA_ptr, iv3_DATA_ptr, c40_DATA_ptr, c48_DATA_ptr *Mem
|
||||
|
||||
func main() {
|
||||
Package("golang.org/x/crypto/blake2b")
|
||||
ConstraintExpr("amd64,gc,!purego")
|
||||
hashBlocksSSE4()
|
||||
Generate()
|
||||
}
|
||||
|
||||
func SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2 VecPhysical) {
|
||||
MOVO(v4, t1)
|
||||
MOVO(v5, v4)
|
||||
MOVO(t1, v5)
|
||||
MOVO(v6, t1)
|
||||
PUNPCKLQDQ(v6, t2)
|
||||
PUNPCKHQDQ(v7, v6)
|
||||
PUNPCKHQDQ(t2, v6)
|
||||
PUNPCKLQDQ(v7, t2)
|
||||
MOVO(t1, v7)
|
||||
MOVO(v2, t1)
|
||||
PUNPCKHQDQ(t2, v7)
|
||||
PUNPCKLQDQ(v3, t2)
|
||||
PUNPCKHQDQ(t2, v2)
|
||||
PUNPCKLQDQ(t1, t2)
|
||||
PUNPCKHQDQ(t2, v3)
|
||||
}
|
||||
|
||||
func SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2 VecPhysical) {
|
||||
MOVO(v4, t1)
|
||||
MOVO(v5, v4)
|
||||
MOVO(t1, v5)
|
||||
MOVO(v2, t1)
|
||||
PUNPCKLQDQ(v2, t2)
|
||||
PUNPCKHQDQ(v3, v2)
|
||||
PUNPCKHQDQ(t2, v2)
|
||||
PUNPCKLQDQ(v3, t2)
|
||||
MOVO(t1, v3)
|
||||
MOVO(v6, t1)
|
||||
PUNPCKHQDQ(t2, v3)
|
||||
PUNPCKLQDQ(v7, t2)
|
||||
PUNPCKHQDQ(t2, v6)
|
||||
PUNPCKLQDQ(t1, t2)
|
||||
PUNPCKHQDQ(t2, v7)
|
||||
}
|
||||
|
||||
func HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7 VecPhysical, m0, m1, m2, m3 Op, t0, c40, c48 VecPhysical) {
|
||||
PADDQ(m0, v0)
|
||||
PADDQ(m1, v1)
|
||||
PADDQ(v2, v0)
|
||||
PADDQ(v3, v1)
|
||||
PXOR(v0, v6)
|
||||
PXOR(v1, v7)
|
||||
PSHUFD(Imm(0xB1), v6, v6)
|
||||
PSHUFD(Imm(0xB1), v7, v7)
|
||||
PADDQ(v6, v4)
|
||||
PADDQ(v7, v5)
|
||||
PXOR(v4, v2)
|
||||
PXOR(v5, v3)
|
||||
PSHUFB(c40, v2)
|
||||
PSHUFB(c40, v3)
|
||||
PADDQ(m2, v0)
|
||||
PADDQ(m3, v1)
|
||||
PADDQ(v2, v0)
|
||||
PADDQ(v3, v1)
|
||||
PXOR(v0, v6)
|
||||
PXOR(v1, v7)
|
||||
PSHUFB(c48, v6)
|
||||
PSHUFB(c48, v7)
|
||||
PADDQ(v6, v4)
|
||||
PADDQ(v7, v5)
|
||||
PXOR(v4, v2)
|
||||
PXOR(v5, v3)
|
||||
MOVOU(v2, t0)
|
||||
PADDQ(v2, t0)
|
||||
PSRLQ(Imm(63), v2)
|
||||
PXOR(t0, v2)
|
||||
MOVOU(v3, t0)
|
||||
PADDQ(v3, t0)
|
||||
PSRLQ(Imm(63), v3)
|
||||
PXOR(t0, v3)
|
||||
}
|
||||
|
||||
func LOAD_MSG(m0, m1, m2, m3 VecPhysical, src GPPhysical, i0, i1, i2, i3, i4, i5, i6, i7 int) {
|
||||
MOVQ(Mem{Base: src}.Offset(i0*8), m0)
|
||||
PINSRQ(Imm(1), Mem{Base: src}.Offset(i1*8), m0)
|
||||
MOVQ(Mem{Base: src}.Offset(i2*8), m1)
|
||||
PINSRQ(Imm(1), Mem{Base: src}.Offset(i3*8), m1)
|
||||
MOVQ(Mem{Base: src}.Offset(i4*8), m2)
|
||||
PINSRQ(Imm(1), Mem{Base: src}.Offset(i5*8), m2)
|
||||
MOVQ(Mem{Base: src}.Offset(i6*8), m3)
|
||||
PINSRQ(Imm(1), Mem{Base: src}.Offset(i7*8), m3)
|
||||
}
|
||||
|
||||
func hashBlocksSSE4() {
|
||||
Implement("hashBlocksSSE4")
|
||||
Attributes(4)
|
||||
AllocLocal(288) // frame size = 272 + 16 byte alignment
|
||||
|
||||
Load(Param("h"), RAX)
|
||||
Load(Param("c"), RBX)
|
||||
Load(Param("flag"), RCX)
|
||||
Load(Param("blocks").Base(), RSI)
|
||||
Load(Param("blocks").Len(), RDI)
|
||||
|
||||
MOVQ(RSP, R10)
|
||||
ADDQ(Imm(15), R10)
|
||||
ANDQ(I32(-16), R10)
|
||||
|
||||
iv3 := iv3_DATA()
|
||||
MOVOU(iv3, X0)
|
||||
MOVO(X0, Mem{Base: R10}.Offset(0))
|
||||
XORQ(RCX, Mem{Base: R10}.Offset(0)) // 0(R10) = ·iv3 ^ (CX || 0)
|
||||
|
||||
c40 := c40_DATA()
|
||||
c48 := c48_DATA()
|
||||
MOVOU(c40, X13)
|
||||
MOVOU(c48, X14)
|
||||
|
||||
MOVOU(Mem{Base: AX}.Offset(0), X12)
|
||||
MOVOU(Mem{Base: AX}.Offset(16), X15)
|
||||
|
||||
MOVQ(Mem{Base: BX}.Offset(0), R8)
|
||||
MOVQ(Mem{Base: BX}.Offset(8), R9)
|
||||
|
||||
Label("loop")
|
||||
ADDQ(Imm(128), R8)
|
||||
CMPQ(R8, Imm(128))
|
||||
JGE(LabelRef("noinc"))
|
||||
INCQ(R9)
|
||||
|
||||
Label("noinc")
|
||||
MOVQ(R8, X8)
|
||||
PINSRQ(Imm(1), R9, X8)
|
||||
|
||||
iv0 := iv0_DATA()
|
||||
iv1 := iv1_DATA()
|
||||
iv2 := iv2_DATA()
|
||||
|
||||
MOVO(X12, X0)
|
||||
MOVO(X15, X1)
|
||||
MOVOU(Mem{Base: AX}.Offset(32), X2)
|
||||
MOVOU(Mem{Base: AX}.Offset(48), X3)
|
||||
MOVOU(iv0, X4)
|
||||
MOVOU(iv1, X5)
|
||||
MOVOU(iv2, X6)
|
||||
|
||||
PXOR(X8, X6)
|
||||
MOVO(Mem{Base: R10}.Offset(0), X7)
|
||||
|
||||
LOAD_MSG(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7)
|
||||
MOVO(X8, Mem{Base: R10}.Offset(16))
|
||||
MOVO(X9, Mem{Base: R10}.Offset(32))
|
||||
MOVO(X10, Mem{Base: R10}.Offset(48))
|
||||
MOVO(X11, Mem{Base: R10}.Offset(64))
|
||||
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, Mem{Base: R10}.Offset(80))
|
||||
MOVO(X9, Mem{Base: R10}.Offset(96))
|
||||
MOVO(X10, Mem{Base: R10}.Offset(112))
|
||||
MOVO(X11, Mem{Base: R10}.Offset(128))
|
||||
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, Mem{Base: R10}.Offset(144))
|
||||
MOVO(X9, Mem{Base: R10}.Offset(160))
|
||||
MOVO(X10, Mem{Base: R10}.Offset(176))
|
||||
MOVO(X11, Mem{Base: R10}.Offset(192))
|
||||
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, Mem{Base: R10}.Offset(208))
|
||||
MOVO(X9, Mem{Base: R10}.Offset(224))
|
||||
MOVO(X10, Mem{Base: R10}.Offset(240))
|
||||
MOVO(X11, Mem{Base: R10}.Offset(256))
|
||||
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, 11, 12, 5, 15, 8, 0, 2, 13)
|
||||
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, 10, 3, 7, 9, 14, 6, 1, 4)
|
||||
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, 7, 3, 13, 11, 9, 1, 12, 14)
|
||||
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, 2, 5, 4, 15, 6, 10, 0, 8)
|
||||
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, 9, 5, 2, 10, 0, 7, 4, 15)
|
||||
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, 14, 11, 6, 3, 1, 12, 8, 13)
|
||||
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, 2, 6, 0, 8, 12, 10, 11, 3)
|
||||
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, 4, 7, 15, 1, 13, 5, 14, 9)
|
||||
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, 12, 1, 14, 4, 5, 15, 13, 10)
|
||||
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, 0, 6, 9, 8, 7, 3, 2, 11)
|
||||
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, 13, 7, 12, 3, 11, 14, 1, 9)
|
||||
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, 5, 15, 8, 2, 0, 4, 6, 10)
|
||||
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, 6, 14, 11, 0, 15, 9, 3, 8)
|
||||
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, 12, 13, 1, 10, 2, 7, 4, 5)
|
||||
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, 10, 8, 7, 1, 2, 4, 6, 5)
|
||||
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, 15, 9, 3, 13, 11, 14, 12, 0)
|
||||
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, Mem{Base: R10}.Offset(16), Mem{Base: R10}.Offset(32), Mem{Base: R10}.Offset(48), Mem{Base: R10}.Offset(64), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, Mem{Base: R10}.Offset(80), Mem{Base: R10}.Offset(96), Mem{Base: R10}.Offset(112), Mem{Base: R10}.Offset(128), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, Mem{Base: R10}.Offset(144), Mem{Base: R10}.Offset(160), Mem{Base: R10}.Offset(176), Mem{Base: R10}.Offset(192), X11, X13, X14)
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, Mem{Base: R10}.Offset(208), Mem{Base: R10}.Offset(224), Mem{Base: R10}.Offset(240), Mem{Base: R10}.Offset(256), X11, X13, X14)
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9)
|
||||
|
||||
MOVOU(Mem{Base: AX}.Offset(32), X10)
|
||||
MOVOU(Mem{Base: AX}.Offset(48), X11)
|
||||
PXOR(X0, X12)
|
||||
PXOR(X1, X15)
|
||||
PXOR(X2, X10)
|
||||
PXOR(X3, X11)
|
||||
PXOR(X4, X12)
|
||||
PXOR(X5, X15)
|
||||
PXOR(X6, X10)
|
||||
PXOR(X7, X11)
|
||||
MOVOU(X10, Mem{Base: AX}.Offset(32))
|
||||
MOVOU(X11, Mem{Base: AX}.Offset(48))
|
||||
|
||||
LEAQ(Mem{Base: SI}.Offset(128), RSI)
|
||||
SUBQ(Imm(128), RDI)
|
||||
JNE(LabelRef("loop"))
|
||||
|
||||
MOVOU(X12, Mem{Base: AX}.Offset(0))
|
||||
MOVOU(X15, Mem{Base: AX}.Offset(16))
|
||||
|
||||
MOVQ(R8, Mem{Base: BX}.Offset(0))
|
||||
MOVQ(R9, Mem{Base: BX}.Offset(8))
|
||||
|
||||
RET()
|
||||
}
|
||||
|
||||
// #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~DATA SECTION~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
|
||||
|
||||
func iv0_DATA() Mem {
|
||||
if iv0_DATA_ptr != nil {
|
||||
return *iv0_DATA_ptr
|
||||
}
|
||||
|
||||
iv0 := GLOBL(ThatPeskyUnicodeDot+"iv0", NOPTR|RODATA)
|
||||
iv0_DATA_ptr = &iv0
|
||||
DATA(0x00, U64(0x6a09e667f3bcc908))
|
||||
DATA(0x08, U64(0xbb67ae8584caa73b))
|
||||
return iv0
|
||||
}
|
||||
|
||||
func iv1_DATA() Mem {
|
||||
if iv1_DATA_ptr != nil {
|
||||
return *iv1_DATA_ptr
|
||||
}
|
||||
|
||||
iv1 := GLOBL(ThatPeskyUnicodeDot+"iv1", NOPTR|RODATA)
|
||||
iv1_DATA_ptr = &iv1
|
||||
DATA(0x00, U64(0x3c6ef372fe94f82b))
|
||||
DATA(0x08, U64(0xa54ff53a5f1d36f1))
|
||||
return iv1
|
||||
}
|
||||
|
||||
func iv2_DATA() Mem {
|
||||
if iv2_DATA_ptr != nil {
|
||||
return *iv2_DATA_ptr
|
||||
}
|
||||
|
||||
iv2 := GLOBL(ThatPeskyUnicodeDot+"iv2", NOPTR|RODATA)
|
||||
iv2_DATA_ptr = &iv2
|
||||
DATA(0x00, U64(0x510e527fade682d1))
|
||||
DATA(0x08, U64(0x9b05688c2b3e6c1f))
|
||||
return iv2
|
||||
}
|
||||
|
||||
func iv3_DATA() Mem {
|
||||
if iv3_DATA_ptr != nil {
|
||||
return *iv3_DATA_ptr
|
||||
}
|
||||
|
||||
iv3 := GLOBL(ThatPeskyUnicodeDot+"iv3", NOPTR|RODATA)
|
||||
iv3_DATA_ptr = &iv3
|
||||
DATA(0x00, U64(0x1f83d9abfb41bd6b))
|
||||
DATA(0x08, U64(0x5be0cd19137e2179))
|
||||
return iv3
|
||||
}
|
||||
|
||||
func c40_DATA() Mem {
|
||||
if c40_DATA_ptr != nil {
|
||||
return *c40_DATA_ptr
|
||||
}
|
||||
|
||||
c40 := GLOBL(ThatPeskyUnicodeDot+"c40", NOPTR|RODATA)
|
||||
c40_DATA_ptr = &c40
|
||||
DATA(0x00, U64(0x0201000706050403))
|
||||
DATA(0x08, U64(0x0a09080f0e0d0c0b))
|
||||
return c40
|
||||
}
|
||||
|
||||
func c48_DATA() Mem {
|
||||
if c48_DATA_ptr != nil {
|
||||
return *c48_DATA_ptr
|
||||
}
|
||||
|
||||
c48 := GLOBL(ThatPeskyUnicodeDot+"c48", NOPTR|RODATA)
|
||||
c48_DATA_ptr = &c48
|
||||
DATA(0x00, U64(0x0100070605040302))
|
||||
DATA(0x08, U64(0x09080f0e0d0c0b0a))
|
||||
return c48
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module blake2b/_asm
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
@@ -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,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
// +build go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
+701
-4510
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.7,amd64,!gccgo,!appengine
|
||||
|
||||
package blake2b
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
func init() {
|
||||
useSSE4 = cpu.X86.HasSSE41
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte)
|
||||
|
||||
func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) {
|
||||
if useSSE4 {
|
||||
hashBlocksSSE4(h, c, flag, blocks)
|
||||
} else {
|
||||
hashBlocksGeneric(h, c, flag, blocks)
|
||||
}
|
||||
}
|
||||
+268
-1428
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !amd64 || purego || !gc
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package blake2b
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package blake2b
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
. "github.com/mmcloughlin/avo/reg"
|
||||
_ "golang.org/x/crypto/blake2s"
|
||||
)
|
||||
|
||||
//go:generate go run . -out ../blake2s_amd64.s -pkg blake2s
|
||||
|
||||
func main() {
|
||||
Package("golang.org/x/crypto/blake2s")
|
||||
ConstraintExpr("amd64,gc,!purego")
|
||||
hashBlocksSSE2()
|
||||
hashBlocksSSSE3()
|
||||
hashBlocksSSE4()
|
||||
Generate()
|
||||
}
|
||||
|
||||
func ROTL_SSE2(n uint64, t, v VecPhysical) {
|
||||
MOVO(v, t)
|
||||
PSLLL(Imm(n), t)
|
||||
PSRLL(Imm(32-n), v)
|
||||
PXOR(t, v)
|
||||
}
|
||||
|
||||
func ROTL_SSSE3(c, v VecPhysical) {
|
||||
PSHUFB(c, v)
|
||||
}
|
||||
|
||||
func ROUND_SSE2(v0, v1, v2, v3 VecPhysical, m0, m1, m2, m3 Mem, t VecPhysical) {
|
||||
PADDL(m0, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSE2(16, t, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(20, t, v1)
|
||||
PADDL(m1, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSE2(24, t, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(25, t, v1)
|
||||
PSHUFL(Imm(0x39), v1, v1)
|
||||
PSHUFL(Imm(0x4E), v2, v2)
|
||||
PSHUFL(Imm(0x93), v3, v3)
|
||||
PADDL(m2, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSE2(16, t, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(20, t, v1)
|
||||
PADDL(m3, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSE2(24, t, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(25, t, v1)
|
||||
PSHUFL(Imm(0x39), v3, v3)
|
||||
PSHUFL(Imm(0x4E), v2, v2)
|
||||
PSHUFL(Imm(0x93), v1, v1)
|
||||
}
|
||||
|
||||
func ROUND_SSSE3(v0, v1, v2, v3 VecPhysical, m0, m1, m2, m3 Op, t, c16, c8 VecPhysical) {
|
||||
PADDL(m0, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSSE3(c16, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(20, t, v1)
|
||||
PADDL(m1, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSSE3(c8, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(25, t, v1)
|
||||
PSHUFL(Imm(0x39), v1, v1)
|
||||
PSHUFL(Imm(0x4E), v2, v2)
|
||||
PSHUFL(Imm(0x93), v3, v3)
|
||||
PADDL(m2, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSSE3(c16, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(20, t, v1)
|
||||
PADDL(m3, v0)
|
||||
PADDL(v1, v0)
|
||||
PXOR(v0, v3)
|
||||
ROTL_SSSE3(c8, v3)
|
||||
PADDL(v3, v2)
|
||||
PXOR(v2, v1)
|
||||
ROTL_SSE2(25, t, v1)
|
||||
PSHUFL(Imm(0x39), v3, v3)
|
||||
PSHUFL(Imm(0x4E), v2, v2)
|
||||
PSHUFL(Imm(0x93), v1, v1)
|
||||
}
|
||||
|
||||
func LOAD_MSG_SSE4(m0, m1, m2, m3 VecPhysical, src GPPhysical, i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15 int) {
|
||||
// Hack to get Avo to emit a MOVL instruction with a VecPhysical as the destination
|
||||
Instruction(&ir.Instruction{Opcode: "MOVL", Operands: []Op{Mem{Base: src}.Offset(i0 * 4), m0}})
|
||||
PINSRD(Imm(1), Mem{Base: src}.Offset(i1*4), m0)
|
||||
PINSRD(Imm(2), Mem{Base: src}.Offset(i2*4), m0)
|
||||
PINSRD(Imm(3), Mem{Base: src}.Offset(i3*4), m0)
|
||||
Instruction(&ir.Instruction{Opcode: "MOVL", Operands: []Op{Mem{Base: src}.Offset(i4 * 4), m1}})
|
||||
PINSRD(Imm(1), Mem{Base: src}.Offset(i5*4), m1)
|
||||
PINSRD(Imm(2), Mem{Base: src}.Offset(i6*4), m1)
|
||||
PINSRD(Imm(3), Mem{Base: src}.Offset(i7*4), m1)
|
||||
Instruction(&ir.Instruction{Opcode: "MOVL", Operands: []Op{Mem{Base: src}.Offset(i8 * 4), m2}})
|
||||
PINSRD(Imm(1), Mem{Base: src}.Offset(i9*4), m2)
|
||||
PINSRD(Imm(2), Mem{Base: src}.Offset(i10*4), m2)
|
||||
PINSRD(Imm(3), Mem{Base: src}.Offset(i11*4), m2)
|
||||
Instruction(&ir.Instruction{Opcode: "MOVL", Operands: []Op{Mem{Base: src}.Offset(i12 * 4), m3}})
|
||||
PINSRD(Imm(1), Mem{Base: src}.Offset(i13*4), m3)
|
||||
PINSRD(Imm(2), Mem{Base: src}.Offset(i14*4), m3)
|
||||
PINSRD(Imm(3), Mem{Base: src}.Offset(i15*4), m3)
|
||||
}
|
||||
|
||||
func PRECOMPUTE_MSG(dst GPPhysical, off int, src, R8, R9, R10, R11, R12, R13, R14, R15 GPPhysical) {
|
||||
MOVQ(Mem{Base: src}.Offset(0*4), R8)
|
||||
MOVQ(Mem{Base: src}.Offset(2*4), R9)
|
||||
MOVQ(Mem{Base: src}.Offset(4*4), R10)
|
||||
MOVQ(Mem{Base: src}.Offset(6*4), R11)
|
||||
MOVQ(Mem{Base: src}.Offset(8*4), R12)
|
||||
MOVQ(Mem{Base: src}.Offset(10*4), R13)
|
||||
MOVQ(Mem{Base: src}.Offset(12*4), R14)
|
||||
MOVQ(Mem{Base: src}.Offset(14*4), R15)
|
||||
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(0*4+off+0))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(9*4+off+64))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(5*4+off+128))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(14*4+off+192))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(4*4+off+256))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(2*4+off+320))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(8*4+off+384))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(12*4+off+448))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(3*4+off+512))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(15*4+off+576))
|
||||
SHRQ(Imm(32), R8)
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(4*4+off+0))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(8*4+off+64))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(14*4+off+128))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(5*4+off+192))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(12*4+off+256))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(11*4+off+320))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(1*4+off+384))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(6*4+off+448))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(10*4+off+512))
|
||||
MOVL(R8L, Mem{Base: dst}.Offset(3*4+off+576))
|
||||
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(1*4+off+0))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(13*4+off+64))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(6*4+off+128))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(8*4+off+192))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(2*4+off+256))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(0*4+off+320))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(14*4+off+384))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(11*4+off+448))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(12*4+off+512))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(4*4+off+576))
|
||||
SHRQ(Imm(32), R9)
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(5*4+off+0))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(15*4+off+64))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(9*4+off+128))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(1*4+off+192))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(11*4+off+256))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(7*4+off+320))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(13*4+off+384))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(3*4+off+448))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(6*4+off+512))
|
||||
MOVL(R9L, Mem{Base: dst}.Offset(10*4+off+576))
|
||||
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(2*4+off+0))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(1*4+off+64))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(15*4+off+128))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(10*4+off+192))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(6*4+off+256))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(8*4+off+320))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(3*4+off+384))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(13*4+off+448))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(14*4+off+512))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(5*4+off+576))
|
||||
SHRQ(Imm(32), R10)
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(6*4+off+0))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(11*4+off+64))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(2*4+off+128))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(9*4+off+192))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(1*4+off+256))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(13*4+off+320))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(4*4+off+384))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(8*4+off+448))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(15*4+off+512))
|
||||
MOVL(R10L, Mem{Base: dst}.Offset(7*4+off+576))
|
||||
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(3*4+off+0))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(7*4+off+64))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(13*4+off+128))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(12*4+off+192))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(10*4+off+256))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(1*4+off+320))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(9*4+off+384))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(14*4+off+448))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(0*4+off+512))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(6*4+off+576))
|
||||
SHRQ(Imm(32), R11)
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(7*4+off+0))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(14*4+off+64))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(10*4+off+128))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(0*4+off+192))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(5*4+off+256))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(9*4+off+320))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(12*4+off+384))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(1*4+off+448))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(13*4+off+512))
|
||||
MOVL(R11L, Mem{Base: dst}.Offset(2*4+off+576))
|
||||
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(8*4+off+0))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(5*4+off+64))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(4*4+off+128))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(15*4+off+192))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(14*4+off+256))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(3*4+off+320))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(11*4+off+384))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(10*4+off+448))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(7*4+off+512))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(1*4+off+576))
|
||||
SHRQ(Imm(32), R12)
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(12*4+off+0))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(2*4+off+64))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(11*4+off+128))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(4*4+off+192))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(0*4+off+256))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(15*4+off+320))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(10*4+off+384))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(7*4+off+448))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(5*4+off+512))
|
||||
MOVL(R12L, Mem{Base: dst}.Offset(9*4+off+576))
|
||||
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(9*4+off+0))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(4*4+off+64))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(8*4+off+128))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(13*4+off+192))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(3*4+off+256))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(5*4+off+320))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(7*4+off+384))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(15*4+off+448))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(11*4+off+512))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(0*4+off+576))
|
||||
SHRQ(Imm(32), R13)
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(13*4+off+0))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(10*4+off+64))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(0*4+off+128))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(3*4+off+192))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(9*4+off+256))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(6*4+off+320))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(15*4+off+384))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(4*4+off+448))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(2*4+off+512))
|
||||
MOVL(R13L, Mem{Base: dst}.Offset(12*4+off+576))
|
||||
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(10*4+off+0))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(12*4+off+64))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(1*4+off+128))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(6*4+off+192))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(13*4+off+256))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(4*4+off+320))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(0*4+off+384))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(2*4+off+448))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(8*4+off+512))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(14*4+off+576))
|
||||
SHRQ(Imm(32), R14)
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(14*4+off+0))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(3*4+off+64))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(7*4+off+128))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(2*4+off+192))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(15*4+off+256))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(12*4+off+320))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(6*4+off+384))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(0*4+off+448))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(9*4+off+512))
|
||||
MOVL(R14L, Mem{Base: dst}.Offset(11*4+off+576))
|
||||
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(11*4+off+0))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(0*4+off+64))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(12*4+off+128))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(7*4+off+192))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(8*4+off+256))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(14*4+off+320))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(2*4+off+384))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(5*4+off+448))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(1*4+off+512))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(13*4+off+576))
|
||||
SHRQ(Imm(32), R15)
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(15*4+off+0))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(6*4+off+64))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(3*4+off+128))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(11*4+off+192))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(7*4+off+256))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(10*4+off+320))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(5*4+off+384))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(9*4+off+448))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(4*4+off+512))
|
||||
MOVL(R15L, Mem{Base: dst}.Offset(8*4+off+576))
|
||||
}
|
||||
|
||||
func BLAKE2s_SSE2() {
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15)
|
||||
for i := 0; i < 10; i++ {
|
||||
ROUND_SSE2(X4, X5, X6, X7, Mem{Base: BP}.Offset(16+64*i), Mem{Base: BP}.Offset(32+64*i), Mem{Base: BP}.Offset(48+64*i), Mem{Base: BP}.Offset(64+64*i), X8)
|
||||
}
|
||||
}
|
||||
|
||||
func BLAKE2s_SSSE3() {
|
||||
PRECOMPUTE_MSG(BP, 16, SI, R8, R9, R10, R11, R12, R13, R14, R15)
|
||||
for i := 0; i < 10; i++ {
|
||||
ROUND_SSSE3(X4, X5, X6, X7, Mem{Base: BP}.Offset(16+64*i), Mem{Base: BP}.Offset(32+64*i), Mem{Base: BP}.Offset(48+64*i), Mem{Base: BP}.Offset(64+64*i), X8, X13, X14)
|
||||
}
|
||||
}
|
||||
|
||||
func 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)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
LOAD_MSG_SSE4(X8, X9, X10, X11, SI, 10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0)
|
||||
ROUND_SSSE3(X4, X5, X6, X7, X8, X9, X10, X11, X8, X13, X14)
|
||||
}
|
||||
|
||||
func HASH_BLOCKS(h, c, flag, blocks_base, blocks_len Mem, BLAKE2s_FUNC func()) {
|
||||
MOVQ(h, RAX)
|
||||
MOVQ(c, RBX)
|
||||
MOVL(flag, ECX)
|
||||
MOVQ(blocks_base, RSI)
|
||||
MOVQ(blocks_len, RDX)
|
||||
|
||||
MOVQ(RSP, RBP)
|
||||
ADDQ(Imm(15), RBP)
|
||||
ANDQ(I32(^15), RBP)
|
||||
|
||||
MOVQ(Mem{Base: BX}.Offset(0), R9)
|
||||
MOVQ(R9, Mem{Base: BP}.Offset(0))
|
||||
MOVQ(RCX, Mem{Base: BP}.Offset(8))
|
||||
|
||||
MOVOU(Mem{Base: AX}.Offset(0), X0)
|
||||
MOVOU(Mem{Base: AX}.Offset(16), X1)
|
||||
|
||||
iv0 := iv0_DATA()
|
||||
iv1 := iv1_DATA()
|
||||
MOVOU(iv0, X2)
|
||||
MOVOU(iv1, X3)
|
||||
|
||||
counter := counter_DATA()
|
||||
rol16 := rol16_DATA()
|
||||
rol8 := rol8_DATA()
|
||||
MOVOU(counter, X12)
|
||||
MOVOU(rol16, X13)
|
||||
MOVOU(rol8, X14)
|
||||
MOVO(Mem{Base: BP}.Offset(0), X15)
|
||||
|
||||
Label("loop")
|
||||
MOVO(X0, X4)
|
||||
MOVO(X1, X5)
|
||||
MOVO(X2, X6)
|
||||
MOVO(X3, X7)
|
||||
|
||||
PADDQ(X12, X15)
|
||||
PXOR(X15, X7)
|
||||
|
||||
BLAKE2s_FUNC()
|
||||
|
||||
PXOR(X4, X0)
|
||||
PXOR(X5, X1)
|
||||
PXOR(X6, X0)
|
||||
PXOR(X7, X1)
|
||||
|
||||
LEAQ(Mem{Base: SI}.Offset(64), RSI)
|
||||
SUBQ(Imm(64), RDX)
|
||||
JNE(LabelRef("loop"))
|
||||
|
||||
MOVO(X15, Mem{Base: BP}.Offset(0))
|
||||
MOVQ(Mem{Base: BP}.Offset(0), R9)
|
||||
MOVQ(R9, Mem{Base: BX}.Offset(0))
|
||||
|
||||
MOVOU(X0, Mem{Base: AX}.Offset(0))
|
||||
MOVOU(X1, Mem{Base: AX}.Offset(16))
|
||||
}
|
||||
|
||||
func hashBlocksSSE2() {
|
||||
Implement("hashBlocksSSE2")
|
||||
Attributes(0)
|
||||
AllocLocal(672) // frame = 656 + 16 byte alignment
|
||||
|
||||
h := NewParamAddr("h", 0)
|
||||
c := NewParamAddr("c", 8)
|
||||
flag := NewParamAddr("flag", 16)
|
||||
blocks_base := NewParamAddr("blocks_base", 24)
|
||||
blocks_len := NewParamAddr("blocks_len", 32)
|
||||
|
||||
HASH_BLOCKS(h, c, flag, blocks_base, blocks_len, BLAKE2s_SSE2)
|
||||
RET()
|
||||
}
|
||||
|
||||
func hashBlocksSSSE3() {
|
||||
Implement("hashBlocksSSSE3")
|
||||
Attributes(0)
|
||||
AllocLocal(672) // frame = 656 + 16 byte alignment
|
||||
|
||||
h := NewParamAddr("h", 0)
|
||||
c := NewParamAddr("c", 8)
|
||||
flag := NewParamAddr("flag", 16)
|
||||
blocks_base := NewParamAddr("blocks_base", 24)
|
||||
blocks_len := NewParamAddr("blocks_len", 32)
|
||||
|
||||
HASH_BLOCKS(h, c, flag, blocks_base, blocks_len, BLAKE2s_SSSE3)
|
||||
RET()
|
||||
}
|
||||
|
||||
func hashBlocksSSE4() {
|
||||
Implement("hashBlocksSSE4")
|
||||
Attributes(0)
|
||||
AllocLocal(32) // frame = 16 + 16 byte alignment
|
||||
|
||||
h := NewParamAddr("h", 0)
|
||||
c := NewParamAddr("c", 8)
|
||||
flag := NewParamAddr("flag", 16)
|
||||
blocks_base := NewParamAddr("blocks_base", 24)
|
||||
blocks_len := NewParamAddr("blocks_len", 32)
|
||||
|
||||
HASH_BLOCKS(h, c, flag, blocks_base, blocks_len, BLAKE2s_SSE4)
|
||||
RET()
|
||||
}
|
||||
|
||||
// ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~DATA SECTION~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
|
||||
|
||||
var iv0_DATA_ptr, iv1_DATA_ptr, rol16_DATA_ptr, rol8_DATA_ptr, counter_DATA_ptr *Mem
|
||||
|
||||
func iv0_DATA() Mem {
|
||||
if iv0_DATA_ptr != nil {
|
||||
return *iv0_DATA_ptr
|
||||
}
|
||||
|
||||
iv0_DATA := GLOBL("iv0", NOPTR|RODATA)
|
||||
iv0_DATA_ptr = &iv0_DATA
|
||||
DATA(0x00, U32(0x6a09e667))
|
||||
DATA(0x04, U32(0xbb67ae85))
|
||||
DATA(0x08, U32(0x3c6ef372))
|
||||
DATA(0x0c, U32(0xa54ff53a))
|
||||
return iv0_DATA
|
||||
}
|
||||
|
||||
func iv1_DATA() Mem {
|
||||
if iv1_DATA_ptr != nil {
|
||||
return *iv1_DATA_ptr
|
||||
}
|
||||
|
||||
iv1_DATA := GLOBL("iv1", NOPTR|RODATA)
|
||||
iv1_DATA_ptr = &iv1_DATA
|
||||
DATA(0x00, U32(0x510e527f))
|
||||
DATA(0x04, U32(0x9b05688c))
|
||||
DATA(0x08, U32(0x1f83d9ab))
|
||||
DATA(0x0c, U32(0x5be0cd19))
|
||||
return iv1_DATA
|
||||
}
|
||||
|
||||
func rol16_DATA() Mem {
|
||||
if rol16_DATA_ptr != nil {
|
||||
return *rol16_DATA_ptr
|
||||
}
|
||||
|
||||
rol16_DATA := GLOBL("rol16", NOPTR|RODATA)
|
||||
rol16_DATA_ptr = &rol16_DATA
|
||||
DATA(0x00, U64(0x0504070601000302))
|
||||
DATA(0x08, U64(0x0D0C0F0E09080B0A))
|
||||
return rol16_DATA
|
||||
}
|
||||
|
||||
func rol8_DATA() Mem {
|
||||
if rol8_DATA_ptr != nil {
|
||||
return *rol8_DATA_ptr
|
||||
}
|
||||
|
||||
rol8_DATA := GLOBL("rol8", NOPTR|RODATA)
|
||||
rol8_DATA_ptr = &rol8_DATA
|
||||
DATA(0x00, U64(0x0407060500030201))
|
||||
DATA(0x08, U64(0x0C0F0E0D080B0A09))
|
||||
return rol8_DATA
|
||||
}
|
||||
|
||||
func counter_DATA() Mem {
|
||||
if counter_DATA_ptr != nil {
|
||||
return *counter_DATA_ptr
|
||||
}
|
||||
|
||||
counter_DATA := GLOBL("counter", NOPTR|RODATA)
|
||||
counter_DATA_ptr = &counter_DATA
|
||||
DATA(0x00, U64(0x0000000000000040))
|
||||
DATA(0x08, U64(0x0000000000000000))
|
||||
return counter_DATA
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module blake2s/_asm
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
+1
-11
@@ -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
|
||||
//
|
||||
@@ -16,10 +14,9 @@
|
||||
//
|
||||
// BLAKE2X is a construction to compute hash values larger than 32 bytes. It
|
||||
// can produce hash values between 0 and 65535 bytes.
|
||||
package blake2s
|
||||
package blake2s // import "golang.org/x/crypto/blake2s"
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -56,13 +53,6 @@ func Sum256(data []byte) [Size]byte {
|
||||
// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash.
|
||||
func New256(key []byte) (hash.Hash, error) { return newDigest(Size, key) }
|
||||
|
||||
func init() {
|
||||
crypto.RegisterHash(crypto.BLAKE2s_256, func() hash.Hash {
|
||||
h, _ := New256(nil)
|
||||
return h
|
||||
})
|
||||
}
|
||||
|
||||
// New128 returns a new hash.Hash computing the BLAKE2s-128 checksum given a
|
||||
// non-empty key. Note that a 128-bit digest is too small to be secure as a
|
||||
// cryptographic hash and should only be used as a MAC, thus the key argument
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build 386 && gc && !purego
|
||||
// +build 386,!gccgo,!appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+49
-43
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build 386 && gc && !purego
|
||||
// +build 386,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -297,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
|
||||
@@ -319,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
|
||||
@@ -345,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)
|
||||
@@ -363,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
|
||||
@@ -418,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,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
+426
-2161
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!amd64 && !386) || !gc || purego
|
||||
// +build !amd64,!386 gccgo appengine
|
||||
|
||||
package blake2s
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package blake2s
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
newHash256 := func() hash.Hash {
|
||||
h, _ := New256(nil)
|
||||
return h
|
||||
}
|
||||
|
||||
crypto.RegisterHash(crypto.BLAKE2s_256, newHash256)
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
|
||||
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
|
||||
// golang.org/x/crypto/chacha20poly1305).
|
||||
package blowfish
|
||||
package blowfish // import "golang.org/x/crypto/blowfish"
|
||||
|
||||
// The code is a port of Bruce Schneier's C implementation.
|
||||
// See https://www.schneier.com/blowfish.html.
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
// elliptic curve. This package is frozen, and not implemented in constant time.
|
||||
// There is a more complete implementation at github.com/cloudflare/bn256, but
|
||||
// note that it suffers from the same security issues of the underlying curve.
|
||||
package bn256
|
||||
package bn256 // import "golang.org/x/crypto/bn256"
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
package bn256
|
||||
|
||||
// For details of the algorithms used, see "Multiplication and Squaring on
|
||||
// Pairing-Friendly Fields", Devegili et al.
|
||||
// Pairing-Friendly Fields, Devegili et al.
|
||||
// http://eprint.iacr.org/2006/471.pdf.
|
||||
|
||||
import (
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
package bn256
|
||||
|
||||
// For details of the algorithms used, see "Multiplication and Squaring on
|
||||
// Pairing-Friendly Fields", Devegili et al.
|
||||
// Pairing-Friendly Fields, Devegili et al.
|
||||
// http://eprint.iacr.org/2006/471.pdf.
|
||||
|
||||
import (
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
package bn256
|
||||
|
||||
// For details of the algorithms used, see "Multiplication and Squaring on
|
||||
// Pairing-Friendly Fields", Devegili et al.
|
||||
// Pairing-Friendly Fields, Devegili et al.
|
||||
// http://eprint.iacr.org/2006/471.pdf.
|
||||
|
||||
import (
|
||||
|
||||
+5
-8
@@ -11,12 +11,9 @@
|
||||
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
|
||||
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
|
||||
// golang.org/x/crypto/chacha20poly1305).
|
||||
package cast5
|
||||
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,16 +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
|
||||
|
||||
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,13 +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 && !ppc64 && !ppc64le) || !gc || purego
|
||||
|
||||
package chacha20
|
||||
|
||||
const bufSize = blockSize
|
||||
|
||||
func (s *Cipher) xorKeyStreamBlocks(dst, src []byte) {
|
||||
s.xorKeyStreamBlocksGeneric(dst, src)
|
||||
}
|
||||
@@ -1,16 +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 && (ppc64 || ppc64le)
|
||||
|
||||
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,501 +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 && (ppc64 || ppc64le)
|
||||
|
||||
#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
|
||||
|
||||
// for VPERMXOR
|
||||
#define MASK R18
|
||||
|
||||
DATA consts<>+0x00(SB)/4, $0x61707865
|
||||
DATA consts<>+0x04(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x08(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x0c(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x10(SB)/4, $0x00000001
|
||||
DATA consts<>+0x14(SB)/4, $0x00000000
|
||||
DATA consts<>+0x18(SB)/4, $0x00000000
|
||||
DATA consts<>+0x1c(SB)/4, $0x00000000
|
||||
DATA consts<>+0x20(SB)/4, $0x00000004
|
||||
DATA consts<>+0x24(SB)/4, $0x00000000
|
||||
DATA consts<>+0x28(SB)/4, $0x00000000
|
||||
DATA consts<>+0x2c(SB)/4, $0x00000000
|
||||
DATA consts<>+0x30(SB)/4, $0x0e0f0c0d
|
||||
DATA consts<>+0x34(SB)/4, $0x0a0b0809
|
||||
DATA consts<>+0x38(SB)/4, $0x06070405
|
||||
DATA consts<>+0x3c(SB)/4, $0x02030001
|
||||
DATA consts<>+0x40(SB)/4, $0x0d0e0f0c
|
||||
DATA consts<>+0x44(SB)/4, $0x090a0b08
|
||||
DATA consts<>+0x48(SB)/4, $0x05060704
|
||||
DATA consts<>+0x4c(SB)/4, $0x01020300
|
||||
DATA consts<>+0x50(SB)/4, $0x61707865
|
||||
DATA consts<>+0x54(SB)/4, $0x61707865
|
||||
DATA consts<>+0x58(SB)/4, $0x61707865
|
||||
DATA consts<>+0x5c(SB)/4, $0x61707865
|
||||
DATA consts<>+0x60(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x64(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x68(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x6c(SB)/4, $0x3320646e
|
||||
DATA consts<>+0x70(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x74(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x78(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x7c(SB)/4, $0x79622d32
|
||||
DATA consts<>+0x80(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x84(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x88(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x8c(SB)/4, $0x6b206574
|
||||
DATA consts<>+0x90(SB)/4, $0x00000000
|
||||
DATA consts<>+0x94(SB)/4, $0x00000001
|
||||
DATA consts<>+0x98(SB)/4, $0x00000002
|
||||
DATA consts<>+0x9c(SB)/4, $0x00000003
|
||||
DATA consts<>+0xa0(SB)/4, $0x11223300
|
||||
DATA consts<>+0xa4(SB)/4, $0x55667744
|
||||
DATA consts<>+0xa8(SB)/4, $0x99aabb88
|
||||
DATA consts<>+0xac(SB)/4, $0xddeeffcc
|
||||
DATA consts<>+0xb0(SB)/4, $0x22330011
|
||||
DATA consts<>+0xb4(SB)/4, $0x66774455
|
||||
DATA consts<>+0xb8(SB)/4, $0xaabb8899
|
||||
DATA consts<>+0xbc(SB)/4, $0xeeffccdd
|
||||
GLOBL consts<>(SB), RODATA, $0xc0
|
||||
|
||||
#ifdef GOARCH_ppc64
|
||||
#define BE_XXBRW_INIT() \
|
||||
LVSL (R0)(R0), V24 \
|
||||
VSPLTISB $3, V25 \
|
||||
VXOR V24, V25, V24 \
|
||||
|
||||
#define BE_XXBRW(vr) VPERM vr, vr, V24, vr
|
||||
#else
|
||||
#define BE_XXBRW_INIT()
|
||||
#define BE_XXBRW(vr)
|
||||
#endif
|
||||
|
||||
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
|
||||
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
|
||||
MOVD out+0(FP), OUT
|
||||
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
|
||||
// for VPERMXOR
|
||||
MOVD $consts<>+0xa0(SB), MASK
|
||||
MOVD $16, R20
|
||||
// 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
|
||||
|
||||
BE_XXBRW_INIT()
|
||||
|
||||
// V28
|
||||
LXVW4X (CONSTBASE)(R11), VS60
|
||||
|
||||
// Load mask constants for VPERMXOR
|
||||
LXVW4X (MASK)(R0), V20
|
||||
LXVW4X (MASK)(R20), V21
|
||||
|
||||
// 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
|
||||
PCALIGN $16
|
||||
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
|
||||
PCALIGN $16
|
||||
loop_vsx:
|
||||
VADDUWM V0, V4, V0
|
||||
VADDUWM V1, V5, V1
|
||||
VADDUWM V2, V6, V2
|
||||
VADDUWM V3, V7, V3
|
||||
|
||||
VPERMXOR V12, V0, V21, V12
|
||||
VPERMXOR V13, V1, V21, V13
|
||||
VPERMXOR V14, V2, V21, V14
|
||||
VPERMXOR V15, V3, V21, 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
|
||||
|
||||
VPERMXOR V12, V0, V20, V12
|
||||
VPERMXOR V13, V1, V20, V13
|
||||
VPERMXOR V14, V2, V20, V14
|
||||
VPERMXOR V15, V3, V20, 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
|
||||
|
||||
VPERMXOR V15, V0, V21, V15
|
||||
VPERMXOR V12, V1, V21, V12
|
||||
VPERMXOR V13, V2, V21, V13
|
||||
VPERMXOR V14, V3, V21, 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
|
||||
|
||||
VPERMXOR V15, V0, V20, V15
|
||||
VPERMXOR V12, V1, V20, V12
|
||||
VPERMXOR V13, V2, V20, V13
|
||||
VPERMXOR V14, V3, V20, 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
|
||||
BDNZ loop_vsx
|
||||
|
||||
VADDUWM V12, V26, V12
|
||||
|
||||
VMRGEW V0, V1, V27
|
||||
VMRGEW V2, V3, V28
|
||||
|
||||
VMRGOW V0, V1, V0
|
||||
VMRGOW V2, V3, V2
|
||||
|
||||
VMRGEW V4, V5, V29
|
||||
VMRGEW V6, V7, V30
|
||||
|
||||
XXPERMDI VS32, VS34, $0, VS33
|
||||
XXPERMDI VS32, VS34, $3, VS35
|
||||
XXPERMDI VS59, VS60, $0, VS32
|
||||
XXPERMDI VS59, VS60, $3, VS34
|
||||
|
||||
VMRGOW V4, V5, V4
|
||||
VMRGOW V6, V7, V6
|
||||
|
||||
VMRGEW V8, V9, V27
|
||||
VMRGEW V10, V11, V28
|
||||
|
||||
XXPERMDI VS36, VS38, $0, VS37
|
||||
XXPERMDI VS36, VS38, $3, VS39
|
||||
XXPERMDI VS61, VS62, $0, VS36
|
||||
XXPERMDI VS61, VS62, $3, VS38
|
||||
|
||||
VMRGOW V8, V9, V8
|
||||
VMRGOW V10, V11, V10
|
||||
|
||||
VMRGEW V12, V13, V29
|
||||
VMRGEW V14, V15, V30
|
||||
|
||||
XXPERMDI VS40, VS42, $0, VS41
|
||||
XXPERMDI VS40, VS42, $3, VS43
|
||||
XXPERMDI VS59, VS60, $0, VS40
|
||||
XXPERMDI VS59, VS60, $3, VS42
|
||||
|
||||
VMRGOW V12, V13, V12
|
||||
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
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(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
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(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
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(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
|
||||
|
||||
BE_XXBRW(V0)
|
||||
BE_XXBRW(V4)
|
||||
BE_XXBRW(V8)
|
||||
BE_XXBRW(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
|
||||
MOVWZ (CNT), R14
|
||||
ADD BLOCKS, R14
|
||||
MOVWZ 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
|
||||
PCALIGN $16
|
||||
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)
|
||||
BDNZ 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,27 +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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
module chacha20poly1305/_asm
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/mmcloughlin/avo v0.6.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
@@ -2,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
|
||||
// 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,14 +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 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"
|
||||
)
|
||||
|
||||
@@ -25,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])
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
|
||||
setupState(&state, &c.key, nonce)
|
||||
|
||||
ret, out := sliceForAppend(dst, len(plaintext)+16)
|
||||
if alias.InexactOverlap(out, plaintext) {
|
||||
if subtle.InexactOverlap(out, plaintext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
|
||||
@@ -72,7 +72,7 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
|
||||
|
||||
ciphertext = ciphertext[:len(ciphertext)-16]
|
||||
ret, out := sliceForAppend(dst, len(ciphertext))
|
||||
if alias.InexactOverlap(out, ciphertext) {
|
||||
if subtle.InexactOverlap(out, ciphertext) {
|
||||
panic("chacha20poly1305: invalid buffer overlap")
|
||||
}
|
||||
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
|
||||
|
||||
+2192
-9259
File diff suppressed because it is too large
Load Diff
@@ -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,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !amd64 || !gc || purego
|
||||
// +build !amd64 !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])
|
||||
|
||||
+46
-119
@@ -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,24 +394,11 @@ 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
|
||||
b := s.read(1)[0]
|
||||
|
||||
// ITU-T X.690, section 8.19.2:
|
||||
// The subidentifier shall be encoded in the fewest possible octets,
|
||||
// that is, the leading octet of the subidentifier shall not have the value 0x80.
|
||||
if i == 0 && b == 0x80 {
|
||||
return false
|
||||
}
|
||||
|
||||
ret |= int(b & 0x7f)
|
||||
if b&0x80 == 0 {
|
||||
*out = ret
|
||||
@@ -506,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 {
|
||||
@@ -554,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 ||
|
||||
@@ -567,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 {
|
||||
@@ -576,7 +497,7 @@ func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
paddingBits := bytes[0]
|
||||
paddingBits := uint8(bytes[0])
|
||||
if paddingBits != 0 {
|
||||
return false
|
||||
}
|
||||
@@ -676,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")
|
||||
}
|
||||
@@ -733,14 +661,13 @@ func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN
|
||||
// explicitly tagged with tag into out and advances. If no element with a
|
||||
// matching tag is present, it sets "out" to defaultValue instead. It reports
|
||||
// whether the read was successful.
|
||||
func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue bool) bool {
|
||||
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
|
||||
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
|
||||
// It reports whether the operation was successful.
|
||||
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
|
||||
var present bool
|
||||
var child String
|
||||
if !s.ReadOptionalASN1(&child, &present, tag) {
|
||||
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -749,7 +676,7 @@ func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue b
|
||||
return true
|
||||
}
|
||||
|
||||
return child.ReadASN1Boolean(out)
|
||||
return s.ReadASN1Boolean(out)
|
||||
}
|
||||
|
||||
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Package asn1 contains supporting types for parsing and building ASN.1
|
||||
// messages with the cryptobyte package.
|
||||
package asn1
|
||||
package asn1 // import "golang.org/x/crypto/cryptobyte/asn1"
|
||||
|
||||
// Tag represents an ASN.1 identifier octet, consisting of a tag number
|
||||
// (indicating a type) and class (such as context-specific or constructed).
|
||||
|
||||
@@ -115,28 +115,6 @@ func TestReadASN1OptionalInteger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultBool = false
|
||||
|
||||
var optionalBoolTestData = []readASN1Test{
|
||||
{"empty", []byte{}, 0xa0, true, false},
|
||||
{"invalid", []byte{0xa1, 0x3, 0x1, 0x2, 0x7f}, 0xa1, false, defaultBool},
|
||||
{"missing", []byte{0xa1, 0x3, 0x1, 0x1, 0x7f}, 0xa0, true, defaultBool},
|
||||
{"present", []byte{0xa1, 0x3, 0x1, 0x1, 0xff}, 0xa1, true, true},
|
||||
}
|
||||
|
||||
func TestReadASN1OptionalBoolean(t *testing.T) {
|
||||
for _, test := range optionalBoolTestData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
in := String(test.in)
|
||||
var out bool
|
||||
ok := in.ReadOptionalASN1Boolean(&out, test.tag, defaultBool)
|
||||
if ok != test.ok || ok && out != test.out.(bool) {
|
||||
t.Errorf("in.ReadOptionalASN1Boolean() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadASN1IntegerSigned(t *testing.T) {
|
||||
testData64 := []struct {
|
||||
in []byte
|
||||
@@ -176,32 +154,6 @@ func TestReadASN1IntegerSigned(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Repeat the same cases, reading into a []byte.
|
||||
t.Run("bytes", func(t *testing.T) {
|
||||
for i, test := range testData64 {
|
||||
in := String(test.in)
|
||||
var out []byte
|
||||
ok := in.ReadASN1Integer(&out)
|
||||
if test.out < 0 {
|
||||
if ok {
|
||||
t.Errorf("#%d: in.ReadASN1Integer(%d) = %v, want false", i, test.out, ok)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v, want true", i, ok)
|
||||
continue
|
||||
}
|
||||
n := new(big.Int).SetBytes(out).Int64()
|
||||
if n != test.out {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %x, want %d", i, ok, out, test.out)
|
||||
}
|
||||
if out[0] == 0 && len(out) > 1 {
|
||||
t.Errorf("#%d: in.ReadASN1Integer() = %v; out = %x, has leading zeroes", i, ok, out)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Repeat with the implicit-tagging functions
|
||||
t.Run("WithTag", func(t *testing.T) {
|
||||
for i, test := range testData64 {
|
||||
@@ -295,10 +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
|
||||
{[]byte{6, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
|
||||
}
|
||||
|
||||
for i, test := range testData {
|
||||
@@ -363,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
|
||||
@@ -418,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
-23
@@ -95,16 +95,6 @@ func (b *Builder) AddUint32(v uint32) {
|
||||
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint48 appends a big-endian, 48-bit value to the byte string.
|
||||
func (b *Builder) AddUint48(v uint64) {
|
||||
b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddUint64 appends a big-endian, 64-bit value to the byte string.
|
||||
func (b *Builder) AddUint64(v uint64) {
|
||||
b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// AddBytes appends a sequence of bytes to the byte string.
|
||||
func (b *Builder) AddBytes(v []byte) {
|
||||
b.add(v...)
|
||||
@@ -116,13 +106,13 @@ func (b *Builder) AddBytes(v []byte) {
|
||||
// supplied to them. The child builder passed to the continuation can be used
|
||||
// to build the content of the length-prefixed sequence. For example:
|
||||
//
|
||||
// parent := cryptobyte.NewBuilder()
|
||||
// parent.AddUint8LengthPrefixed(func (child *Builder) {
|
||||
// child.AddUint8(42)
|
||||
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
|
||||
// grandchild.AddUint8(5)
|
||||
// })
|
||||
// })
|
||||
// parent := cryptobyte.NewBuilder()
|
||||
// parent.AddUint8LengthPrefixed(func (child *Builder) {
|
||||
// child.AddUint8(42)
|
||||
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
|
||||
// grandchild.AddUint8(5)
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// It is an error to write more bytes to the child than allowed by the reserved
|
||||
// length prefix. After the continuation returns, the child must be considered
|
||||
@@ -308,9 +298,9 @@ func (b *Builder) add(bytes ...byte) {
|
||||
b.result = append(b.result, bytes...)
|
||||
}
|
||||
|
||||
// Unwrite rolls back non-negative n bytes written directly to the Builder.
|
||||
// An attempt by a child builder passed to a continuation to unwrite bytes
|
||||
// from its parent will panic.
|
||||
// Unwrite rolls back n bytes written directly to the Builder. An attempt by a
|
||||
// child builder passed to a continuation to unwrite bytes from its parent will
|
||||
// panic.
|
||||
func (b *Builder) Unwrite(n int) {
|
||||
if b.err != nil {
|
||||
return
|
||||
@@ -322,9 +312,6 @@ func (b *Builder) Unwrite(n int) {
|
||||
if length < 0 {
|
||||
panic("cryptobyte: internal error")
|
||||
}
|
||||
if n < 0 {
|
||||
panic("cryptobyte: attempted to unwrite negative number of bytes")
|
||||
}
|
||||
if n > length {
|
||||
panic("cryptobyte: attempted to unwrite more than was written")
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func TestUint24(t *testing.T) {
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint32
|
||||
if !s.ReadUint24(&v) {
|
||||
t.Error("ReadUint24() = false, want true")
|
||||
t.Error("ReadUint8() = false, want true")
|
||||
}
|
||||
if v != 0xfffefd {
|
||||
t.Errorf("v = %d, want fffefd", v)
|
||||
@@ -169,7 +169,7 @@ func TestUint32(t *testing.T) {
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint32
|
||||
if !s.ReadUint32(&v) {
|
||||
t.Error("ReadUint32() = false, want true")
|
||||
t.Error("ReadUint8() = false, want true")
|
||||
}
|
||||
if v != 0xfffefdfc {
|
||||
t.Errorf("v = %x, want fffefdfc", v)
|
||||
@@ -179,47 +179,6 @@ func TestUint32(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint48(t *testing.T) {
|
||||
var b Builder
|
||||
var u uint64 = 0xfefcff3cfdfc
|
||||
b.AddUint48(u)
|
||||
if err := builderBytesEq(&b, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint48(&v) {
|
||||
t.Error("ReadUint48() = false, want true")
|
||||
}
|
||||
if v != u {
|
||||
t.Errorf("v = %x, want %x", v, u)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint64(0xf2fefefcff3cfdfc)
|
||||
if err := builderBytesEq(&b, 242, 254, 254, 252, 255, 60, 253, 252); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var s String = b.BytesOrPanic()
|
||||
var v uint64
|
||||
if !s.ReadUint64(&v) {
|
||||
t.Error("ReadUint64() = false, want true")
|
||||
}
|
||||
if v != 0xf2fefefcff3cfdfc {
|
||||
t.Errorf("v = %x, want f2fefefcff3cfdfc", v)
|
||||
}
|
||||
if len(s) != 0 {
|
||||
t.Errorf("len(s) = %d, want 0", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUMultiple(t *testing.T) {
|
||||
var b Builder
|
||||
b.AddUint8(23)
|
||||
|
||||
+1
-23
@@ -15,7 +15,7 @@
|
||||
//
|
||||
// See the documentation and examples for the Builder and String types to get
|
||||
// started.
|
||||
package cryptobyte
|
||||
package cryptobyte // import "golang.org/x/crypto/cryptobyte"
|
||||
|
||||
// String represents a string of bytes. It provides methods for parsing
|
||||
// fixed-length and length-prefixed values from it.
|
||||
@@ -81,28 +81,6 @@ func (s *String) ReadUint32(out *uint32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint48(out *uint64) bool {
|
||||
v := s.read(6)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5])
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
||||
// It reports whether the read was successful.
|
||||
func (s *String) ReadUint64(out *uint64) bool {
|
||||
v := s.read(8)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
*out = uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7])
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *String) readUnsigned(out *uint32, length int) bool {
|
||||
v := s.read(length)
|
||||
if v == nil {
|
||||
|
||||
@@ -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
|
||||
+822
-78
@@ -1,90 +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.
|
||||
//
|
||||
// This package is a wrapper for the X25519 implementation
|
||||
// in the crypto/ecdh package.
|
||||
// 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/ecdh"
|
||||
|
||||
// ScalarMult sets dst to the product scalar * point.
|
||||
//
|
||||
// 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) {
|
||||
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
|
||||
// The only error condition for x25519 when the inputs are 32 bytes long
|
||||
// is if the output would have been the all-zero value.
|
||||
for i := range dst {
|
||||
dst[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
curve := ecdh.X25519()
|
||||
priv, err := curve.NewPrivateKey(scalar[:])
|
||||
if err != nil {
|
||||
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
|
||||
}
|
||||
copy(dst[:], priv.PublicKey().Bytes())
|
||||
}
|
||||
|
||||
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
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Basepoint is the canonical Curve25519 generator.
|
||||
var Basepoint []byte
|
||||
// This code is a port of the public domain, "ref10" implementation of
|
||||
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
|
||||
|
||||
var basePoint = [32]byte{9}
|
||||
// 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 init() { Basepoint = basePoint[:] }
|
||||
|
||||
// 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 feZero(fe *fieldElement) {
|
||||
for i := range fe {
|
||||
fe[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
|
||||
curve := ecdh.X25519()
|
||||
pub, err := curve.NewPublicKey(point)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := curve.NewPrivateKey(scalar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := priv.ECDH(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(dst[:], out)
|
||||
return dst[:], nil
|
||||
func feOne(fe *fieldElement) {
|
||||
feZero(fe)
|
||||
fe[0] = 1
|
||||
}
|
||||
|
||||
func feAdd(dst, a, b *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = a[i] + b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func feSub(dst, a, b *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = a[i] - b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func feCopy(dst, src *fieldElement) {
|
||||
for i := range dst {
|
||||
dst[i] = src[i]
|
||||
}
|
||||
}
|
||||
|
||||
// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0.
|
||||
//
|
||||
// Preconditions: b in {0,1}.
|
||||
func feCSwap(f, g *fieldElement, b int32) {
|
||||
b = -b
|
||||
for i := range f {
|
||||
t := b & (f[i] ^ g[i])
|
||||
f[i] ^= t
|
||||
g[i] ^= t
|
||||
}
|
||||
}
|
||||
|
||||
// load3 reads a 24-bit, little-endian value from in.
|
||||
func load3(in []byte) int64 {
|
||||
var r int64
|
||||
r = int64(in[0])
|
||||
r |= int64(in[1]) << 8
|
||||
r |= int64(in[2]) << 16
|
||||
return r
|
||||
}
|
||||
|
||||
// load4 reads a 32-bit, little-endian value from in.
|
||||
func load4(in []byte) int64 {
|
||||
return int64(binary.LittleEndian.Uint32(in))
|
||||
}
|
||||
|
||||
func feFromBytes(dst *fieldElement, src *[32]byte) {
|
||||
h0 := load4(src[:])
|
||||
h1 := load3(src[4:]) << 6
|
||||
h2 := load3(src[7:]) << 5
|
||||
h3 := load3(src[10:]) << 3
|
||||
h4 := load3(src[13:]) << 2
|
||||
h5 := load4(src[16:])
|
||||
h6 := load3(src[20:]) << 7
|
||||
h7 := load3(src[23:]) << 5
|
||||
h8 := load3(src[26:]) << 4
|
||||
h9 := (load3(src[29:]) & 0x7fffff) << 2
|
||||
|
||||
var carry [10]int64
|
||||
carry[9] = (h9 + 1<<24) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
carry[1] = (h1 + 1<<24) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[3] = (h3 + 1<<24) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[5] = (h5 + 1<<24) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
carry[7] = (h7 + 1<<24) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[0] = (h0 + 1<<25) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[2] = (h2 + 1<<25) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[4] = (h4 + 1<<25) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[6] = (h6 + 1<<25) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
carry[8] = (h8 + 1<<25) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
dst[0] = int32(h0)
|
||||
dst[1] = int32(h1)
|
||||
dst[2] = int32(h2)
|
||||
dst[3] = int32(h3)
|
||||
dst[4] = int32(h4)
|
||||
dst[5] = int32(h5)
|
||||
dst[6] = int32(h6)
|
||||
dst[7] = int32(h7)
|
||||
dst[8] = int32(h8)
|
||||
dst[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feToBytes marshals h to s.
|
||||
// Preconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
//
|
||||
// Write p=2^255-19; q=floor(h/p).
|
||||
// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
|
||||
//
|
||||
// Proof:
|
||||
// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
|
||||
// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4.
|
||||
//
|
||||
// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
|
||||
// Then 0<y<1.
|
||||
//
|
||||
// Write r=h-pq.
|
||||
// Have 0<=r<=p-1=2^255-20.
|
||||
// Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.
|
||||
//
|
||||
// Write x=r+19(2^-255)r+y.
|
||||
// Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
|
||||
//
|
||||
// Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
|
||||
// so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
|
||||
func feToBytes(s *[32]byte, h *fieldElement) {
|
||||
var carry [10]int32
|
||||
|
||||
q := (19*h[9] + (1 << 24)) >> 25
|
||||
q = (h[0] + q) >> 26
|
||||
q = (h[1] + q) >> 25
|
||||
q = (h[2] + q) >> 26
|
||||
q = (h[3] + q) >> 25
|
||||
q = (h[4] + q) >> 26
|
||||
q = (h[5] + q) >> 25
|
||||
q = (h[6] + q) >> 26
|
||||
q = (h[7] + q) >> 25
|
||||
q = (h[8] + q) >> 26
|
||||
q = (h[9] + q) >> 25
|
||||
|
||||
// Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20.
|
||||
h[0] += 19 * q
|
||||
// Goal: Output h-2^255 q, which is between 0 and 2^255-20.
|
||||
|
||||
carry[0] = h[0] >> 26
|
||||
h[1] += carry[0]
|
||||
h[0] -= carry[0] << 26
|
||||
carry[1] = h[1] >> 25
|
||||
h[2] += carry[1]
|
||||
h[1] -= carry[1] << 25
|
||||
carry[2] = h[2] >> 26
|
||||
h[3] += carry[2]
|
||||
h[2] -= carry[2] << 26
|
||||
carry[3] = h[3] >> 25
|
||||
h[4] += carry[3]
|
||||
h[3] -= carry[3] << 25
|
||||
carry[4] = h[4] >> 26
|
||||
h[5] += carry[4]
|
||||
h[4] -= carry[4] << 26
|
||||
carry[5] = h[5] >> 25
|
||||
h[6] += carry[5]
|
||||
h[5] -= carry[5] << 25
|
||||
carry[6] = h[6] >> 26
|
||||
h[7] += carry[6]
|
||||
h[6] -= carry[6] << 26
|
||||
carry[7] = h[7] >> 25
|
||||
h[8] += carry[7]
|
||||
h[7] -= carry[7] << 25
|
||||
carry[8] = h[8] >> 26
|
||||
h[9] += carry[8]
|
||||
h[8] -= carry[8] << 26
|
||||
carry[9] = h[9] >> 25
|
||||
h[9] -= carry[9] << 25
|
||||
// h10 = carry9
|
||||
|
||||
// Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
|
||||
// Have h[0]+...+2^230 h[9] between 0 and 2^255-1;
|
||||
// evidently 2^255 h10-2^255 q = 0.
|
||||
// Goal: Output h[0]+...+2^230 h[9].
|
||||
|
||||
s[0] = byte(h[0] >> 0)
|
||||
s[1] = byte(h[0] >> 8)
|
||||
s[2] = byte(h[0] >> 16)
|
||||
s[3] = byte((h[0] >> 24) | (h[1] << 2))
|
||||
s[4] = byte(h[1] >> 6)
|
||||
s[5] = byte(h[1] >> 14)
|
||||
s[6] = byte((h[1] >> 22) | (h[2] << 3))
|
||||
s[7] = byte(h[2] >> 5)
|
||||
s[8] = byte(h[2] >> 13)
|
||||
s[9] = byte((h[2] >> 21) | (h[3] << 5))
|
||||
s[10] = byte(h[3] >> 3)
|
||||
s[11] = byte(h[3] >> 11)
|
||||
s[12] = byte((h[3] >> 19) | (h[4] << 6))
|
||||
s[13] = byte(h[4] >> 2)
|
||||
s[14] = byte(h[4] >> 10)
|
||||
s[15] = byte(h[4] >> 18)
|
||||
s[16] = byte(h[5] >> 0)
|
||||
s[17] = byte(h[5] >> 8)
|
||||
s[18] = byte(h[5] >> 16)
|
||||
s[19] = byte((h[5] >> 24) | (h[6] << 1))
|
||||
s[20] = byte(h[6] >> 7)
|
||||
s[21] = byte(h[6] >> 15)
|
||||
s[22] = byte((h[6] >> 23) | (h[7] << 3))
|
||||
s[23] = byte(h[7] >> 5)
|
||||
s[24] = byte(h[7] >> 13)
|
||||
s[25] = byte((h[7] >> 21) | (h[8] << 4))
|
||||
s[26] = byte(h[8] >> 4)
|
||||
s[27] = byte(h[8] >> 12)
|
||||
s[28] = byte((h[8] >> 20) | (h[9] << 6))
|
||||
s[29] = byte(h[9] >> 2)
|
||||
s[30] = byte(h[9] >> 10)
|
||||
s[31] = byte(h[9] >> 18)
|
||||
}
|
||||
|
||||
// feMul calculates h = f * g
|
||||
// Can overlap h with f or g.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
//
|
||||
// Notes on implementation strategy:
|
||||
//
|
||||
// Using schoolbook multiplication.
|
||||
// Karatsuba would save a little in some cost models.
|
||||
//
|
||||
// Most multiplications by 2 and 19 are 32-bit precomputations;
|
||||
// cheaper than 64-bit postcomputations.
|
||||
//
|
||||
// There is one remaining multiplication by 19 in the carry chain;
|
||||
// one *19 precomputation can be merged into this,
|
||||
// but the resulting data flow is considerably less clean.
|
||||
//
|
||||
// There are 12 carries below.
|
||||
// 10 of them are 2-way parallelizable and vectorizable.
|
||||
// Can get away with 11 carries, but then data flow is much deeper.
|
||||
//
|
||||
// With tighter constraints on inputs can squeeze carries into int32.
|
||||
func feMul(h, f, g *fieldElement) {
|
||||
f0 := f[0]
|
||||
f1 := f[1]
|
||||
f2 := f[2]
|
||||
f3 := f[3]
|
||||
f4 := f[4]
|
||||
f5 := f[5]
|
||||
f6 := f[6]
|
||||
f7 := f[7]
|
||||
f8 := f[8]
|
||||
f9 := f[9]
|
||||
g0 := g[0]
|
||||
g1 := g[1]
|
||||
g2 := g[2]
|
||||
g3 := g[3]
|
||||
g4 := g[4]
|
||||
g5 := g[5]
|
||||
g6 := g[6]
|
||||
g7 := g[7]
|
||||
g8 := g[8]
|
||||
g9 := g[9]
|
||||
g1_19 := 19 * g1 // 1.4*2^29
|
||||
g2_19 := 19 * g2 // 1.4*2^30; still ok
|
||||
g3_19 := 19 * g3
|
||||
g4_19 := 19 * g4
|
||||
g5_19 := 19 * g5
|
||||
g6_19 := 19 * g6
|
||||
g7_19 := 19 * g7
|
||||
g8_19 := 19 * g8
|
||||
g9_19 := 19 * g9
|
||||
f1_2 := 2 * f1
|
||||
f3_2 := 2 * f3
|
||||
f5_2 := 2 * f5
|
||||
f7_2 := 2 * f7
|
||||
f9_2 := 2 * f9
|
||||
f0g0 := int64(f0) * int64(g0)
|
||||
f0g1 := int64(f0) * int64(g1)
|
||||
f0g2 := int64(f0) * int64(g2)
|
||||
f0g3 := int64(f0) * int64(g3)
|
||||
f0g4 := int64(f0) * int64(g4)
|
||||
f0g5 := int64(f0) * int64(g5)
|
||||
f0g6 := int64(f0) * int64(g6)
|
||||
f0g7 := int64(f0) * int64(g7)
|
||||
f0g8 := int64(f0) * int64(g8)
|
||||
f0g9 := int64(f0) * int64(g9)
|
||||
f1g0 := int64(f1) * int64(g0)
|
||||
f1g1_2 := int64(f1_2) * int64(g1)
|
||||
f1g2 := int64(f1) * int64(g2)
|
||||
f1g3_2 := int64(f1_2) * int64(g3)
|
||||
f1g4 := int64(f1) * int64(g4)
|
||||
f1g5_2 := int64(f1_2) * int64(g5)
|
||||
f1g6 := int64(f1) * int64(g6)
|
||||
f1g7_2 := int64(f1_2) * int64(g7)
|
||||
f1g8 := int64(f1) * int64(g8)
|
||||
f1g9_38 := int64(f1_2) * int64(g9_19)
|
||||
f2g0 := int64(f2) * int64(g0)
|
||||
f2g1 := int64(f2) * int64(g1)
|
||||
f2g2 := int64(f2) * int64(g2)
|
||||
f2g3 := int64(f2) * int64(g3)
|
||||
f2g4 := int64(f2) * int64(g4)
|
||||
f2g5 := int64(f2) * int64(g5)
|
||||
f2g6 := int64(f2) * int64(g6)
|
||||
f2g7 := int64(f2) * int64(g7)
|
||||
f2g8_19 := int64(f2) * int64(g8_19)
|
||||
f2g9_19 := int64(f2) * int64(g9_19)
|
||||
f3g0 := int64(f3) * int64(g0)
|
||||
f3g1_2 := int64(f3_2) * int64(g1)
|
||||
f3g2 := int64(f3) * int64(g2)
|
||||
f3g3_2 := int64(f3_2) * int64(g3)
|
||||
f3g4 := int64(f3) * int64(g4)
|
||||
f3g5_2 := int64(f3_2) * int64(g5)
|
||||
f3g6 := int64(f3) * int64(g6)
|
||||
f3g7_38 := int64(f3_2) * int64(g7_19)
|
||||
f3g8_19 := int64(f3) * int64(g8_19)
|
||||
f3g9_38 := int64(f3_2) * int64(g9_19)
|
||||
f4g0 := int64(f4) * int64(g0)
|
||||
f4g1 := int64(f4) * int64(g1)
|
||||
f4g2 := int64(f4) * int64(g2)
|
||||
f4g3 := int64(f4) * int64(g3)
|
||||
f4g4 := int64(f4) * int64(g4)
|
||||
f4g5 := int64(f4) * int64(g5)
|
||||
f4g6_19 := int64(f4) * int64(g6_19)
|
||||
f4g7_19 := int64(f4) * int64(g7_19)
|
||||
f4g8_19 := int64(f4) * int64(g8_19)
|
||||
f4g9_19 := int64(f4) * int64(g9_19)
|
||||
f5g0 := int64(f5) * int64(g0)
|
||||
f5g1_2 := int64(f5_2) * int64(g1)
|
||||
f5g2 := int64(f5) * int64(g2)
|
||||
f5g3_2 := int64(f5_2) * int64(g3)
|
||||
f5g4 := int64(f5) * int64(g4)
|
||||
f5g5_38 := int64(f5_2) * int64(g5_19)
|
||||
f5g6_19 := int64(f5) * int64(g6_19)
|
||||
f5g7_38 := int64(f5_2) * int64(g7_19)
|
||||
f5g8_19 := int64(f5) * int64(g8_19)
|
||||
f5g9_38 := int64(f5_2) * int64(g9_19)
|
||||
f6g0 := int64(f6) * int64(g0)
|
||||
f6g1 := int64(f6) * int64(g1)
|
||||
f6g2 := int64(f6) * int64(g2)
|
||||
f6g3 := int64(f6) * int64(g3)
|
||||
f6g4_19 := int64(f6) * int64(g4_19)
|
||||
f6g5_19 := int64(f6) * int64(g5_19)
|
||||
f6g6_19 := int64(f6) * int64(g6_19)
|
||||
f6g7_19 := int64(f6) * int64(g7_19)
|
||||
f6g8_19 := int64(f6) * int64(g8_19)
|
||||
f6g9_19 := int64(f6) * int64(g9_19)
|
||||
f7g0 := int64(f7) * int64(g0)
|
||||
f7g1_2 := int64(f7_2) * int64(g1)
|
||||
f7g2 := int64(f7) * int64(g2)
|
||||
f7g3_38 := int64(f7_2) * int64(g3_19)
|
||||
f7g4_19 := int64(f7) * int64(g4_19)
|
||||
f7g5_38 := int64(f7_2) * int64(g5_19)
|
||||
f7g6_19 := int64(f7) * int64(g6_19)
|
||||
f7g7_38 := int64(f7_2) * int64(g7_19)
|
||||
f7g8_19 := int64(f7) * int64(g8_19)
|
||||
f7g9_38 := int64(f7_2) * int64(g9_19)
|
||||
f8g0 := int64(f8) * int64(g0)
|
||||
f8g1 := int64(f8) * int64(g1)
|
||||
f8g2_19 := int64(f8) * int64(g2_19)
|
||||
f8g3_19 := int64(f8) * int64(g3_19)
|
||||
f8g4_19 := int64(f8) * int64(g4_19)
|
||||
f8g5_19 := int64(f8) * int64(g5_19)
|
||||
f8g6_19 := int64(f8) * int64(g6_19)
|
||||
f8g7_19 := int64(f8) * int64(g7_19)
|
||||
f8g8_19 := int64(f8) * int64(g8_19)
|
||||
f8g9_19 := int64(f8) * int64(g9_19)
|
||||
f9g0 := int64(f9) * int64(g0)
|
||||
f9g1_38 := int64(f9_2) * int64(g1_19)
|
||||
f9g2_19 := int64(f9) * int64(g2_19)
|
||||
f9g3_38 := int64(f9_2) * int64(g3_19)
|
||||
f9g4_19 := int64(f9) * int64(g4_19)
|
||||
f9g5_38 := int64(f9_2) * int64(g5_19)
|
||||
f9g6_19 := int64(f9) * int64(g6_19)
|
||||
f9g7_38 := int64(f9_2) * int64(g7_19)
|
||||
f9g8_19 := int64(f9) * int64(g8_19)
|
||||
f9g9_38 := int64(f9_2) * int64(g9_19)
|
||||
h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38
|
||||
h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19
|
||||
h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38
|
||||
h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19
|
||||
h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38
|
||||
h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19
|
||||
h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38
|
||||
h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19
|
||||
h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38
|
||||
h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0
|
||||
var carry [10]int64
|
||||
|
||||
// |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38))
|
||||
// i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8
|
||||
// |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19))
|
||||
// i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
// |h0| <= 2^25
|
||||
// |h4| <= 2^25
|
||||
// |h1| <= 1.51*2^58
|
||||
// |h5| <= 1.51*2^58
|
||||
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
// |h1| <= 2^24; from now on fits into int32
|
||||
// |h5| <= 2^24; from now on fits into int32
|
||||
// |h2| <= 1.21*2^59
|
||||
// |h6| <= 1.21*2^59
|
||||
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
// |h2| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h6| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h3| <= 1.51*2^58
|
||||
// |h7| <= 1.51*2^58
|
||||
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
// |h3| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h7| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h4| <= 1.52*2^33
|
||||
// |h8| <= 1.52*2^33
|
||||
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
// |h4| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h8| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h5| <= 1.01*2^24
|
||||
// |h9| <= 1.51*2^58
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
// |h9| <= 2^24; from now on fits into int32 unchanged
|
||||
// |h0| <= 1.8*2^37
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
// |h0| <= 2^25; from now on fits into int32 unchanged
|
||||
// |h1| <= 1.01*2^24
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feSquare calculates h = f*f. Can overlap h with f.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
func feSquare(h, f *fieldElement) {
|
||||
f0 := f[0]
|
||||
f1 := f[1]
|
||||
f2 := f[2]
|
||||
f3 := f[3]
|
||||
f4 := f[4]
|
||||
f5 := f[5]
|
||||
f6 := f[6]
|
||||
f7 := f[7]
|
||||
f8 := f[8]
|
||||
f9 := f[9]
|
||||
f0_2 := 2 * f0
|
||||
f1_2 := 2 * f1
|
||||
f2_2 := 2 * f2
|
||||
f3_2 := 2 * f3
|
||||
f4_2 := 2 * f4
|
||||
f5_2 := 2 * f5
|
||||
f6_2 := 2 * f6
|
||||
f7_2 := 2 * f7
|
||||
f5_38 := 38 * f5 // 1.31*2^30
|
||||
f6_19 := 19 * f6 // 1.31*2^30
|
||||
f7_38 := 38 * f7 // 1.31*2^30
|
||||
f8_19 := 19 * f8 // 1.31*2^30
|
||||
f9_38 := 38 * f9 // 1.31*2^30
|
||||
f0f0 := int64(f0) * int64(f0)
|
||||
f0f1_2 := int64(f0_2) * int64(f1)
|
||||
f0f2_2 := int64(f0_2) * int64(f2)
|
||||
f0f3_2 := int64(f0_2) * int64(f3)
|
||||
f0f4_2 := int64(f0_2) * int64(f4)
|
||||
f0f5_2 := int64(f0_2) * int64(f5)
|
||||
f0f6_2 := int64(f0_2) * int64(f6)
|
||||
f0f7_2 := int64(f0_2) * int64(f7)
|
||||
f0f8_2 := int64(f0_2) * int64(f8)
|
||||
f0f9_2 := int64(f0_2) * int64(f9)
|
||||
f1f1_2 := int64(f1_2) * int64(f1)
|
||||
f1f2_2 := int64(f1_2) * int64(f2)
|
||||
f1f3_4 := int64(f1_2) * int64(f3_2)
|
||||
f1f4_2 := int64(f1_2) * int64(f4)
|
||||
f1f5_4 := int64(f1_2) * int64(f5_2)
|
||||
f1f6_2 := int64(f1_2) * int64(f6)
|
||||
f1f7_4 := int64(f1_2) * int64(f7_2)
|
||||
f1f8_2 := int64(f1_2) * int64(f8)
|
||||
f1f9_76 := int64(f1_2) * int64(f9_38)
|
||||
f2f2 := int64(f2) * int64(f2)
|
||||
f2f3_2 := int64(f2_2) * int64(f3)
|
||||
f2f4_2 := int64(f2_2) * int64(f4)
|
||||
f2f5_2 := int64(f2_2) * int64(f5)
|
||||
f2f6_2 := int64(f2_2) * int64(f6)
|
||||
f2f7_2 := int64(f2_2) * int64(f7)
|
||||
f2f8_38 := int64(f2_2) * int64(f8_19)
|
||||
f2f9_38 := int64(f2) * int64(f9_38)
|
||||
f3f3_2 := int64(f3_2) * int64(f3)
|
||||
f3f4_2 := int64(f3_2) * int64(f4)
|
||||
f3f5_4 := int64(f3_2) * int64(f5_2)
|
||||
f3f6_2 := int64(f3_2) * int64(f6)
|
||||
f3f7_76 := int64(f3_2) * int64(f7_38)
|
||||
f3f8_38 := int64(f3_2) * int64(f8_19)
|
||||
f3f9_76 := int64(f3_2) * int64(f9_38)
|
||||
f4f4 := int64(f4) * int64(f4)
|
||||
f4f5_2 := int64(f4_2) * int64(f5)
|
||||
f4f6_38 := int64(f4_2) * int64(f6_19)
|
||||
f4f7_38 := int64(f4) * int64(f7_38)
|
||||
f4f8_38 := int64(f4_2) * int64(f8_19)
|
||||
f4f9_38 := int64(f4) * int64(f9_38)
|
||||
f5f5_38 := int64(f5) * int64(f5_38)
|
||||
f5f6_38 := int64(f5_2) * int64(f6_19)
|
||||
f5f7_76 := int64(f5_2) * int64(f7_38)
|
||||
f5f8_38 := int64(f5_2) * int64(f8_19)
|
||||
f5f9_76 := int64(f5_2) * int64(f9_38)
|
||||
f6f6_19 := int64(f6) * int64(f6_19)
|
||||
f6f7_38 := int64(f6) * int64(f7_38)
|
||||
f6f8_38 := int64(f6_2) * int64(f8_19)
|
||||
f6f9_38 := int64(f6) * int64(f9_38)
|
||||
f7f7_38 := int64(f7) * int64(f7_38)
|
||||
f7f8_38 := int64(f7_2) * int64(f8_19)
|
||||
f7f9_76 := int64(f7_2) * int64(f9_38)
|
||||
f8f8_19 := int64(f8) * int64(f8_19)
|
||||
f8f9_38 := int64(f8) * int64(f9_38)
|
||||
f9f9_38 := int64(f9) * int64(f9_38)
|
||||
h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
||||
h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
|
||||
h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
|
||||
h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
|
||||
h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
|
||||
h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
|
||||
h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
|
||||
h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
|
||||
h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
|
||||
h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
|
||||
var carry [10]int64
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feMul121666 calculates h = f * 121666. Can overlap h with f.
|
||||
//
|
||||
// Preconditions:
|
||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||
//
|
||||
// Postconditions:
|
||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||
func feMul121666(h, f *fieldElement) {
|
||||
h0 := int64(f[0]) * 121666
|
||||
h1 := int64(f[1]) * 121666
|
||||
h2 := int64(f[2]) * 121666
|
||||
h3 := int64(f[3]) * 121666
|
||||
h4 := int64(f[4]) * 121666
|
||||
h5 := int64(f[5]) * 121666
|
||||
h6 := int64(f[6]) * 121666
|
||||
h7 := int64(f[7]) * 121666
|
||||
h8 := int64(f[8]) * 121666
|
||||
h9 := int64(f[9]) * 121666
|
||||
var carry [10]int64
|
||||
|
||||
carry[9] = (h9 + (1 << 24)) >> 25
|
||||
h0 += carry[9] * 19
|
||||
h9 -= carry[9] << 25
|
||||
carry[1] = (h1 + (1 << 24)) >> 25
|
||||
h2 += carry[1]
|
||||
h1 -= carry[1] << 25
|
||||
carry[3] = (h3 + (1 << 24)) >> 25
|
||||
h4 += carry[3]
|
||||
h3 -= carry[3] << 25
|
||||
carry[5] = (h5 + (1 << 24)) >> 25
|
||||
h6 += carry[5]
|
||||
h5 -= carry[5] << 25
|
||||
carry[7] = (h7 + (1 << 24)) >> 25
|
||||
h8 += carry[7]
|
||||
h7 -= carry[7] << 25
|
||||
|
||||
carry[0] = (h0 + (1 << 25)) >> 26
|
||||
h1 += carry[0]
|
||||
h0 -= carry[0] << 26
|
||||
carry[2] = (h2 + (1 << 25)) >> 26
|
||||
h3 += carry[2]
|
||||
h2 -= carry[2] << 26
|
||||
carry[4] = (h4 + (1 << 25)) >> 26
|
||||
h5 += carry[4]
|
||||
h4 -= carry[4] << 26
|
||||
carry[6] = (h6 + (1 << 25)) >> 26
|
||||
h7 += carry[6]
|
||||
h6 -= carry[6] << 26
|
||||
carry[8] = (h8 + (1 << 25)) >> 26
|
||||
h9 += carry[8]
|
||||
h8 -= carry[8] << 26
|
||||
|
||||
h[0] = int32(h0)
|
||||
h[1] = int32(h1)
|
||||
h[2] = int32(h2)
|
||||
h[3] = int32(h3)
|
||||
h[4] = int32(h4)
|
||||
h[5] = int32(h5)
|
||||
h[6] = int32(h6)
|
||||
h[7] = int32(h7)
|
||||
h[8] = int32(h8)
|
||||
h[9] = int32(h9)
|
||||
}
|
||||
|
||||
// feInvert sets out = z^-1.
|
||||
func feInvert(out, z *fieldElement) {
|
||||
var t0, t1, t2, t3 fieldElement
|
||||
var i int
|
||||
|
||||
feSquare(&t0, z)
|
||||
for i = 1; i < 1; i++ {
|
||||
feSquare(&t0, &t0)
|
||||
}
|
||||
feSquare(&t1, &t0)
|
||||
for i = 1; i < 2; i++ {
|
||||
feSquare(&t1, &t1)
|
||||
}
|
||||
feMul(&t1, z, &t1)
|
||||
feMul(&t0, &t0, &t1)
|
||||
feSquare(&t2, &t0)
|
||||
for i = 1; i < 1; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t1, &t2)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 5; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 10; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t2, &t2, &t1)
|
||||
feSquare(&t3, &t2)
|
||||
for i = 1; i < 20; i++ {
|
||||
feSquare(&t3, &t3)
|
||||
}
|
||||
feMul(&t2, &t3, &t2)
|
||||
feSquare(&t2, &t2)
|
||||
for i = 1; i < 10; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t2, &t1)
|
||||
for i = 1; i < 50; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t2, &t2, &t1)
|
||||
feSquare(&t3, &t2)
|
||||
for i = 1; i < 100; i++ {
|
||||
feSquare(&t3, &t3)
|
||||
}
|
||||
feMul(&t2, &t3, &t2)
|
||||
feSquare(&t2, &t2)
|
||||
for i = 1; i < 50; i++ {
|
||||
feSquare(&t2, &t2)
|
||||
}
|
||||
feMul(&t1, &t2, &t1)
|
||||
feSquare(&t1, &t1)
|
||||
for i = 1; i < 5; i++ {
|
||||
feSquare(&t1, &t1)
|
||||
}
|
||||
feMul(out, &t1, &t0)
|
||||
}
|
||||
|
||||
func scalarMult(out, in, base *[32]byte) {
|
||||
var e [32]byte
|
||||
|
||||
copy(e[:], in[:])
|
||||
e[0] &= 248
|
||||
e[31] &= 127
|
||||
e[31] |= 64
|
||||
|
||||
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
|
||||
feFromBytes(&x1, base)
|
||||
feOne(&x2)
|
||||
feCopy(&x3, &x1)
|
||||
feOne(&z3)
|
||||
|
||||
swap := int32(0)
|
||||
for pos := 254; pos >= 0; pos-- {
|
||||
b := e[pos/8] >> uint(pos&7)
|
||||
b &= 1
|
||||
swap ^= int32(b)
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
swap = int32(b)
|
||||
|
||||
feSub(&tmp0, &x3, &z3)
|
||||
feSub(&tmp1, &x2, &z2)
|
||||
feAdd(&x2, &x2, &z2)
|
||||
feAdd(&z2, &x3, &z3)
|
||||
feMul(&z3, &tmp0, &x2)
|
||||
feMul(&z2, &z2, &tmp1)
|
||||
feSquare(&tmp0, &tmp1)
|
||||
feSquare(&tmp1, &x2)
|
||||
feAdd(&x3, &z3, &z2)
|
||||
feSub(&z2, &z3, &z2)
|
||||
feMul(&x2, &tmp1, &tmp0)
|
||||
feSub(&tmp1, &tmp1, &tmp0)
|
||||
feSquare(&z2, &z2)
|
||||
feMul121666(&z3, &tmp1)
|
||||
feSquare(&x3, &x3)
|
||||
feAdd(&tmp0, &tmp0, &z3)
|
||||
feMul(&z3, &x1, &z2)
|
||||
feMul(&z2, &tmp1, &tmp0)
|
||||
}
|
||||
|
||||
feCSwap(&x2, &x3, swap)
|
||||
feCSwap(&z2, &z3, swap)
|
||||
|
||||
feInvert(&z2, &z2)
|
||||
feMul(&x2, &x2, &z2)
|
||||
feToBytes(out, &x2)
|
||||
}
|
||||
|
||||
@@ -2,70 +2,38 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519_test
|
||||
package curve25519
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||
|
||||
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 = curve25519.X25519(x, curve25519.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, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, p := range lowOrderPoints {
|
||||
out, err := curve25519.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, curve25519.ScalarMult) })
|
||||
t.Run("X25519", func(t *testing.T) {
|
||||
testTestVectors(t, func(dst, scalar, point *[32]byte) {
|
||||
out, err := curve25519.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)
|
||||
@@ -90,53 +58,22 @@ func TestHighBitIgnored(t *testing.T) {
|
||||
var hi0, hi1 [32]byte
|
||||
|
||||
u[31] &= 0x7f
|
||||
curve25519.ScalarMult(&hi0, &s, &u)
|
||||
ScalarMult(&hi0, &s, &u)
|
||||
|
||||
u[31] |= 0x80
|
||||
curve25519.ScalarMult(&hi1, &s, &u)
|
||||
ScalarMult(&hi1, &s, &u)
|
||||
|
||||
if !bytes.Equal(hi0[:], hi1[:]) {
|
||||
t.Errorf("high bit of group point should not affect result")
|
||||
}
|
||||
}
|
||||
|
||||
var benchmarkSink byte
|
||||
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||
var in, out [32]byte
|
||||
in[0] = 1
|
||||
|
||||
func BenchmarkX25519Basepoint(b *testing.B) {
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(32)
|
||||
for i := 0; i < b.N; i++ {
|
||||
out, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkX25519(b *testing.B) {
|
||||
scalar := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
point, err := curve25519.X25519(scalar, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if _, err := rand.Read(scalar); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out, err := curve25519.X25519(scalar, point)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
benchmarkSink ^= out[0]
|
||||
ScalarBaseMult(&out, &in)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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
|
||||
@@ -2,19 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package curve25519_test
|
||||
|
||||
// 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},
|
||||
}
|
||||
package curve25519
|
||||
|
||||
// testVectors generated with BoringSSL.
|
||||
var testVectors = []struct {
|
||||
+164
-16
@@ -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,13 +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”.
|
||||
//
|
||||
// This package is a wrapper around the standard library crypto/ed25519 package.
|
||||
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 (
|
||||
@@ -31,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
|
||||
@@ -53,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[:])
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user