forked from Gitlink/gitea-1156
add letsencrypt to Gitea (#4189)
This commit is contained in:
parent
6c1a31ffaa
commit
b82c14b3d2
|
@ -563,14 +563,18 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = [
|
packages = [
|
||||||
|
"acme",
|
||||||
|
"acme/autocert",
|
||||||
"curve25519",
|
"curve25519",
|
||||||
"ed25519",
|
"ed25519",
|
||||||
"ed25519/internal/edwards25519",
|
"ed25519/internal/edwards25519",
|
||||||
|
"internal/chacha20",
|
||||||
"md4",
|
"md4",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
|
"poly1305",
|
||||||
"ssh"
|
"ssh"
|
||||||
]
|
]
|
||||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
revision = "12dd70caea0268ac0d6c2707d0611ef601e7c64e"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
|
|
|
@ -15,7 +15,7 @@ ignored = ["google.golang.org/appengine*"]
|
||||||
name = "code.gitea.io/sdk"
|
name = "code.gitea.io/sdk"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
revision = "12dd70caea0268ac0d6c2707d0611ef601e7c64e"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
|
33
cmd/web.go
33
cmd/web.go
|
@ -5,6 +5,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -22,6 +23,7 @@ import (
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
context2 "github.com/gorilla/context"
|
context2 "github.com/gorilla/context"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
ini "gopkg.in/ini.v1"
|
ini "gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,6 +73,33 @@ func runHTTPRedirector() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) error {
|
||||||
|
certManager := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(domain),
|
||||||
|
Cache: autocert.DirCache(directory),
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
go http.ListenAndServe(listenAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validatio happens here)
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: listenAddr,
|
||||||
|
Handler: m,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
GetCertificate: certManager.GetCertificate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return server.ListenAndServeTLS("", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" && r.Method != "HEAD" {
|
||||||
|
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := setting.AppURL + r.URL.RequestURI()
|
||||||
|
http.Redirect(w, r, target, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
func runWeb(ctx *cli.Context) error {
|
func runWeb(ctx *cli.Context) error {
|
||||||
if ctx.IsSet("config") {
|
if ctx.IsSet("config") {
|
||||||
setting.CustomConf = ctx.String("config")
|
setting.CustomConf = ctx.String("config")
|
||||||
|
@ -143,6 +172,10 @@ func runWeb(ctx *cli.Context) error {
|
||||||
case setting.HTTP:
|
case setting.HTTP:
|
||||||
err = runHTTP(listenAddr, context2.ClearHandler(m))
|
err = runHTTP(listenAddr, context2.ClearHandler(m))
|
||||||
case setting.HTTPS:
|
case setting.HTTPS:
|
||||||
|
if setting.EnableLetsEncrypt {
|
||||||
|
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
|
||||||
|
break
|
||||||
|
}
|
||||||
if setting.RedirectOtherPort {
|
if setting.RedirectOtherPort {
|
||||||
go runHTTPRedirector()
|
go runHTTPRedirector()
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests
|
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests
|
||||||
on another (https) port.
|
on another (https) port.
|
||||||
- `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true.
|
- `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true.
|
||||||
|
- `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server).
|
||||||
|
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)
|
||||||
|
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt
|
||||||
|
- `LETSENCRYPT_DIRECTORY`: **https**: Directory that Letsencrypt will use to cache information such as certs and private keys
|
||||||
|
- `LETSENCRYPT_EMAIL`: **email@example.com**: Email used by Letsencrypt to notify about problems with issued certificates. (No default)
|
||||||
|
|
||||||
## Database (`database`)
|
## Database (`database`)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,24 @@ KEY_FILE = key.pem
|
||||||
```
|
```
|
||||||
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
|
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
|
||||||
|
|
||||||
|
## Using Let's Encrypt
|
||||||
|
|
||||||
|
[Let's Encrypt](https://letsencrypt.org/) is a Certificate Authority that allows you to automatically request and renew SSL/TLS certificates. In addition to starting Gitea on your configured port, to request HTTPS certificates Gitea will also need to listed on port 80, and will set up an autoredirect to HTTPS for you. Let's Encrypt will need to be able to access Gitea via the Internet to verify your ownership of the domain.
|
||||||
|
|
||||||
|
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[server]
|
||||||
|
PROTOCOL=https
|
||||||
|
DOMAIN=git.example.com
|
||||||
|
ENABLE_LETSENCRYPT=true
|
||||||
|
LETSENCRYPT_ACCEPTTOS=true
|
||||||
|
LETSENCRYPT_DIRECTORY=https
|
||||||
|
LETSENCRYPT_EMAIL=email@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
|
||||||
|
|
||||||
## Using reverse proxy
|
## Using reverse proxy
|
||||||
|
|
||||||
Setup up your reverse proxy like shown in the [reverse proxy guide](../reverse-proxies).
|
Setup up your reverse proxy like shown in the [reverse proxy guide](../reverse-proxies).
|
||||||
|
|
|
@ -112,6 +112,10 @@ var (
|
||||||
UnixSocketPermission uint32
|
UnixSocketPermission uint32
|
||||||
EnablePprof bool
|
EnablePprof bool
|
||||||
PprofDataPath string
|
PprofDataPath string
|
||||||
|
EnableLetsEncrypt bool
|
||||||
|
LetsEncryptTOS bool
|
||||||
|
LetsEncryptDirectory string
|
||||||
|
LetsEncryptEmail string
|
||||||
|
|
||||||
SSH = struct {
|
SSH = struct {
|
||||||
Disabled bool `ini:"DISABLE_SSH"`
|
Disabled bool `ini:"DISABLE_SSH"`
|
||||||
|
@ -737,6 +741,14 @@ func NewContext() {
|
||||||
}
|
}
|
||||||
UnixSocketPermission = uint32(UnixSocketPermissionParsed)
|
UnixSocketPermission = uint32(UnixSocketPermissionParsed)
|
||||||
}
|
}
|
||||||
|
EnableLetsEncrypt := sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
||||||
|
LetsEncryptTOS := sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
|
||||||
|
if !LetsEncryptTOS && EnableLetsEncrypt {
|
||||||
|
log.Warn("Failed to enable Let's Encrypt due to Let's Encrypt TOS not being accepted")
|
||||||
|
EnableLetsEncrypt = false
|
||||||
|
}
|
||||||
|
LetsEncryptDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
|
||||||
|
LetsEncryptEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
|
||||||
Domain = sec.Key("DOMAIN").MustString("localhost")
|
Domain = sec.Key("DOMAIN").MustString("localhost")
|
||||||
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
|
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
|
||||||
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
|
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,962 @@
|
||||||
|
// 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 autocert 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 autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
mathrand "math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// TODO: Consider making it configurable or an exp backoff?
|
||||||
|
var createCertRetryAfter = time.Minute
|
||||||
|
|
||||||
|
// pseudoRand is safe for concurrent use.
|
||||||
|
var pseudoRand *lockedMathRand
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
src := mathrand.NewSource(timeNow().UnixNano())
|
||||||
|
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptTOS is a Manager.Prompt function that always returns true to
|
||||||
|
// indicate acceptance of the CA's Terms of Service during account
|
||||||
|
// registration.
|
||||||
|
func AcceptTOS(tosURL string) bool { return true }
|
||||||
|
|
||||||
|
// HostPolicy specifies which host names the Manager is allowed to respond to.
|
||||||
|
// It returns a non-nil error if the host should be rejected.
|
||||||
|
// The returned error is accessible via tls.Conn.Handshake and its callers.
|
||||||
|
// See Manager's HostPolicy field and GetCertificate method docs for more details.
|
||||||
|
type HostPolicy func(ctx context.Context, host string) error
|
||||||
|
|
||||||
|
// HostWhitelist returns a policy where only the specified host names are allowed.
|
||||||
|
// Only exact matches are currently supported. Subdomains, regexp or wildcard
|
||||||
|
// will not match.
|
||||||
|
func HostWhitelist(hosts ...string) HostPolicy {
|
||||||
|
whitelist := make(map[string]bool, len(hosts))
|
||||||
|
for _, h := range hosts {
|
||||||
|
whitelist[h] = true
|
||||||
|
}
|
||||||
|
return func(_ context.Context, host string) error {
|
||||||
|
if !whitelist[host] {
|
||||||
|
return errors.New("acme/autocert: host not configured")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultHostPolicy is used when Manager.HostPolicy is not set.
|
||||||
|
func defaultHostPolicy(context.Context, string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager is a stateful certificate manager built on top of acme.Client.
|
||||||
|
// It obtains and refreshes certificates automatically using "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.
|
||||||
|
// Otherwise your server is very likely to exceed the certificate
|
||||||
|
// issuer's request rate limits.
|
||||||
|
type Manager struct {
|
||||||
|
// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
|
||||||
|
// The registration may require the caller to agree to the CA's TOS.
|
||||||
|
// If so, Manager 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 callers can use AcceptTOS.
|
||||||
|
Prompt func(tosURL string) bool
|
||||||
|
|
||||||
|
// Cache optionally stores and retrieves previously-obtained certificates.
|
||||||
|
// If nil, certs will only be cached for the lifetime of the Manager.
|
||||||
|
//
|
||||||
|
// Manager passes the Cache certificates data encoded in PEM, with private/public
|
||||||
|
// parts combined in a single Cache.Put call, private key first.
|
||||||
|
Cache Cache
|
||||||
|
|
||||||
|
// HostPolicy controls which domains the Manager will attempt
|
||||||
|
// to retrieve new certificates for. It does not affect cached certs.
|
||||||
|
//
|
||||||
|
// If non-nil, HostPolicy is called before requesting a new cert.
|
||||||
|
// If nil, all hosts are currently allowed. This is not recommended,
|
||||||
|
// as it opens a potential attack where clients connect to a server
|
||||||
|
// by IP address and pretend to be asking for an incorrect host name.
|
||||||
|
// Manager will attempt to obtain a certificate for that host, incorrectly,
|
||||||
|
// eventually reaching the CA's rate limit for certificate requests
|
||||||
|
// and making it impossible to obtain actual certificates.
|
||||||
|
//
|
||||||
|
// See GetCertificate for more details.
|
||||||
|
HostPolicy HostPolicy
|
||||||
|
|
||||||
|
// RenewBefore optionally specifies how early certificates should
|
||||||
|
// be renewed before they expire.
|
||||||
|
//
|
||||||
|
// If zero, they're renewed 30 days before expiration.
|
||||||
|
RenewBefore time.Duration
|
||||||
|
|
||||||
|
// 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 acme.LetsEncryptURL
|
||||||
|
// directory endpoint and a newly-generated ECDSA P-256 key.
|
||||||
|
//
|
||||||
|
// Mutating the field after the first call of GetCertificate method will have no effect.
|
||||||
|
Client *acme.Client
|
||||||
|
|
||||||
|
// Email optionally specifies a contact email address.
|
||||||
|
// This is used by CAs, such as Let's Encrypt, to notify about problems
|
||||||
|
// with issued certificates.
|
||||||
|
//
|
||||||
|
// If the Client's account key is already registered, Email is not used.
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// ForceRSA makes the Manager generate certificates with 2048-bit RSA keys.
|
||||||
|
//
|
||||||
|
// If false, a default is used. Currently the default
|
||||||
|
// is EC-based keys using the P-256 curve.
|
||||||
|
ForceRSA bool
|
||||||
|
|
||||||
|
clientMu sync.Mutex
|
||||||
|
client *acme.Client // initialized by acmeClient method
|
||||||
|
|
||||||
|
stateMu sync.Mutex
|
||||||
|
state map[string]*certState // keyed by domain name
|
||||||
|
|
||||||
|
// renewal tracks the set of domains currently running renewal timers.
|
||||||
|
// It is keyed by domain name.
|
||||||
|
renewalMu sync.Mutex
|
||||||
|
renewal map[string]*domainRenewal
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// httpTokens contains response body values for http-01 challenges
|
||||||
|
// and is keyed by the URL path at which a challenge response is expected
|
||||||
|
// to be provisioned.
|
||||||
|
// The entries are stored for the duration of the authorization flow.
|
||||||
|
httpTokens map[string][]byte
|
||||||
|
// certTokens contains temporary certificates for tls-sni challenges
|
||||||
|
// and is keyed by token domain name, which matches server name of ClientHello.
|
||||||
|
// Keys always have ".acme.invalid" suffix.
|
||||||
|
// The entries are stored for the duration of the authorization flow.
|
||||||
|
certTokens map[string]*tls.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificate implements the tls.Config.GetCertificate hook.
|
||||||
|
// It provides a TLS certificate for hello.ServerName host, including answering
|
||||||
|
// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
|
||||||
|
//
|
||||||
|
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
|
||||||
|
// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
|
||||||
|
// The error is propagated back to the caller of GetCertificate and is user-visible.
|
||||||
|
// This does not affect cached certs. See HostPolicy field description for more details.
|
||||||
|
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
if m.Prompt == nil {
|
||||||
|
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := hello.ServerName
|
||||||
|
if name == "" {
|
||||||
|
return nil, errors.New("acme/autocert: missing server name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(strings.Trim(name, "."), ".") {
|
||||||
|
return nil, errors.New("acme/autocert: server name component count invalid")
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(name, `/\`) {
|
||||||
|
return nil, errors.New("acme/autocert: server name contains invalid character")
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the worst-case scenario, the timeout needs to account for caching, host policy,
|
||||||
|
// domain ownership verification and certificate issuance.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// check whether this is a token cert requested for TLS-SNI challenge
|
||||||
|
if strings.HasSuffix(name, ".acme.invalid") {
|
||||||
|
m.tokensMu.RLock()
|
||||||
|
defer m.tokensMu.RUnlock()
|
||||||
|
if cert := m.certTokens[name]; cert != nil {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
if cert, err := m.cacheGet(ctx, name); err == nil {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
// TODO: cache error results?
|
||||||
|
return nil, fmt.Errorf("acme/autocert: no token cert for %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// regular domain
|
||||||
|
name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
|
||||||
|
cert, err := m.cert(ctx, name)
|
||||||
|
if err == nil {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
if err != ErrCacheMiss {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// first-time
|
||||||
|
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert, err = m.createCert(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.cachePut(ctx, name, cert)
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
|
||||||
|
// It returns an http.Handler that responds to the challenges and must be
|
||||||
|
// running on port 80. If it receives a request that is not an ACME challenge,
|
||||||
|
// it delegates the request to the optional fallback handler.
|
||||||
|
//
|
||||||
|
// If fallback is nil, the returned handler redirects all GET and HEAD requests
|
||||||
|
// to the default TLS port 443 with 302 Found status code, preserving the original
|
||||||
|
// request path and query. It responds with 400 Bad Request to all other HTTP methods.
|
||||||
|
// The fallback is not protected by the optional HostPolicy.
|
||||||
|
//
|
||||||
|
// Because the fallback handler is run with unencrypted port 80 requests,
|
||||||
|
// the fallback should not serve TLS-only requests.
|
||||||
|
//
|
||||||
|
// If HTTPHandler is never called, the Manager will only use TLS SNI
|
||||||
|
// challenges for domain verification.
|
||||||
|
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||||
|
m.tokensMu.Lock()
|
||||||
|
defer m.tokensMu.Unlock()
|
||||||
|
m.tryHTTP01 = true
|
||||||
|
|
||||||
|
if fallback == nil {
|
||||||
|
fallback = http.HandlerFunc(handleHTTPRedirect)
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
|
||||||
|
fallback.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// A reasonable context timeout for cache and host policy only,
|
||||||
|
// because we don't wait for a new certificate issuance here.
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
if err := m.hostPolicy()(ctx, r.Host); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := m.httpToken(ctx, r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" && r.Method != "HEAD" {
|
||||||
|
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
|
||||||
|
http.Redirect(w, r, target, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripPort(hostport string) string {
|
||||||
|
host, _, err := net.SplitHostPort(hostport)
|
||||||
|
if err != nil {
|
||||||
|
return hostport
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, "443")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cert returns an existing certificate either from m.state or cache.
|
||||||
|
// If a certificate is found in cache but not in m.state, the latter will be filled
|
||||||
|
// with the cached value.
|
||||||
|
func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
|
||||||
|
m.stateMu.Lock()
|
||||||
|
if s, ok := m.state[name]; ok {
|
||||||
|
m.stateMu.Unlock()
|
||||||
|
s.RLock()
|
||||||
|
defer s.RUnlock()
|
||||||
|
return s.tlscert()
|
||||||
|
}
|
||||||
|
defer m.stateMu.Unlock()
|
||||||
|
cert, err := m.cacheGet(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signer, ok := cert.PrivateKey.(crypto.Signer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("acme/autocert: private key cannot sign")
|
||||||
|
}
|
||||||
|
if m.state == nil {
|
||||||
|
m.state = make(map[string]*certState)
|
||||||
|
}
|
||||||
|
s := &certState{
|
||||||
|
key: signer,
|
||||||
|
cert: cert.Certificate,
|
||||||
|
leaf: cert.Leaf,
|
||||||
|
}
|
||||||
|
m.state[name] = s
|
||||||
|
go m.renew(name, s.key, s.leaf.NotAfter)
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheGet always returns a valid certificate, or an error otherwise.
|
||||||
|
// If a cached certficate exists but is not valid, ErrCacheMiss is returned.
|
||||||
|
func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) {
|
||||||
|
if m.Cache == nil {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
data, err := m.Cache.Get(ctx, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// private
|
||||||
|
priv, pub := pem.Decode(data)
|
||||||
|
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
privKey, err := parsePrivateKey(priv.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// public
|
||||||
|
var pubDER [][]byte
|
||||||
|
for len(pub) > 0 {
|
||||||
|
var b *pem.Block
|
||||||
|
b, pub = pem.Decode(pub)
|
||||||
|
if b == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pubDER = append(pubDER, b.Bytes)
|
||||||
|
}
|
||||||
|
if len(pub) > 0 {
|
||||||
|
// Leftover content not consumed by pem.Decode. Corrupt. Ignore.
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify and create TLS cert
|
||||||
|
leaf, err := validCert(domain, pubDER, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
tlscert := &tls.Certificate{
|
||||||
|
Certificate: pubDER,
|
||||||
|
PrivateKey: privKey,
|
||||||
|
Leaf: leaf,
|
||||||
|
}
|
||||||
|
return tlscert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
|
||||||
|
if m.Cache == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains PEM-encoded data
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// private
|
||||||
|
switch key := tlscert.PrivateKey.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if err := encodeECDSAKey(&buf, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
b := x509.MarshalPKCS1PrivateKey(key)
|
||||||
|
pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b}
|
||||||
|
if err := pem.Encode(&buf, pb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("acme/autocert: unknown private key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// public
|
||||||
|
for _, b := range tlscert.Certificate {
|
||||||
|
pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
|
||||||
|
if err := pem.Encode(&buf, pb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Cache.Put(ctx, domain, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
|
||||||
|
b, err := x509.MarshalECPrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||||
|
return pem.Encode(w, pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createCert starts the domain ownership verification and returns a certificate
|
||||||
|
// for that domain upon success.
|
||||||
|
//
|
||||||
|
// If the domain is already being verified, it waits for the existing verification to complete.
|
||||||
|
// Either way, createCert blocks for the duration of the whole process.
|
||||||
|
func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) {
|
||||||
|
// TODO: maybe rewrite this whole piece using sync.Once
|
||||||
|
state, err := m.certState(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// state may exist if another goroutine is already working on it
|
||||||
|
// in which case just wait for it to finish
|
||||||
|
if !state.locked {
|
||||||
|
state.RLock()
|
||||||
|
defer state.RUnlock()
|
||||||
|
return state.tlscert()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are the first; state is locked.
|
||||||
|
// Unblock the readers when domain ownership is verified
|
||||||
|
// and we got the cert or the process failed.
|
||||||
|
defer state.Unlock()
|
||||||
|
state.locked = false
|
||||||
|
|
||||||
|
der, leaf, err := m.authorizedCert(ctx, state.key, domain)
|
||||||
|
if err != nil {
|
||||||
|
// Remove the failed state after some time,
|
||||||
|
// making the manager call createCert again on the following TLS hello.
|
||||||
|
time.AfterFunc(createCertRetryAfter, func() {
|
||||||
|
defer testDidRemoveState(domain)
|
||||||
|
m.stateMu.Lock()
|
||||||
|
defer m.stateMu.Unlock()
|
||||||
|
// Verify the state hasn't changed and it's still invalid
|
||||||
|
// before deleting.
|
||||||
|
s, ok := m.state[domain]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := validCert(domain, s.cert, s.key); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(m.state, domain)
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state.cert = der
|
||||||
|
state.leaf = leaf
|
||||||
|
go m.renew(domain, state.key, state.leaf.NotAfter)
|
||||||
|
return state.tlscert()
|
||||||
|
}
|
||||||
|
|
||||||
|
// certState returns a new or existing certState.
|
||||||
|
// If a new certState is returned, state.exist is false and the state is locked.
|
||||||
|
// The returned error is non-nil only in the case where a new state could not be created.
|
||||||
|
func (m *Manager) certState(domain string) (*certState, error) {
|
||||||
|
m.stateMu.Lock()
|
||||||
|
defer m.stateMu.Unlock()
|
||||||
|
if m.state == nil {
|
||||||
|
m.state = make(map[string]*certState)
|
||||||
|
}
|
||||||
|
// existing state
|
||||||
|
if state, ok := m.state[domain]; ok {
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// new locked state
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
key crypto.Signer
|
||||||
|
)
|
||||||
|
if m.ForceRSA {
|
||||||
|
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
} else {
|
||||||
|
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := &certState{
|
||||||
|
key: key,
|
||||||
|
locked: true,
|
||||||
|
}
|
||||||
|
state.Lock() // will be unlocked by m.certState caller
|
||||||
|
m.state[domain] = state
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
|
||||||
|
client, err := m.acmeClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.verify(ctx, client, domain); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
csr, err := certRequest(key, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
der, _, err = client.CreateCert(ctx, csr, 0, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
leaf, err = validCert(domain, der, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return der, leaf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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-sni-02", "tls-sni-01"}
|
||||||
|
m.tokensMu.RLock()
|
||||||
|
if m.tryHTTP01 {
|
||||||
|
challengeTypes = append(challengeTypes, "http-01")
|
||||||
|
}
|
||||||
|
m.tokensMu.RUnlock()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
|
||||||
|
}
|
||||||
|
cleanup, err := m.fulfill(ctx, client, chal)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
if _, err := client.Accept(ctx, chal); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A challenge is fulfilled and accepted: wait for the CA to validate.
|
||||||
|
if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fulfill provisions a response to the challenge chal.
|
||||||
|
// The cleanup is non-nil only if provisioning succeeded.
|
||||||
|
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
|
||||||
|
switch chal.Type {
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := client.HTTP01ChallengePath(chal.Token)
|
||||||
|
m.putHTTPToken(ctx, p, resp)
|
||||||
|
return func() { go m.deleteHTTPToken(p) }, nil
|
||||||
|
}
|
||||||
|
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 cert under the named key in both m.certTokens map
|
||||||
|
// and m.Cache.
|
||||||
|
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
|
||||||
|
m.tokensMu.Lock()
|
||||||
|
defer m.tokensMu.Unlock()
|
||||||
|
if m.certTokens == nil {
|
||||||
|
m.certTokens = make(map[string]*tls.Certificate)
|
||||||
|
}
|
||||||
|
m.certTokens[name] = cert
|
||||||
|
m.cachePut(ctx, name, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteCertToken removes the token certificate for the specified domain name
|
||||||
|
// from both m.certTokens map and m.Cache.
|
||||||
|
func (m *Manager) deleteCertToken(name string) {
|
||||||
|
m.tokensMu.Lock()
|
||||||
|
defer m.tokensMu.Unlock()
|
||||||
|
delete(m.certTokens, name)
|
||||||
|
if m.Cache != nil {
|
||||||
|
m.Cache.Delete(context.Background(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.tokensMu.RLock()
|
||||||
|
defer m.tokensMu.RUnlock()
|
||||||
|
if v, ok := m.httpTokens[tokenPath]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
if m.Cache == nil {
|
||||||
|
return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath)
|
||||||
|
}
|
||||||
|
return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// putHTTPToken stores an http-01 token value using tokenPath as key
|
||||||
|
// in both in-memory map and the optional Cache.
|
||||||
|
//
|
||||||
|
// It ignores any error returned from Cache.Put.
|
||||||
|
func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
|
||||||
|
m.tokensMu.Lock()
|
||||||
|
defer m.tokensMu.Unlock()
|
||||||
|
if m.httpTokens == nil {
|
||||||
|
m.httpTokens = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
b := []byte(val)
|
||||||
|
m.httpTokens[tokenPath] = b
|
||||||
|
if m.Cache != nil {
|
||||||
|
m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteHTTPToken removes an http-01 token value from both in-memory map
|
||||||
|
// and the optional Cache, ignoring any error returned from the latter.
|
||||||
|
//
|
||||||
|
// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
|
||||||
|
func (m *Manager) deleteHTTPToken(tokenPath string) {
|
||||||
|
m.tokensMu.Lock()
|
||||||
|
defer m.tokensMu.Unlock()
|
||||||
|
delete(m.httpTokens, tokenPath)
|
||||||
|
if m.Cache != nil {
|
||||||
|
m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
|
||||||
|
// in the Manager's optional Cache.
|
||||||
|
func httpTokenCacheKey(tokenPath string) string {
|
||||||
|
return "http-01-" + path.Base(tokenPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// - a new cert was created by m.createCert
|
||||||
|
//
|
||||||
|
// The key argument is a certificate private key.
|
||||||
|
// The exp argument is the cert expiration time (NotAfter).
|
||||||
|
func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) {
|
||||||
|
m.renewalMu.Lock()
|
||||||
|
defer m.renewalMu.Unlock()
|
||||||
|
if m.renewal[domain] != nil {
|
||||||
|
// another goroutine is already on it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.renewal == nil {
|
||||||
|
m.renewal = make(map[string]*domainRenewal)
|
||||||
|
}
|
||||||
|
dr := &domainRenewal{m: m, domain: domain, key: key}
|
||||||
|
m.renewal[domain] = dr
|
||||||
|
dr.start(exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopRenew stops all currently running cert renewal timers.
|
||||||
|
// The timers are not restarted during the lifetime of the Manager.
|
||||||
|
func (m *Manager) stopRenew() {
|
||||||
|
m.renewalMu.Lock()
|
||||||
|
defer m.renewalMu.Unlock()
|
||||||
|
for name, dr := range m.renewal {
|
||||||
|
delete(m.renewal, name)
|
||||||
|
dr.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
|
||||||
|
const keyName = "acme_account.key"
|
||||||
|
|
||||||
|
genKey := func() (*ecdsa.PrivateKey, error) {
|
||||||
|
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Cache == nil {
|
||||||
|
return genKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := m.Cache.Get(ctx, keyName)
|
||||||
|
if err == ErrCacheMiss {
|
||||||
|
key, err := genKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := encodeECDSAKey(&buf, key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, _ := pem.Decode(data)
|
||||||
|
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
||||||
|
return nil, errors.New("acme/autocert: invalid account key found in cache")
|
||||||
|
}
|
||||||
|
return parsePrivateKey(priv.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
||||||
|
m.clientMu.Lock()
|
||||||
|
defer m.clientMu.Unlock()
|
||||||
|
if m.client != nil {
|
||||||
|
return m.client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client := m.Client
|
||||||
|
if client == nil {
|
||||||
|
client = &acme.Client{DirectoryURL: acme.LetsEncryptURL}
|
||||||
|
}
|
||||||
|
if client.Key == nil {
|
||||||
|
var err error
|
||||||
|
client.Key, err = m.accountKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var contact []string
|
||||||
|
if m.Email != "" {
|
||||||
|
contact = []string{"mailto:" + m.Email}
|
||||||
|
}
|
||||||
|
a := &acme.Account{Contact: contact}
|
||||||
|
_, err := client.Register(ctx, a, m.Prompt)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) hostPolicy() HostPolicy {
|
||||||
|
if m.HostPolicy != nil {
|
||||||
|
return m.HostPolicy
|
||||||
|
}
|
||||||
|
return defaultHostPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) renewBefore() time.Duration {
|
||||||
|
if m.RenewBefore > renewJitter {
|
||||||
|
return m.RenewBefore
|
||||||
|
}
|
||||||
|
return 720 * time.Hour // 30 days
|
||||||
|
}
|
||||||
|
|
||||||
|
// certState is ready when its mutex is unlocked for reading.
|
||||||
|
type certState struct {
|
||||||
|
sync.RWMutex
|
||||||
|
locked bool // locked for read/write
|
||||||
|
key crypto.Signer // private key for cert
|
||||||
|
cert [][]byte // DER encoding
|
||||||
|
leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlscert creates a tls.Certificate from s.key and s.cert.
|
||||||
|
// Callers should wrap it in s.RLock() and s.RUnlock().
|
||||||
|
func (s *certState) tlscert() (*tls.Certificate, error) {
|
||||||
|
if s.key == nil {
|
||||||
|
return nil, errors.New("acme/autocert: missing signer")
|
||||||
|
}
|
||||||
|
if len(s.cert) == 0 {
|
||||||
|
return nil, errors.New("acme/autocert: missing certificate")
|
||||||
|
}
|
||||||
|
return &tls.Certificate{
|
||||||
|
PrivateKey: s.key,
|
||||||
|
Certificate: s.cert,
|
||||||
|
Leaf: s.leaf,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// certRequest creates a certificate request for the given common name cn
|
||||||
|
// and optional SANs.
|
||||||
|
func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) {
|
||||||
|
req := &x509.CertificateRequest{
|
||||||
|
Subject: pkix.Name{CommonName: cn},
|
||||||
|
DNSNames: san,
|
||||||
|
}
|
||||||
|
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
||||||
|
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
|
||||||
|
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
||||||
|
//
|
||||||
|
// Inspired by parsePrivateKey in crypto/tls/tls.go.
|
||||||
|
func parsePrivateKey(der []byte) (crypto.Signer, error) {
|
||||||
|
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return key, nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return key, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("acme/autocert: failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validCert parses a cert chain provided as der argument and verifies the leaf, der[0],
|
||||||
|
// corresponds to the private key, as well as the domain match and expiration dates.
|
||||||
|
// It doesn't do any revocation checking.
|
||||||
|
//
|
||||||
|
// The returned value is the verified leaf cert.
|
||||||
|
func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
|
||||||
|
// parse public part(s)
|
||||||
|
var n int
|
||||||
|
for _, b := range der {
|
||||||
|
n += len(b)
|
||||||
|
}
|
||||||
|
pub := make([]byte, n)
|
||||||
|
n = 0
|
||||||
|
for _, b := range der {
|
||||||
|
n += copy(pub[n:], b)
|
||||||
|
}
|
||||||
|
x509Cert, err := x509.ParseCertificates(pub)
|
||||||
|
if len(x509Cert) == 0 {
|
||||||
|
return nil, errors.New("acme/autocert: no public key found")
|
||||||
|
}
|
||||||
|
// verify the leaf is not expired and matches the domain name
|
||||||
|
leaf = x509Cert[0]
|
||||||
|
now := timeNow()
|
||||||
|
if now.Before(leaf.NotBefore) {
|
||||||
|
return nil, errors.New("acme/autocert: certificate is not valid yet")
|
||||||
|
}
|
||||||
|
if now.After(leaf.NotAfter) {
|
||||||
|
return nil, errors.New("acme/autocert: expired certificate")
|
||||||
|
}
|
||||||
|
if err := leaf.VerifyHostname(domain); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// ensure the leaf corresponds to the private key
|
||||||
|
switch pub := leaf.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
prv, ok := key.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
||||||
|
}
|
||||||
|
if pub.N.Cmp(prv.N) != 0 {
|
||||||
|
return nil, errors.New("acme/autocert: private key does not match public key")
|
||||||
|
}
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
prv, ok := key.(*ecdsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
||||||
|
}
|
||||||
|
if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
|
||||||
|
return nil, errors.New("acme/autocert: private key does not match public key")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("acme/autocert: unknown public key algorithm")
|
||||||
|
}
|
||||||
|
return leaf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type lockedMathRand struct {
|
||||||
|
sync.Mutex
|
||||||
|
rnd *mathrand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lockedMathRand) int63n(max int64) int64 {
|
||||||
|
r.Lock()
|
||||||
|
n := r.rnd.Int63n(max)
|
||||||
|
r.Unlock()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// For easier testing.
|
||||||
|
var (
|
||||||
|
timeNow = time.Now
|
||||||
|
|
||||||
|
// Called when a state is removed.
|
||||||
|
testDidRemoveState = func(domain string) {}
|
||||||
|
)
|
|
@ -0,0 +1,130 @@
|
||||||
|
// 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 autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrCacheMiss is returned when a certificate is not found in cache.
|
||||||
|
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
||||||
|
|
||||||
|
// Cache is used by Manager to store and retrieve previously obtained certificates
|
||||||
|
// as opaque data.
|
||||||
|
//
|
||||||
|
// The key argument of the methods refers to a domain name but need not be an FQDN.
|
||||||
|
// Cache implementations should not rely on the key naming pattern.
|
||||||
|
type Cache interface {
|
||||||
|
// Get returns a certificate data for the specified key.
|
||||||
|
// If there's no such key, Get returns ErrCacheMiss.
|
||||||
|
Get(ctx context.Context, key string) ([]byte, error)
|
||||||
|
|
||||||
|
// Put stores the data in the cache under the specified key.
|
||||||
|
// Underlying implementations may use any data storage format,
|
||||||
|
// as long as the reverse operation, Get, results in the original data.
|
||||||
|
Put(ctx context.Context, key string, data []byte) error
|
||||||
|
|
||||||
|
// Delete removes a certificate data from the cache under the specified key.
|
||||||
|
// If there's no such key in the cache, Delete returns nil.
|
||||||
|
Delete(ctx context.Context, key string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirCache implements Cache using a directory on the local filesystem.
|
||||||
|
// If the directory does not exist, it will be created with 0700 permissions.
|
||||||
|
type DirCache string
|
||||||
|
|
||||||
|
// Get reads a certificate data from the specified file name.
|
||||||
|
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
||||||
|
name = filepath.Join(string(d), name)
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
done = make(chan struct{})
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
data, err = ioutil.ReadFile(name)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put writes the certificate data to the specified file name.
|
||||||
|
// The file will be created with 0600 permissions.
|
||||||
|
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||||
|
if err := os.MkdirAll(string(d), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
var tmp string
|
||||||
|
if tmp, err = d.writeTempFile(name, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Don't overwrite the file if the context was canceled.
|
||||||
|
default:
|
||||||
|
newName := filepath.Join(string(d), name)
|
||||||
|
err = os.Rename(tmp, newName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the specified file name.
|
||||||
|
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||||
|
name = filepath.Join(string(d), name)
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
done = make(chan struct{})
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
err = os.Remove(name)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
||||||
|
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
||||||
|
// TempFile uses 0600 permissions
|
||||||
|
f, err := ioutil.TempFile(string(d), prefix)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := f.Write(b); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return f.Name(), f.Close()
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewListener returns a net.Listener that listens on the standard TLS
|
||||||
|
// port (443) on all interfaces and returns *tls.Conn connections with
|
||||||
|
// LetsEncrypt certificates for the provided domain or domains.
|
||||||
|
//
|
||||||
|
// It enables one-line HTTPS servers:
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// type instead.
|
||||||
|
//
|
||||||
|
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
||||||
|
// Service. If domains is not empty, the provided domains are passed
|
||||||
|
// to HostWhitelist. If domains is empty, the listener will do
|
||||||
|
// LetsEncrypt challenges for any requested domain, which is not
|
||||||
|
// recommended.
|
||||||
|
//
|
||||||
|
// Certificates are cached in a "golang-autocert" directory under an
|
||||||
|
// operating system-specific cache or temp directory. This may not
|
||||||
|
// be suitable for servers spanning multiple machines.
|
||||||
|
//
|
||||||
|
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||||
|
// should only be used with servers that support HTTP/2.
|
||||||
|
//
|
||||||
|
// The returned Listener also enables TCP keep-alives on the accepted
|
||||||
|
// connections. The returned *tls.Conn are returned before their TLS
|
||||||
|
// handshake has completed.
|
||||||
|
func NewListener(domains ...string) net.Listener {
|
||||||
|
m := &Manager{
|
||||||
|
Prompt: AcceptTOS,
|
||||||
|
}
|
||||||
|
if len(domains) > 0 {
|
||||||
|
m.HostPolicy = HostWhitelist(domains...)
|
||||||
|
}
|
||||||
|
dir := cacheDir()
|
||||||
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||||
|
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
||||||
|
} else {
|
||||||
|
m.Cache = DirCache(dir)
|
||||||
|
}
|
||||||
|
return m.Listener()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener listens on the standard TLS port (443) on all interfaces
|
||||||
|
// and returns a net.Listener returning *tls.Conn connections.
|
||||||
|
//
|
||||||
|
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||||
|
// should only be used with servers that support HTTP/2.
|
||||||
|
//
|
||||||
|
// The returned Listener also enables TCP keep-alives on the accepted
|
||||||
|
// connections. The returned *tls.Conn are returned before their TLS
|
||||||
|
// handshake has completed.
|
||||||
|
//
|
||||||
|
// Unlike NewListener, it is the caller's responsibility to initialize
|
||||||
|
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||||
|
func (m *Manager) Listener() net.Listener {
|
||||||
|
ln := &listener{
|
||||||
|
m: m,
|
||||||
|
conf: &tls.Config{
|
||||||
|
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
||||||
|
NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
|
||||||
|
type listener struct {
|
||||||
|
m *Manager
|
||||||
|
conf *tls.Config
|
||||||
|
|
||||||
|
tcpListener net.Listener
|
||||||
|
tcpListenErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Accept() (net.Conn, error) {
|
||||||
|
if ln.tcpListenErr != nil {
|
||||||
|
return nil, ln.tcpListenErr
|
||||||
|
}
|
||||||
|
conn, err := ln.tcpListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tcpConn := conn.(*net.TCPConn)
|
||||||
|
|
||||||
|
// Because Listener is a convenience function, help out with
|
||||||
|
// this too. This is not possible for the caller to set once
|
||||||
|
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
||||||
|
// If callers don't want this, they can do things the manual
|
||||||
|
// way and tweak as needed. But this is what net/http does
|
||||||
|
// itself, so copy that. If net/http changes, we can change
|
||||||
|
// here too.
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
|
||||||
|
return tls.Server(tcpConn, ln.conf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Addr() net.Addr {
|
||||||
|
if ln.tcpListener != nil {
|
||||||
|
return ln.tcpListener.Addr()
|
||||||
|
}
|
||||||
|
// net.Listen failed. Return something non-nil in case callers
|
||||||
|
// call Addr before Accept:
|
||||||
|
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Close() error {
|
||||||
|
if ln.tcpListenErr != nil {
|
||||||
|
return ln.tcpListenErr
|
||||||
|
}
|
||||||
|
return ln.tcpListener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func homeDir() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||||
|
}
|
||||||
|
if h := os.Getenv("HOME"); h != "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheDir() string {
|
||||||
|
const base = "golang-autocert"
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||||
|
case "windows":
|
||||||
|
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||||
|
if v := os.Getenv(ev); v != "" {
|
||||||
|
return filepath.Join(v, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Worst case:
|
||||||
|
return filepath.Join(homeDir(), base)
|
||||||
|
}
|
||||||
|
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||||
|
return filepath.Join(xdg, base)
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir(), ".cache", base)
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// 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 autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
||||||
|
const renewJitter = time.Hour
|
||||||
|
|
||||||
|
// domainRenewal tracks the state used by the periodic timers
|
||||||
|
// renewing a single domain's cert.
|
||||||
|
type domainRenewal struct {
|
||||||
|
m *Manager
|
||||||
|
domain string
|
||||||
|
key crypto.Signer
|
||||||
|
|
||||||
|
timerMu sync.Mutex
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// start starts a cert renewal timer at the time
|
||||||
|
// defined by the certificate expiration time exp.
|
||||||
|
//
|
||||||
|
// If the timer is already started, calling start is a noop.
|
||||||
|
func (dr *domainRenewal) start(exp time.Time) {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops the cert renewal timer.
|
||||||
|
// If the timer is already stopped, calling stop is a noop.
|
||||||
|
func (dr *domainRenewal) stop() {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dr.timer.Stop()
|
||||||
|
dr.timer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// renew is called periodically by a timer.
|
||||||
|
// The first renew call is kicked off by dr.start.
|
||||||
|
func (dr *domainRenewal) renew() {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
// TODO: rotate dr.key at some point?
|
||||||
|
next, err := dr.do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
next = renewJitter / 2
|
||||||
|
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||||
|
}
|
||||||
|
dr.timer = time.AfterFunc(next, dr.renew)
|
||||||
|
testDidRenewLoop(next, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateState locks and replaces the relevant Manager.state item with the given
|
||||||
|
// state. It additionally updates dr.key with the given state's key.
|
||||||
|
func (dr *domainRenewal) updateState(state *certState) {
|
||||||
|
dr.m.stateMu.Lock()
|
||||||
|
defer dr.m.stateMu.Unlock()
|
||||||
|
dr.key = state.key
|
||||||
|
dr.m.state[dr.domain] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
||||||
|
// Instead, it requests a new certificate independently and, upon success,
|
||||||
|
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
||||||
|
//
|
||||||
|
// It may lock and update the Manager.state if the expiration date of the currently
|
||||||
|
// cached cert is far enough in the future.
|
||||||
|
//
|
||||||
|
// The returned value is a time interval after which the renewal should occur again.
|
||||||
|
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||||
|
// a race is likely unavoidable in a distributed environment
|
||||||
|
// but we try nonetheless
|
||||||
|
if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
|
||||||
|
next := dr.next(tlscert.Leaf.NotAfter)
|
||||||
|
if next > dr.m.renewBefore()+renewJitter {
|
||||||
|
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||||
|
if ok {
|
||||||
|
state := &certState{
|
||||||
|
key: signer,
|
||||||
|
cert: tlscert.Certificate,
|
||||||
|
leaf: tlscert.Leaf,
|
||||||
|
}
|
||||||
|
dr.updateState(state)
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
state := &certState{
|
||||||
|
key: dr.key,
|
||||||
|
cert: der,
|
||||||
|
leaf: leaf,
|
||||||
|
}
|
||||||
|
tlscert, err := state.tlscert()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := dr.m.cachePut(ctx, dr.domain, tlscert); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dr.updateState(state)
|
||||||
|
return dr.next(leaf.NotAfter), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
||||||
|
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
||||||
|
// add a bit of randomness to renew deadline
|
||||||
|
n := pseudoRand.int63n(int64(renewJitter))
|
||||||
|
d -= time.Duration(n)
|
||||||
|
if d < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
var testDidRenewLoop = func(next time.Duration, err error) {}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
_ "crypto/sha512" // need for EC keys
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||||
|
// The result is serialized in JSON format.
|
||||||
|
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||||
|
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)
|
||||||
|
if alg == "" || !sha.Available() {
|
||||||
|
return nil, ErrUnsupportedKey
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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 := struct {
|
||||||
|
Protected string `json:"protected"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Sig string `json:"signature"`
|
||||||
|
}{
|
||||||
|
Protected: phead,
|
||||||
|
Payload: payload,
|
||||||
|
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||||
|
}
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||||
|
n := pub.N
|
||||||
|
e := big.NewInt(int64(pub.E))
|
||||||
|
// Field order is important.
|
||||||
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||||
|
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||||
|
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||||
|
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||||
|
), nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||||
|
p := pub.Curve.Params()
|
||||||
|
n := p.BitSize / 8
|
||||||
|
if p.BitSize%8 != 0 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
x := pub.X.Bytes()
|
||||||
|
if n > len(x) {
|
||||||
|
x = append(make([]byte, n-len(x)), x...)
|
||||||
|
}
|
||||||
|
y := pub.Y.Bytes()
|
||||||
|
if n > len(y) {
|
||||||
|
y = append(make([]byte, n-len(y)), y...)
|
||||||
|
}
|
||||||
|
// Field order is important.
|
||||||
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||||
|
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||||
|
p.Name,
|
||||||
|
base64.RawURLEncoding.EncodeToString(x),
|
||||||
|
base64.RawURLEncoding.EncodeToString(y),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
return "", ErrUnsupportedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwsSign signs the digest using the given key.
|
||||||
|
// It returns ErrUnsupportedKey if the key type is unknown.
|
||||||
|
// The hash is used only for RSA keys.
|
||||||
|
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return key.Sign(rand.Reader, digest, hash)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rb, sb := r.Bytes(), s.Bytes()
|
||||||
|
size := key.Params().BitSize / 8
|
||||||
|
if size%8 > 0 {
|
||||||
|
size++
|
||||||
|
}
|
||||||
|
sig := make([]byte, size*2)
|
||||||
|
copy(sig[size-len(rb):], rb)
|
||||||
|
copy(sig[size*2-len(sb):], sb)
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
return nil, ErrUnsupportedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||||
|
// to use for signing a digest with the provided key.
|
||||||
|
// It returns ("", 0) if the key is not supported.
|
||||||
|
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return "RS256", crypto.SHA256
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
switch key.Params().Name {
|
||||||
|
case "P-256":
|
||||||
|
return "ES256", crypto.SHA256
|
||||||
|
case "P-384":
|
||||||
|
return "ES384", crypto.SHA384
|
||||||
|
case "P-521":
|
||||||
|
return "ES512", crypto.SHA512
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||||
|
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||||
|
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
||||||
|
jwk, err := jwkEncode(pub)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b := sha256.Sum256([]byte(jwk))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
// 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 acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||||
|
const (
|
||||||
|
StatusUnknown = "unknown"
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusProcessing = "processing"
|
||||||
|
StatusValid = "valid"
|
||||||
|
StatusInvalid = "invalid"
|
||||||
|
StatusRevoked = "revoked"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CRLReasonCode identifies the reason for a certificate revocation.
|
||||||
|
type CRLReasonCode int
|
||||||
|
|
||||||
|
// CRL reason codes as defined in RFC 5280.
|
||||||
|
const (
|
||||||
|
CRLReasonUnspecified CRLReasonCode = 0
|
||||||
|
CRLReasonKeyCompromise CRLReasonCode = 1
|
||||||
|
CRLReasonCACompromise CRLReasonCode = 2
|
||||||
|
CRLReasonAffiliationChanged CRLReasonCode = 3
|
||||||
|
CRLReasonSuperseded CRLReasonCode = 4
|
||||||
|
CRLReasonCessationOfOperation CRLReasonCode = 5
|
||||||
|
CRLReasonCertificateHold CRLReasonCode = 6
|
||||||
|
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
||||||
|
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
||||||
|
CRLReasonAACompromise CRLReasonCode = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
type Error struct {
|
||||||
|
// StatusCode is The HTTP status code generated by the origin server.
|
||||||
|
StatusCode int
|
||||||
|
// ProblemType is a URI reference that identifies the problem type,
|
||||||
|
// typically in a "urn:acme:error:xxx" form.
|
||||||
|
ProblemType string
|
||||||
|
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||||
|
Detail string
|
||||||
|
// Header is the original server error response headers.
|
||||||
|
// It may be nil.
|
||||||
|
Header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizationError indicates that an authorization for an identifier
|
||||||
|
// did not succeed.
|
||||||
|
// It contains all errors from Challenge items of the failed Authorization.
|
||||||
|
type AuthorizationError struct {
|
||||||
|
// URI uniquely identifies the failed Authorization.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Identifier is an AuthzID.Value of the failed Authorization.
|
||||||
|
Identifier string
|
||||||
|
|
||||||
|
// Errors is a collection of non-nil error values of Challenge items
|
||||||
|
// of the failed Authorization.
|
||||||
|
Errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthorizationError) Error() string {
|
||||||
|
e := make([]string, len(a.Errors))
|
||||||
|
for i, err := range a.Errors {
|
||||||
|
e[i] = err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit reports whether err represents a rate limit error and
|
||||||
|
// any Retry-After duration returned by the server.
|
||||||
|
//
|
||||||
|
// See the following for more details on rate limiting:
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
||||||
|
func RateLimit(err error) (time.Duration, bool) {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
// Some CA implementations may return incorrect values.
|
||||||
|
// Use case-insensitive comparison.
|
||||||
|
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if e.Header == nil {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
return retryAfter(e.Header.Get("Retry-After"), 0), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account is a user account. It is associated with a private key.
|
||||||
|
type Account struct {
|
||||||
|
// URI is the account unique ID, which is also a URL used to retrieve
|
||||||
|
// account data from the CA.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Contact is a slice of contact info used during registration.
|
||||||
|
Contact []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.
|
||||||
|
AgreedTerms string
|
||||||
|
|
||||||
|
// Actual terms of a CA.
|
||||||
|
CurrentTerms string
|
||||||
|
|
||||||
|
// Authz is the authorization URL used to initiate a new authz flow.
|
||||||
|
Authz string
|
||||||
|
|
||||||
|
// Authorizations is a URI from which a list of authorizations
|
||||||
|
// granted to this account can be fetched via a GET request.
|
||||||
|
Authorizations string
|
||||||
|
|
||||||
|
// Certificates is a URI from which a list of certificates
|
||||||
|
// issued for this account can be fetched via a GET request.
|
||||||
|
Certificates string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory is ACME server discovery data.
|
||||||
|
type Directory struct {
|
||||||
|
// RegURL is an account endpoint URL, allowing for creating new
|
||||||
|
// and modifying existing accounts.
|
||||||
|
RegURL string
|
||||||
|
|
||||||
|
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||||
|
AuthzURL string
|
||||||
|
|
||||||
|
// CertURL is a new certificate issuance endpoint URL.
|
||||||
|
CertURL string
|
||||||
|
|
||||||
|
// RevokeURL is used to initiate a certificate revocation flow.
|
||||||
|
RevokeURL string
|
||||||
|
|
||||||
|
// Term is a URI identifying the current terms of service.
|
||||||
|
Terms string
|
||||||
|
|
||||||
|
// Website is an HTTP or HTTPS URL locating a website
|
||||||
|
// providing more information about the ACME server.
|
||||||
|
Website string
|
||||||
|
|
||||||
|
// CAA consists of lowercase hostname elements, which the ACME server
|
||||||
|
// recognises as referring to itself for the purposes of CAA record validation
|
||||||
|
// as defined in RFC6844.
|
||||||
|
CAA []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Token is a random value that uniquely identifies the challenge.
|
||||||
|
Token string
|
||||||
|
|
||||||
|
// Status identifies the status of this challenge.
|
||||||
|
Status string
|
||||||
|
|
||||||
|
// Error indicates the reason for an authorization failure
|
||||||
|
// when this challenge was used.
|
||||||
|
// The type of a non-nil value is *Error.
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization encodes an authorization response.
|
||||||
|
type Authorization struct {
|
||||||
|
// URI uniquely identifies a authorization.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Status identifies the status of an authorization.
|
||||||
|
Status string
|
||||||
|
|
||||||
|
// Identifier is what the account is authorized to represent.
|
||||||
|
Identifier AuthzID
|
||||||
|
|
||||||
|
// Challenges that the client needs to fulfill in order to prove possession
|
||||||
|
// of the identifier (for pending authorizations).
|
||||||
|
// For final authorizations, the challenges that were used.
|
||||||
|
Challenges []*Challenge
|
||||||
|
|
||||||
|
// A collection of sets of challenges, each of which would be sufficient
|
||||||
|
// to prove possession of the identifier.
|
||||||
|
// 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.
|
||||||
|
Combinations [][]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthzID is an identifier that an account is authorized to represent.
|
||||||
|
type AuthzID struct {
|
||||||
|
Type string // The type of identifier, e.g. "dns".
|
||||||
|
Value string // The identifier itself, e.g. "example.org".
|
||||||
|
}
|
||||||
|
|
||||||
|
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||||
|
type wireAuthz struct {
|
||||||
|
Status string
|
||||||
|
Challenges []wireChallenge
|
||||||
|
Combinations [][]int
|
||||||
|
Identifier struct {
|
||||||
|
Type string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||||
|
a := &Authorization{
|
||||||
|
URI: uri,
|
||||||
|
Status: z.Status,
|
||||||
|
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||||
|
Combinations: z.Combinations, // shallow copy
|
||||||
|
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||||
|
}
|
||||||
|
for i, v := range z.Challenges {
|
||||||
|
a.Challenges[i] = v.challenge()
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||||
|
err := &AuthorizationError{
|
||||||
|
URI: uri,
|
||||||
|
Identifier: z.Identifier.Value,
|
||||||
|
}
|
||||||
|
for _, raw := range z.Challenges {
|
||||||
|
if raw.Error != nil {
|
||||||
|
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wireChallenge is ACME JSON challenge representation.
|
||||||
|
type wireChallenge struct {
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Type string
|
||||||
|
Token string
|
||||||
|
Status string
|
||||||
|
Error *wireError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wireChallenge) challenge() *Challenge {
|
||||||
|
v := &Challenge{
|
||||||
|
URI: c.URI,
|
||||||
|
Type: c.Type,
|
||||||
|
Token: c.Token,
|
||||||
|
Status: c.Status,
|
||||||
|
}
|
||||||
|
if v.Status == "" {
|
||||||
|
v.Status = StatusPending
|
||||||
|
}
|
||||||
|
if c.Error != nil {
|
||||||
|
v.Error = c.Error.error(nil)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wireError) error(h http.Header) *Error {
|
||||||
|
return &Error{
|
||||||
|
StatusCode: e.Status,
|
||||||
|
ProblemType: e.Type,
|
||||||
|
Detail: e.Detail,
|
||||||
|
Header: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
|
||||||
|
// customizing a temporary certificate for TLS-SNI challenges.
|
||||||
|
type CertOption interface {
|
||||||
|
privateCertOpt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKey creates an option holding a private/public key pair.
|
||||||
|
// The private part signs a certificate, and the public part represents the signee.
|
||||||
|
func WithKey(key crypto.Signer) CertOption {
|
||||||
|
return &certOptKey{key}
|
||||||
|
}
|
||||||
|
|
||||||
|
type certOptKey struct {
|
||||||
|
key crypto.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*certOptKey) privateCertOpt() {}
|
||||||
|
|
||||||
|
// WithTemplate creates an option for specifying a certificate template.
|
||||||
|
// See x509.CreateCertificate for template usage details.
|
||||||
|
//
|
||||||
|
// In TLSSNIxChallengeCert methods, the template is also used as parent,
|
||||||
|
// resulting in a self-signed certificate.
|
||||||
|
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||||
|
func WithTemplate(t *x509.Certificate) CertOption {
|
||||||
|
return (*certOptTemplate)(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type certOptTemplate x509.Certificate
|
||||||
|
|
||||||
|
func (*certOptTemplate) privateCertOpt() {}
|
|
@ -171,9 +171,16 @@ func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
edwards25519.ScReduce(&hReduced, &digest)
|
edwards25519.ScReduce(&hReduced, &digest)
|
||||||
|
|
||||||
var R edwards25519.ProjectiveGroupElement
|
var R edwards25519.ProjectiveGroupElement
|
||||||
var b [32]byte
|
var s [32]byte
|
||||||
copy(b[:], sig[32:])
|
copy(s[:], sig[32:])
|
||||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
|
||||||
|
// 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
|
var checkR [32]byte
|
||||||
R.ToBytes(&checkR)
|
R.ToBytes(&checkR)
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package edwards25519
|
package edwards25519
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
// This code is a port of the public domain, “ref10” implementation of ed25519
|
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||||
// from SUPERCOP.
|
// from SUPERCOP.
|
||||||
|
|
||||||
|
@ -1769,3 +1771,23 @@ func ScReduce(out *[32]byte, s *[64]byte) {
|
||||||
out[30] = byte(s11 >> 9)
|
out[30] = byte(s11 >> 9)
|
||||||
out[31] = byte(s11 >> 17)
|
out[31] = byte(s11 >> 17)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// order is the order of Curve25519 in little-endian form.
|
||||||
|
var order = [4]uint64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0, 0x1000000000000000}
|
||||||
|
|
||||||
|
// ScMinimal returns true if the given scalar is less than the order of the
|
||||||
|
// curve.
|
||||||
|
func ScMinimal(scalar *[32]byte) bool {
|
||||||
|
for i := 3; ; i-- {
|
||||||
|
v := binary.LittleEndian.Uint64(scalar[i*8:])
|
||||||
|
if v > order[i] {
|
||||||
|
return false
|
||||||
|
} else if v < order[i] {
|
||||||
|
break
|
||||||
|
} else if i == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
// 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 core ChaCha20 function as specified
|
||||||
|
// in https://tools.ietf.org/html/rfc7539#section-2.3.
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assert that *Cipher implements cipher.Stream
|
||||||
|
var _ cipher.Stream = (*Cipher)(nil)
|
||||||
|
|
||||||
|
// Cipher is a stateful instance of ChaCha20 using a particular key
|
||||||
|
// and nonce. A *Cipher implements the cipher.Stream interface.
|
||||||
|
type Cipher struct {
|
||||||
|
key [8]uint32
|
||||||
|
nonce [3]uint32
|
||||||
|
counter uint32 // incremented after each block
|
||||||
|
buf [64]byte // buffer for unused keystream bytes
|
||||||
|
len int // number of unused keystream bytes at end of buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new ChaCha20 stream cipher with the given key and nonce.
|
||||||
|
// The initial counter value is set to 0.
|
||||||
|
func New(key [8]uint32, nonce [3]uint32) *Cipher {
|
||||||
|
return &Cipher{key: key, nonce: nonce}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// xor src with buffered keystream first
|
||||||
|
if s.len != 0 {
|
||||||
|
buf := s.buf[len(s.buf)-s.len:]
|
||||||
|
if len(src) < len(buf) {
|
||||||
|
buf = buf[:len(src)]
|
||||||
|
}
|
||||||
|
td, ts := dst[:len(buf)], src[:len(buf)] // BCE hint
|
||||||
|
for i, b := range buf {
|
||||||
|
td[i] = ts[i] ^ b
|
||||||
|
}
|
||||||
|
s.len -= len(buf)
|
||||||
|
if s.len != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.buf = [len(s.buf)]byte{} // zero the empty buffer
|
||||||
|
src = src[len(buf):]
|
||||||
|
dst = dst[len(buf):]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(src) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up a 64-byte buffer to pad out the final block if needed
|
||||||
|
// (hoisted out of the main loop to avoid spills)
|
||||||
|
rem := len(src) % 64 // length of final block
|
||||||
|
fin := len(src) - rem // index of final block
|
||||||
|
if rem > 0 {
|
||||||
|
copy(s.buf[len(s.buf)-64:], src[fin:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// qr calculates a quarter round
|
||||||
|
qr := func(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
|
||||||
|
a += b
|
||||||
|
d ^= a
|
||||||
|
d = (d << 16) | (d >> 16)
|
||||||
|
c += d
|
||||||
|
b ^= c
|
||||||
|
b = (b << 12) | (b >> 20)
|
||||||
|
a += b
|
||||||
|
d ^= a
|
||||||
|
d = (d << 8) | (d >> 24)
|
||||||
|
c += d
|
||||||
|
b ^= c
|
||||||
|
b = (b << 7) | (b >> 25)
|
||||||
|
return a, b, c, d
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChaCha20 constants
|
||||||
|
const (
|
||||||
|
j0 = 0x61707865
|
||||||
|
j1 = 0x3320646e
|
||||||
|
j2 = 0x79622d32
|
||||||
|
j3 = 0x6b206574
|
||||||
|
)
|
||||||
|
|
||||||
|
// pre-calculate most of the first round
|
||||||
|
s1, s5, s9, s13 := qr(j1, s.key[1], s.key[5], s.nonce[0])
|
||||||
|
s2, s6, s10, s14 := qr(j2, s.key[2], s.key[6], s.nonce[1])
|
||||||
|
s3, s7, s11, s15 := qr(j3, s.key[3], s.key[7], s.nonce[2])
|
||||||
|
|
||||||
|
n := len(src)
|
||||||
|
src, dst = src[:n:n], dst[:n:n] // BCE hint
|
||||||
|
for i := 0; i < n; i += 64 {
|
||||||
|
// calculate the remainder of the first round
|
||||||
|
s0, s4, s8, s12 := qr(j0, s.key[0], s.key[4], s.counter)
|
||||||
|
|
||||||
|
// execute the second round
|
||||||
|
x0, x5, x10, x15 := qr(s0, s5, s10, s15)
|
||||||
|
x1, x6, x11, x12 := qr(s1, s6, s11, s12)
|
||||||
|
x2, x7, x8, x13 := qr(s2, s7, s8, s13)
|
||||||
|
x3, x4, x9, x14 := qr(s3, s4, s9, s14)
|
||||||
|
|
||||||
|
// execute the remaining 18 rounds
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
x0, x4, x8, x12 = qr(x0, x4, x8, x12)
|
||||||
|
x1, x5, x9, x13 = qr(x1, x5, x9, x13)
|
||||||
|
x2, x6, x10, x14 = qr(x2, x6, x10, x14)
|
||||||
|
x3, x7, x11, x15 = qr(x3, x7, x11, x15)
|
||||||
|
|
||||||
|
x0, x5, x10, x15 = qr(x0, x5, x10, x15)
|
||||||
|
x1, x6, x11, x12 = qr(x1, x6, x11, x12)
|
||||||
|
x2, x7, x8, x13 = qr(x2, x7, x8, x13)
|
||||||
|
x3, x4, x9, x14 = qr(x3, x4, x9, x14)
|
||||||
|
}
|
||||||
|
|
||||||
|
x0 += j0
|
||||||
|
x1 += j1
|
||||||
|
x2 += j2
|
||||||
|
x3 += j3
|
||||||
|
|
||||||
|
x4 += s.key[0]
|
||||||
|
x5 += s.key[1]
|
||||||
|
x6 += s.key[2]
|
||||||
|
x7 += s.key[3]
|
||||||
|
x8 += s.key[4]
|
||||||
|
x9 += s.key[5]
|
||||||
|
x10 += s.key[6]
|
||||||
|
x11 += s.key[7]
|
||||||
|
|
||||||
|
x12 += s.counter
|
||||||
|
x13 += s.nonce[0]
|
||||||
|
x14 += s.nonce[1]
|
||||||
|
x15 += s.nonce[2]
|
||||||
|
|
||||||
|
// increment the counter
|
||||||
|
s.counter += 1
|
||||||
|
if s.counter == 0 {
|
||||||
|
panic("chacha20: counter overflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
// pad to 64 bytes if needed
|
||||||
|
in, out := src[i:], dst[i:]
|
||||||
|
if i == fin {
|
||||||
|
// src[fin:] has already been copied into s.buf before
|
||||||
|
// the main loop
|
||||||
|
in, out = s.buf[len(s.buf)-64:], s.buf[len(s.buf)-64:]
|
||||||
|
}
|
||||||
|
in, out = in[:64], out[:64] // BCE hint
|
||||||
|
|
||||||
|
// XOR the key stream with the source and write out the result
|
||||||
|
xor(out[0:], in[0:], x0)
|
||||||
|
xor(out[4:], in[4:], x1)
|
||||||
|
xor(out[8:], in[8:], x2)
|
||||||
|
xor(out[12:], in[12:], x3)
|
||||||
|
xor(out[16:], in[16:], x4)
|
||||||
|
xor(out[20:], in[20:], x5)
|
||||||
|
xor(out[24:], in[24:], x6)
|
||||||
|
xor(out[28:], in[28:], x7)
|
||||||
|
xor(out[32:], in[32:], x8)
|
||||||
|
xor(out[36:], in[36:], x9)
|
||||||
|
xor(out[40:], in[40:], x10)
|
||||||
|
xor(out[44:], in[44:], x11)
|
||||||
|
xor(out[48:], in[48:], x12)
|
||||||
|
xor(out[52:], in[52:], x13)
|
||||||
|
xor(out[56:], in[56:], x14)
|
||||||
|
xor(out[60:], in[60:], x15)
|
||||||
|
}
|
||||||
|
// copy any trailing bytes out of the buffer and into dst
|
||||||
|
if rem != 0 {
|
||||||
|
s.len = 64 - rem
|
||||||
|
copy(dst[fin:], s.buf[len(s.buf)-64:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance discards bytes in the key stream until the next 64 byte block
|
||||||
|
// boundary is reached and updates the counter accordingly. If the key
|
||||||
|
// stream is already at a block boundary no bytes will be discarded and
|
||||||
|
// the counter will be unchanged.
|
||||||
|
func (s *Cipher) Advance() {
|
||||||
|
s.len -= s.len % 64
|
||||||
|
if s.len == 0 {
|
||||||
|
s.buf = [len(s.buf)]byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
||||||
|
// In and out must overlap entirely or not at all. Counter contains the raw
|
||||||
|
// ChaCha20 counter bytes (i.e. block counter followed by nonce).
|
||||||
|
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
||||||
|
s := Cipher{
|
||||||
|
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]),
|
||||||
|
},
|
||||||
|
nonce: [3]uint32{
|
||||||
|
binary.LittleEndian.Uint32(counter[4:8]),
|
||||||
|
binary.LittleEndian.Uint32(counter[8:12]),
|
||||||
|
binary.LittleEndian.Uint32(counter[12:16]),
|
||||||
|
},
|
||||||
|
counter: binary.LittleEndian.Uint32(counter[0:4]),
|
||||||
|
}
|
||||||
|
s.XORKeyStream(out, in)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found src the LICENSE file.
|
||||||
|
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platforms that have fast unaligned 32-bit little endian accesses.
|
||||||
|
const unaligned = runtime.GOARCH == "386" ||
|
||||||
|
runtime.GOARCH == "amd64" ||
|
||||||
|
runtime.GOARCH == "arm64" ||
|
||||||
|
runtime.GOARCH == "ppc64le" ||
|
||||||
|
runtime.GOARCH == "s390x"
|
||||||
|
|
||||||
|
// xor reads a little endian uint32 from src, XORs it with u and
|
||||||
|
// places the result in little endian byte order in dst.
|
||||||
|
func xor(dst, src []byte, u uint32) {
|
||||||
|
_, _ = src[3], dst[3] // eliminate bounds checks
|
||||||
|
if unaligned {
|
||||||
|
// The compiler should optimize this code into
|
||||||
|
// 32-bit unaligned little endian loads and stores.
|
||||||
|
// TODO: delete once the compiler does a reliably
|
||||||
|
// good job with the generic code below.
|
||||||
|
// See issue #25111 for more details.
|
||||||
|
v := uint32(src[0])
|
||||||
|
v |= uint32(src[1]) << 8
|
||||||
|
v |= uint32(src[2]) << 16
|
||||||
|
v |= uint32(src[3]) << 24
|
||||||
|
v ^= u
|
||||||
|
dst[0] = byte(v)
|
||||||
|
dst[1] = byte(v >> 8)
|
||||||
|
dst[2] = byte(v >> 16)
|
||||||
|
dst[3] = byte(v >> 24)
|
||||||
|
} else {
|
||||||
|
dst[0] = src[0] ^ byte(u)
|
||||||
|
dst[1] = src[1] ^ byte(u>>8)
|
||||||
|
dst[2] = src[2] ^ byte(u>>16)
|
||||||
|
dst[3] = src[3] ^ byte(u>>24)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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 poly1305 implements Poly1305 one-time message authentication code as
|
||||||
|
specified in https://cr.yp.to/mac/poly1305-20050329.pdf.
|
||||||
|
|
||||||
|
Poly1305 is a fast, one-time authentication function. It is infeasible for an
|
||||||
|
attacker to generate an authenticator for a message without the key. However, a
|
||||||
|
key must only be used for a single message. Authenticating two different
|
||||||
|
messages with the same key allows an attacker to forge authenticators for other
|
||||||
|
messages with the same key.
|
||||||
|
|
||||||
|
Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was
|
||||||
|
used with a fixed key in order to generate one-time keys from an nonce.
|
||||||
|
However, in this package AES isn't used and the one-time key is specified
|
||||||
|
directly.
|
||||||
|
*/
|
||||||
|
package poly1305 // import "golang.org/x/crypto/poly1305"
|
||||||
|
|
||||||
|
import "crypto/subtle"
|
||||||
|
|
||||||
|
// TagSize is the size, in bytes, of a poly1305 authenticator.
|
||||||
|
const TagSize = 16
|
||||||
|
|
||||||
|
// Verify returns true if mac is a valid authenticator for m with the given
|
||||||
|
// key.
|
||||||
|
func Verify(mac *[16]byte, m []byte, key *[32]byte) bool {
|
||||||
|
var tmp [16]byte
|
||||||
|
Sum(&tmp, m, key)
|
||||||
|
return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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 poly1305
|
||||||
|
|
||||||
|
// This function is implemented in sum_amd64.s
|
||||||
|
//go:noescape
|
||||||
|
func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]byte)
|
||||||
|
|
||||||
|
// Sum generates an authenticator for m using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[16]byte, m []byte, key *[32]byte) {
|
||||||
|
var mPtr *byte
|
||||||
|
if len(m) > 0 {
|
||||||
|
mPtr = &m[0]
|
||||||
|
}
|
||||||
|
poly1305(out, mPtr, uint64(len(m)), key)
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
#define POLY1305_ADD(msg, h0, h1, h2) \
|
||||||
|
ADDQ 0(msg), h0; \
|
||||||
|
ADCQ 8(msg), h1; \
|
||||||
|
ADCQ $1, h2; \
|
||||||
|
LEAQ 16(msg), msg
|
||||||
|
|
||||||
|
#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3) \
|
||||||
|
MOVQ r0, AX; \
|
||||||
|
MULQ h0; \
|
||||||
|
MOVQ AX, t0; \
|
||||||
|
MOVQ DX, t1; \
|
||||||
|
MOVQ r0, AX; \
|
||||||
|
MULQ h1; \
|
||||||
|
ADDQ AX, t1; \
|
||||||
|
ADCQ $0, DX; \
|
||||||
|
MOVQ r0, t2; \
|
||||||
|
IMULQ h2, t2; \
|
||||||
|
ADDQ DX, t2; \
|
||||||
|
\
|
||||||
|
MOVQ r1, AX; \
|
||||||
|
MULQ h0; \
|
||||||
|
ADDQ AX, t1; \
|
||||||
|
ADCQ $0, DX; \
|
||||||
|
MOVQ DX, h0; \
|
||||||
|
MOVQ r1, t3; \
|
||||||
|
IMULQ h2, t3; \
|
||||||
|
MOVQ r1, AX; \
|
||||||
|
MULQ h1; \
|
||||||
|
ADDQ AX, t2; \
|
||||||
|
ADCQ DX, t3; \
|
||||||
|
ADDQ h0, t2; \
|
||||||
|
ADCQ $0, t3; \
|
||||||
|
\
|
||||||
|
MOVQ t0, h0; \
|
||||||
|
MOVQ t1, h1; \
|
||||||
|
MOVQ t2, h2; \
|
||||||
|
ANDQ $3, h2; \
|
||||||
|
MOVQ t2, t0; \
|
||||||
|
ANDQ $0xFFFFFFFFFFFFFFFC, t0; \
|
||||||
|
ADDQ t0, h0; \
|
||||||
|
ADCQ t3, h1; \
|
||||||
|
ADCQ $0, h2; \
|
||||||
|
SHRQ $2, t3, t2; \
|
||||||
|
SHRQ $2, t3; \
|
||||||
|
ADDQ t2, h0; \
|
||||||
|
ADCQ t3, h1; \
|
||||||
|
ADCQ $0, h2
|
||||||
|
|
||||||
|
DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
|
||||||
|
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
|
||||||
|
GLOBL ·poly1305Mask<>(SB), RODATA, $16
|
||||||
|
|
||||||
|
// func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]key)
|
||||||
|
TEXT ·poly1305(SB), $0-32
|
||||||
|
MOVQ out+0(FP), DI
|
||||||
|
MOVQ m+8(FP), SI
|
||||||
|
MOVQ mlen+16(FP), R15
|
||||||
|
MOVQ key+24(FP), AX
|
||||||
|
|
||||||
|
MOVQ 0(AX), R11
|
||||||
|
MOVQ 8(AX), R12
|
||||||
|
ANDQ ·poly1305Mask<>(SB), R11 // r0
|
||||||
|
ANDQ ·poly1305Mask<>+8(SB), R12 // r1
|
||||||
|
XORQ R8, R8 // h0
|
||||||
|
XORQ R9, R9 // h1
|
||||||
|
XORQ R10, R10 // h2
|
||||||
|
|
||||||
|
CMPQ R15, $16
|
||||||
|
JB bytes_between_0_and_15
|
||||||
|
|
||||||
|
loop:
|
||||||
|
POLY1305_ADD(SI, R8, R9, R10)
|
||||||
|
|
||||||
|
multiply:
|
||||||
|
POLY1305_MUL(R8, R9, R10, R11, R12, BX, CX, R13, R14)
|
||||||
|
SUBQ $16, R15
|
||||||
|
CMPQ R15, $16
|
||||||
|
JAE loop
|
||||||
|
|
||||||
|
bytes_between_0_and_15:
|
||||||
|
TESTQ R15, R15
|
||||||
|
JZ done
|
||||||
|
MOVQ $1, BX
|
||||||
|
XORQ CX, CX
|
||||||
|
XORQ R13, R13
|
||||||
|
ADDQ R15, SI
|
||||||
|
|
||||||
|
flush_buffer:
|
||||||
|
SHLQ $8, BX, CX
|
||||||
|
SHLQ $8, BX
|
||||||
|
MOVB -1(SI), R13
|
||||||
|
XORQ R13, BX
|
||||||
|
DECQ SI
|
||||||
|
DECQ R15
|
||||||
|
JNZ flush_buffer
|
||||||
|
|
||||||
|
ADDQ BX, R8
|
||||||
|
ADCQ CX, R9
|
||||||
|
ADCQ $0, R10
|
||||||
|
MOVQ $16, R15
|
||||||
|
JMP multiply
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVQ R8, AX
|
||||||
|
MOVQ R9, BX
|
||||||
|
SUBQ $0xFFFFFFFFFFFFFFFB, AX
|
||||||
|
SBBQ $0xFFFFFFFFFFFFFFFF, BX
|
||||||
|
SBBQ $3, R10
|
||||||
|
CMOVQCS R8, AX
|
||||||
|
CMOVQCS R9, BX
|
||||||
|
MOVQ key+24(FP), R8
|
||||||
|
ADDQ 16(R8), AX
|
||||||
|
ADCQ 24(R8), BX
|
||||||
|
|
||||||
|
MOVQ AX, 0(DI)
|
||||||
|
MOVQ BX, 8(DI)
|
||||||
|
RET
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build arm,!gccgo,!appengine,!nacl
|
||||||
|
|
||||||
|
package poly1305
|
||||||
|
|
||||||
|
// This function is implemented in sum_arm.s
|
||||||
|
//go:noescape
|
||||||
|
func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]byte)
|
||||||
|
|
||||||
|
// Sum generates an authenticator for m using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[16]byte, m []byte, key *[32]byte) {
|
||||||
|
var mPtr *byte
|
||||||
|
if len(m) > 0 {
|
||||||
|
mPtr = &m[0]
|
||||||
|
}
|
||||||
|
poly1305_auth_armv6(out, mPtr, uint32(len(m)), key)
|
||||||
|
}
|
|
@ -0,0 +1,427 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build arm,!gccgo,!appengine,!nacl
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 5a from the public
|
||||||
|
// domain source by Andrew Moon: github.com/floodyberry/poly1305-opt/blob/master/app/extensions/poly1305.
|
||||||
|
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x00(SB)/4, $0x3ffffff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x04(SB)/4, $0x3ffff03
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x08(SB)/4, $0x3ffc0ff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x0c(SB)/4, $0x3f03fff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x10(SB)/4, $0x00fffff
|
||||||
|
GLOBL ·poly1305_init_constants_armv6<>(SB), 8, $20
|
||||||
|
|
||||||
|
// Warning: the linker may use R11 to synthesize certain instructions. Please
|
||||||
|
// take care and verify that no synthetic instructions use it.
|
||||||
|
|
||||||
|
TEXT poly1305_init_ext_armv6<>(SB), NOSPLIT, $0
|
||||||
|
// Needs 16 bytes of stack and 64 bytes of space pointed to by R0. (It
|
||||||
|
// might look like it's only 60 bytes of space but the final four bytes
|
||||||
|
// will be written by another function.) We need to skip over four
|
||||||
|
// bytes of stack because that's saving the value of 'g'.
|
||||||
|
ADD $4, R13, R8
|
||||||
|
MOVM.IB [R4-R7], (R8)
|
||||||
|
MOVM.IA.W (R1), [R2-R5]
|
||||||
|
MOVW $·poly1305_init_constants_armv6<>(SB), R7
|
||||||
|
MOVW R2, R8
|
||||||
|
MOVW R2>>26, R9
|
||||||
|
MOVW R3>>20, g
|
||||||
|
MOVW R4>>14, R11
|
||||||
|
MOVW R5>>8, R12
|
||||||
|
ORR R3<<6, R9, R9
|
||||||
|
ORR R4<<12, g, g
|
||||||
|
ORR R5<<18, R11, R11
|
||||||
|
MOVM.IA (R7), [R2-R6]
|
||||||
|
AND R8, R2, R2
|
||||||
|
AND R9, R3, R3
|
||||||
|
AND g, R4, R4
|
||||||
|
AND R11, R5, R5
|
||||||
|
AND R12, R6, R6
|
||||||
|
MOVM.IA.W [R2-R6], (R0)
|
||||||
|
EOR R2, R2, R2
|
||||||
|
EOR R3, R3, R3
|
||||||
|
EOR R4, R4, R4
|
||||||
|
EOR R5, R5, R5
|
||||||
|
EOR R6, R6, R6
|
||||||
|
MOVM.IA.W [R2-R6], (R0)
|
||||||
|
MOVM.IA.W (R1), [R2-R5]
|
||||||
|
MOVM.IA [R2-R6], (R0)
|
||||||
|
ADD $20, R13, R0
|
||||||
|
MOVM.DA (R0), [R4-R7]
|
||||||
|
RET
|
||||||
|
|
||||||
|
#define MOVW_UNALIGNED(Rsrc, Rdst, Rtmp, offset) \
|
||||||
|
MOVBU (offset+0)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+0)(Rdst); \
|
||||||
|
MOVBU (offset+1)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+1)(Rdst); \
|
||||||
|
MOVBU (offset+2)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+2)(Rdst); \
|
||||||
|
MOVBU (offset+3)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+3)(Rdst)
|
||||||
|
|
||||||
|
TEXT poly1305_blocks_armv6<>(SB), NOSPLIT, $0
|
||||||
|
// Needs 24 bytes of stack for saved registers and then 88 bytes of
|
||||||
|
// scratch space after that. We assume that 24 bytes at (R13) have
|
||||||
|
// already been used: four bytes for the link register saved in the
|
||||||
|
// prelude of poly1305_auth_armv6, four bytes for saving the value of g
|
||||||
|
// in that function and 16 bytes of scratch space used around
|
||||||
|
// poly1305_finish_ext_armv6_skip1.
|
||||||
|
ADD $24, R13, R12
|
||||||
|
MOVM.IB [R4-R8, R14], (R12)
|
||||||
|
MOVW R0, 88(R13)
|
||||||
|
MOVW R1, 92(R13)
|
||||||
|
MOVW R2, 96(R13)
|
||||||
|
MOVW R1, R14
|
||||||
|
MOVW R2, R12
|
||||||
|
MOVW 56(R0), R8
|
||||||
|
WORD $0xe1180008 // TST R8, R8 not working see issue 5921
|
||||||
|
EOR R6, R6, R6
|
||||||
|
MOVW.EQ $(1<<24), R6
|
||||||
|
MOVW R6, 84(R13)
|
||||||
|
ADD $116, R13, g
|
||||||
|
MOVM.IA (R0), [R0-R9]
|
||||||
|
MOVM.IA [R0-R4], (g)
|
||||||
|
CMP $16, R12
|
||||||
|
BLO poly1305_blocks_armv6_done
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop:
|
||||||
|
WORD $0xe31e0003 // TST R14, #3 not working see issue 5921
|
||||||
|
BEQ poly1305_blocks_armv6_mainloop_aligned
|
||||||
|
ADD $100, R13, g
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 0)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 4)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 8)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 12)
|
||||||
|
MOVM.IA (g), [R0-R3]
|
||||||
|
ADD $16, R14
|
||||||
|
B poly1305_blocks_armv6_mainloop_loaded
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop_aligned:
|
||||||
|
MOVM.IA.W (R14), [R0-R3]
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop_loaded:
|
||||||
|
MOVW R0>>26, g
|
||||||
|
MOVW R1>>20, R11
|
||||||
|
MOVW R2>>14, R12
|
||||||
|
MOVW R14, 92(R13)
|
||||||
|
MOVW R3>>8, R4
|
||||||
|
ORR R1<<6, g, g
|
||||||
|
ORR R2<<12, R11, R11
|
||||||
|
ORR R3<<18, R12, R12
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
MOVW 84(R13), R3
|
||||||
|
BIC $0xfc000000, R11, R11
|
||||||
|
BIC $0xfc000000, R12, R12
|
||||||
|
ADD R0, R5, R5
|
||||||
|
ADD g, R6, R6
|
||||||
|
ORR R3, R4, R4
|
||||||
|
ADD R11, R7, R7
|
||||||
|
ADD $116, R13, R14
|
||||||
|
ADD R12, R8, R8
|
||||||
|
ADD R4, R9, R9
|
||||||
|
MOVM.IA (R14), [R0-R4]
|
||||||
|
MULLU R4, R5, (R11, g)
|
||||||
|
MULLU R3, R5, (R14, R12)
|
||||||
|
MULALU R3, R6, (R11, g)
|
||||||
|
MULALU R2, R6, (R14, R12)
|
||||||
|
MULALU R2, R7, (R11, g)
|
||||||
|
MULALU R1, R7, (R14, R12)
|
||||||
|
ADD R4<<2, R4, R4
|
||||||
|
ADD R3<<2, R3, R3
|
||||||
|
MULALU R1, R8, (R11, g)
|
||||||
|
MULALU R0, R8, (R14, R12)
|
||||||
|
MULALU R0, R9, (R11, g)
|
||||||
|
MULALU R4, R9, (R14, R12)
|
||||||
|
MOVW g, 76(R13)
|
||||||
|
MOVW R11, 80(R13)
|
||||||
|
MOVW R12, 68(R13)
|
||||||
|
MOVW R14, 72(R13)
|
||||||
|
MULLU R2, R5, (R11, g)
|
||||||
|
MULLU R1, R5, (R14, R12)
|
||||||
|
MULALU R1, R6, (R11, g)
|
||||||
|
MULALU R0, R6, (R14, R12)
|
||||||
|
MULALU R0, R7, (R11, g)
|
||||||
|
MULALU R4, R7, (R14, R12)
|
||||||
|
ADD R2<<2, R2, R2
|
||||||
|
ADD R1<<2, R1, R1
|
||||||
|
MULALU R4, R8, (R11, g)
|
||||||
|
MULALU R3, R8, (R14, R12)
|
||||||
|
MULALU R3, R9, (R11, g)
|
||||||
|
MULALU R2, R9, (R14, R12)
|
||||||
|
MOVW g, 60(R13)
|
||||||
|
MOVW R11, 64(R13)
|
||||||
|
MOVW R12, 52(R13)
|
||||||
|
MOVW R14, 56(R13)
|
||||||
|
MULLU R0, R5, (R11, g)
|
||||||
|
MULALU R4, R6, (R11, g)
|
||||||
|
MULALU R3, R7, (R11, g)
|
||||||
|
MULALU R2, R8, (R11, g)
|
||||||
|
MULALU R1, R9, (R11, g)
|
||||||
|
ADD $52, R13, R0
|
||||||
|
MOVM.IA (R0), [R0-R7]
|
||||||
|
MOVW g>>26, R12
|
||||||
|
MOVW R4>>26, R14
|
||||||
|
ORR R11<<6, R12, R12
|
||||||
|
ORR R5<<6, R14, R14
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
BIC $0xfc000000, R4, R4
|
||||||
|
ADD.S R12, R0, R0
|
||||||
|
ADC $0, R1, R1
|
||||||
|
ADD.S R14, R6, R6
|
||||||
|
ADC $0, R7, R7
|
||||||
|
MOVW R0>>26, R12
|
||||||
|
MOVW R6>>26, R14
|
||||||
|
ORR R1<<6, R12, R12
|
||||||
|
ORR R7<<6, R14, R14
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
BIC $0xfc000000, R6, R6
|
||||||
|
ADD R14<<2, R14, R14
|
||||||
|
ADD.S R12, R2, R2
|
||||||
|
ADC $0, R3, R3
|
||||||
|
ADD R14, g, g
|
||||||
|
MOVW R2>>26, R12
|
||||||
|
MOVW g>>26, R14
|
||||||
|
ORR R3<<6, R12, R12
|
||||||
|
BIC $0xfc000000, g, R5
|
||||||
|
BIC $0xfc000000, R2, R7
|
||||||
|
ADD R12, R4, R4
|
||||||
|
ADD R14, R0, R0
|
||||||
|
MOVW R4>>26, R12
|
||||||
|
BIC $0xfc000000, R4, R8
|
||||||
|
ADD R12, R6, R9
|
||||||
|
MOVW 96(R13), R12
|
||||||
|
MOVW 92(R13), R14
|
||||||
|
MOVW R0, R6
|
||||||
|
CMP $32, R12
|
||||||
|
SUB $16, R12, R12
|
||||||
|
MOVW R12, 96(R13)
|
||||||
|
BHS poly1305_blocks_armv6_mainloop
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_done:
|
||||||
|
MOVW 88(R13), R12
|
||||||
|
MOVW R5, 20(R12)
|
||||||
|
MOVW R6, 24(R12)
|
||||||
|
MOVW R7, 28(R12)
|
||||||
|
MOVW R8, 32(R12)
|
||||||
|
MOVW R9, 36(R12)
|
||||||
|
ADD $48, R13, R0
|
||||||
|
MOVM.DA (R0), [R4-R8, R14]
|
||||||
|
RET
|
||||||
|
|
||||||
|
#define MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp) \
|
||||||
|
MOVBU.P 1(Rsrc), Rtmp; \
|
||||||
|
MOVBU.P Rtmp, 1(Rdst); \
|
||||||
|
MOVBU.P 1(Rsrc), Rtmp; \
|
||||||
|
MOVBU.P Rtmp, 1(Rdst)
|
||||||
|
|
||||||
|
#define MOVWP_UNALIGNED(Rsrc, Rdst, Rtmp) \
|
||||||
|
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp); \
|
||||||
|
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp)
|
||||||
|
|
||||||
|
// func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]key)
|
||||||
|
TEXT ·poly1305_auth_armv6(SB), $196-16
|
||||||
|
// The value 196, just above, is the sum of 64 (the size of the context
|
||||||
|
// structure) and 132 (the amount of stack needed).
|
||||||
|
//
|
||||||
|
// At this point, the stack pointer (R13) has been moved down. It
|
||||||
|
// points to the saved link register and there's 196 bytes of free
|
||||||
|
// space above it.
|
||||||
|
//
|
||||||
|
// The stack for this function looks like:
|
||||||
|
//
|
||||||
|
// +---------------------
|
||||||
|
// |
|
||||||
|
// | 64 bytes of context structure
|
||||||
|
// |
|
||||||
|
// +---------------------
|
||||||
|
// |
|
||||||
|
// | 112 bytes for poly1305_blocks_armv6
|
||||||
|
// |
|
||||||
|
// +---------------------
|
||||||
|
// | 16 bytes of final block, constructed at
|
||||||
|
// | poly1305_finish_ext_armv6_skip8
|
||||||
|
// +---------------------
|
||||||
|
// | four bytes of saved 'g'
|
||||||
|
// +---------------------
|
||||||
|
// | lr, saved by prelude <- R13 points here
|
||||||
|
// +---------------------
|
||||||
|
MOVW g, 4(R13)
|
||||||
|
|
||||||
|
MOVW out+0(FP), R4
|
||||||
|
MOVW m+4(FP), R5
|
||||||
|
MOVW mlen+8(FP), R6
|
||||||
|
MOVW key+12(FP), R7
|
||||||
|
|
||||||
|
ADD $136, R13, R0 // 136 = 4 + 4 + 16 + 112
|
||||||
|
MOVW R7, R1
|
||||||
|
|
||||||
|
// poly1305_init_ext_armv6 will write to the stack from R13+4, but
|
||||||
|
// that's ok because none of the other values have been written yet.
|
||||||
|
BL poly1305_init_ext_armv6<>(SB)
|
||||||
|
BIC.S $15, R6, R2
|
||||||
|
BEQ poly1305_auth_armv6_noblocks
|
||||||
|
ADD $136, R13, R0
|
||||||
|
MOVW R5, R1
|
||||||
|
ADD R2, R5, R5
|
||||||
|
SUB R2, R6, R6
|
||||||
|
BL poly1305_blocks_armv6<>(SB)
|
||||||
|
|
||||||
|
poly1305_auth_armv6_noblocks:
|
||||||
|
ADD $136, R13, R0
|
||||||
|
MOVW R5, R1
|
||||||
|
MOVW R6, R2
|
||||||
|
MOVW R4, R3
|
||||||
|
|
||||||
|
MOVW R0, R5
|
||||||
|
MOVW R1, R6
|
||||||
|
MOVW R2, R7
|
||||||
|
MOVW R3, R8
|
||||||
|
AND.S R2, R2, R2
|
||||||
|
BEQ poly1305_finish_ext_armv6_noremaining
|
||||||
|
EOR R0, R0
|
||||||
|
ADD $8, R13, R9 // 8 = offset to 16 byte scratch space
|
||||||
|
MOVW R0, (R9)
|
||||||
|
MOVW R0, 4(R9)
|
||||||
|
MOVW R0, 8(R9)
|
||||||
|
MOVW R0, 12(R9)
|
||||||
|
WORD $0xe3110003 // TST R1, #3 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_aligned
|
||||||
|
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip8
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip8:
|
||||||
|
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip4
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip4:
|
||||||
|
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip2
|
||||||
|
MOVHUP_UNALIGNED(R1, R9, g)
|
||||||
|
B poly1305_finish_ext_armv6_skip2
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_aligned:
|
||||||
|
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip8_aligned
|
||||||
|
MOVM.IA.W (R1), [g-R11]
|
||||||
|
MOVM.IA.W [g-R11], (R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip8_aligned:
|
||||||
|
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip4_aligned
|
||||||
|
MOVW.P 4(R1), g
|
||||||
|
MOVW.P g, 4(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip4_aligned:
|
||||||
|
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip2
|
||||||
|
MOVHU.P 2(R1), g
|
||||||
|
MOVH.P g, 2(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip2:
|
||||||
|
WORD $0xe3120001 // TST $1, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip1
|
||||||
|
MOVBU.P 1(R1), g
|
||||||
|
MOVBU.P g, 1(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip1:
|
||||||
|
MOVW $1, R11
|
||||||
|
MOVBU R11, 0(R9)
|
||||||
|
MOVW R11, 56(R5)
|
||||||
|
MOVW R5, R0
|
||||||
|
ADD $8, R13, R1
|
||||||
|
MOVW $16, R2
|
||||||
|
BL poly1305_blocks_armv6<>(SB)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_noremaining:
|
||||||
|
MOVW 20(R5), R0
|
||||||
|
MOVW 24(R5), R1
|
||||||
|
MOVW 28(R5), R2
|
||||||
|
MOVW 32(R5), R3
|
||||||
|
MOVW 36(R5), R4
|
||||||
|
MOVW R4>>26, R12
|
||||||
|
BIC $0xfc000000, R4, R4
|
||||||
|
ADD R12<<2, R12, R12
|
||||||
|
ADD R12, R0, R0
|
||||||
|
MOVW R0>>26, R12
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
ADD R12, R1, R1
|
||||||
|
MOVW R1>>26, R12
|
||||||
|
BIC $0xfc000000, R1, R1
|
||||||
|
ADD R12, R2, R2
|
||||||
|
MOVW R2>>26, R12
|
||||||
|
BIC $0xfc000000, R2, R2
|
||||||
|
ADD R12, R3, R3
|
||||||
|
MOVW R3>>26, R12
|
||||||
|
BIC $0xfc000000, R3, R3
|
||||||
|
ADD R12, R4, R4
|
||||||
|
ADD $5, R0, R6
|
||||||
|
MOVW R6>>26, R12
|
||||||
|
BIC $0xfc000000, R6, R6
|
||||||
|
ADD R12, R1, R7
|
||||||
|
MOVW R7>>26, R12
|
||||||
|
BIC $0xfc000000, R7, R7
|
||||||
|
ADD R12, R2, g
|
||||||
|
MOVW g>>26, R12
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
ADD R12, R3, R11
|
||||||
|
MOVW $-(1<<26), R12
|
||||||
|
ADD R11>>26, R12, R12
|
||||||
|
BIC $0xfc000000, R11, R11
|
||||||
|
ADD R12, R4, R9
|
||||||
|
MOVW R9>>31, R12
|
||||||
|
SUB $1, R12
|
||||||
|
AND R12, R6, R6
|
||||||
|
AND R12, R7, R7
|
||||||
|
AND R12, g, g
|
||||||
|
AND R12, R11, R11
|
||||||
|
AND R12, R9, R9
|
||||||
|
MVN R12, R12
|
||||||
|
AND R12, R0, R0
|
||||||
|
AND R12, R1, R1
|
||||||
|
AND R12, R2, R2
|
||||||
|
AND R12, R3, R3
|
||||||
|
AND R12, R4, R4
|
||||||
|
ORR R6, R0, R0
|
||||||
|
ORR R7, R1, R1
|
||||||
|
ORR g, R2, R2
|
||||||
|
ORR R11, R3, R3
|
||||||
|
ORR R9, R4, R4
|
||||||
|
ORR R1<<26, R0, R0
|
||||||
|
MOVW R1>>6, R1
|
||||||
|
ORR R2<<20, R1, R1
|
||||||
|
MOVW R2>>12, R2
|
||||||
|
ORR R3<<14, R2, R2
|
||||||
|
MOVW R3>>18, R3
|
||||||
|
ORR R4<<8, R3, R3
|
||||||
|
MOVW 40(R5), R6
|
||||||
|
MOVW 44(R5), R7
|
||||||
|
MOVW 48(R5), g
|
||||||
|
MOVW 52(R5), R11
|
||||||
|
ADD.S R6, R0, R0
|
||||||
|
ADC.S R7, R1, R1
|
||||||
|
ADC.S g, R2, R2
|
||||||
|
ADC.S R11, R3, R3
|
||||||
|
MOVM.IA [R0-R3], (R8)
|
||||||
|
MOVW R5, R12
|
||||||
|
EOR R0, R0, R0
|
||||||
|
EOR R1, R1, R1
|
||||||
|
EOR R2, R2, R2
|
||||||
|
EOR R3, R3, R3
|
||||||
|
EOR R4, R4, R4
|
||||||
|
EOR R5, R5, R5
|
||||||
|
EOR R6, R6, R6
|
||||||
|
EOR R7, R7, R7
|
||||||
|
MOVM.IA.W [R0-R7], (R12)
|
||||||
|
MOVM.IA [R0-R7], (R12)
|
||||||
|
MOVW 4(R13), g
|
||||||
|
RET
|
|
@ -0,0 +1,141 @@
|
||||||
|
// 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,!arm gccgo appengine nacl
|
||||||
|
|
||||||
|
package poly1305
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// Sum generates an authenticator for msg using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[TagSize]byte, msg []byte, key *[32]byte) {
|
||||||
|
var (
|
||||||
|
h0, h1, h2, h3, h4 uint32 // the hash accumulators
|
||||||
|
r0, r1, r2, r3, r4 uint64 // the r part of the key
|
||||||
|
)
|
||||||
|
|
||||||
|
r0 = uint64(binary.LittleEndian.Uint32(key[0:]) & 0x3ffffff)
|
||||||
|
r1 = uint64((binary.LittleEndian.Uint32(key[3:]) >> 2) & 0x3ffff03)
|
||||||
|
r2 = uint64((binary.LittleEndian.Uint32(key[6:]) >> 4) & 0x3ffc0ff)
|
||||||
|
r3 = uint64((binary.LittleEndian.Uint32(key[9:]) >> 6) & 0x3f03fff)
|
||||||
|
r4 = uint64((binary.LittleEndian.Uint32(key[12:]) >> 8) & 0x00fffff)
|
||||||
|
|
||||||
|
R1, R2, R3, R4 := r1*5, r2*5, r3*5, r4*5
|
||||||
|
|
||||||
|
for len(msg) >= TagSize {
|
||||||
|
// h += msg
|
||||||
|
h0 += binary.LittleEndian.Uint32(msg[0:]) & 0x3ffffff
|
||||||
|
h1 += (binary.LittleEndian.Uint32(msg[3:]) >> 2) & 0x3ffffff
|
||||||
|
h2 += (binary.LittleEndian.Uint32(msg[6:]) >> 4) & 0x3ffffff
|
||||||
|
h3 += (binary.LittleEndian.Uint32(msg[9:]) >> 6) & 0x3ffffff
|
||||||
|
h4 += (binary.LittleEndian.Uint32(msg[12:]) >> 8) | (1 << 24)
|
||||||
|
|
||||||
|
// h *= r
|
||||||
|
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
|
||||||
|
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
|
||||||
|
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
|
||||||
|
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
|
||||||
|
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
|
||||||
|
|
||||||
|
// h %= p
|
||||||
|
h0 = uint32(d0) & 0x3ffffff
|
||||||
|
h1 = uint32(d1) & 0x3ffffff
|
||||||
|
h2 = uint32(d2) & 0x3ffffff
|
||||||
|
h3 = uint32(d3) & 0x3ffffff
|
||||||
|
h4 = uint32(d4) & 0x3ffffff
|
||||||
|
|
||||||
|
h0 += uint32(d4>>26) * 5
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 = h0 & 0x3ffffff
|
||||||
|
|
||||||
|
msg = msg[TagSize:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
var block [TagSize]byte
|
||||||
|
off := copy(block[:], msg)
|
||||||
|
block[off] = 0x01
|
||||||
|
|
||||||
|
// h += msg
|
||||||
|
h0 += binary.LittleEndian.Uint32(block[0:]) & 0x3ffffff
|
||||||
|
h1 += (binary.LittleEndian.Uint32(block[3:]) >> 2) & 0x3ffffff
|
||||||
|
h2 += (binary.LittleEndian.Uint32(block[6:]) >> 4) & 0x3ffffff
|
||||||
|
h3 += (binary.LittleEndian.Uint32(block[9:]) >> 6) & 0x3ffffff
|
||||||
|
h4 += (binary.LittleEndian.Uint32(block[12:]) >> 8)
|
||||||
|
|
||||||
|
// h *= r
|
||||||
|
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
|
||||||
|
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
|
||||||
|
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
|
||||||
|
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
|
||||||
|
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
|
||||||
|
|
||||||
|
// h %= p
|
||||||
|
h0 = uint32(d0) & 0x3ffffff
|
||||||
|
h1 = uint32(d1) & 0x3ffffff
|
||||||
|
h2 = uint32(d2) & 0x3ffffff
|
||||||
|
h3 = uint32(d3) & 0x3ffffff
|
||||||
|
h4 = uint32(d4) & 0x3ffffff
|
||||||
|
|
||||||
|
h0 += uint32(d4>>26) * 5
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 = h0 & 0x3ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
// h %= p reduction
|
||||||
|
h2 += h1 >> 26
|
||||||
|
h1 &= 0x3ffffff
|
||||||
|
h3 += h2 >> 26
|
||||||
|
h2 &= 0x3ffffff
|
||||||
|
h4 += h3 >> 26
|
||||||
|
h3 &= 0x3ffffff
|
||||||
|
h0 += 5 * (h4 >> 26)
|
||||||
|
h4 &= 0x3ffffff
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 &= 0x3ffffff
|
||||||
|
|
||||||
|
// h - p
|
||||||
|
t0 := h0 + 5
|
||||||
|
t1 := h1 + (t0 >> 26)
|
||||||
|
t2 := h2 + (t1 >> 26)
|
||||||
|
t3 := h3 + (t2 >> 26)
|
||||||
|
t4 := h4 + (t3 >> 26) - (1 << 26)
|
||||||
|
t0 &= 0x3ffffff
|
||||||
|
t1 &= 0x3ffffff
|
||||||
|
t2 &= 0x3ffffff
|
||||||
|
t3 &= 0x3ffffff
|
||||||
|
|
||||||
|
// select h if h < p else h - p
|
||||||
|
t_mask := (t4 >> 31) - 1
|
||||||
|
h_mask := ^t_mask
|
||||||
|
h0 = (h0 & h_mask) | (t0 & t_mask)
|
||||||
|
h1 = (h1 & h_mask) | (t1 & t_mask)
|
||||||
|
h2 = (h2 & h_mask) | (t2 & t_mask)
|
||||||
|
h3 = (h3 & h_mask) | (t3 & t_mask)
|
||||||
|
h4 = (h4 & h_mask) | (t4 & t_mask)
|
||||||
|
|
||||||
|
// h %= 2^128
|
||||||
|
h0 |= h1 << 26
|
||||||
|
h1 = ((h1 >> 6) | (h2 << 20))
|
||||||
|
h2 = ((h2 >> 12) | (h3 << 14))
|
||||||
|
h3 = ((h3 >> 18) | (h4 << 8))
|
||||||
|
|
||||||
|
// s: the s part of the key
|
||||||
|
// tag = (h + s) % (2^128)
|
||||||
|
t := uint64(h0) + uint64(binary.LittleEndian.Uint32(key[16:]))
|
||||||
|
h0 = uint32(t)
|
||||||
|
t = uint64(h1) + uint64(binary.LittleEndian.Uint32(key[20:])) + (t >> 32)
|
||||||
|
h1 = uint32(t)
|
||||||
|
t = uint64(h2) + uint64(binary.LittleEndian.Uint32(key[24:])) + (t >> 32)
|
||||||
|
h2 = uint32(t)
|
||||||
|
t = uint64(h3) + uint64(binary.LittleEndian.Uint32(key[28:])) + (t >> 32)
|
||||||
|
h3 = uint32(t)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(out[0:], h0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:], h1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:], h2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:], h3)
|
||||||
|
}
|
|
@ -44,7 +44,9 @@ type Signature struct {
|
||||||
const CertTimeInfinity = 1<<64 - 1
|
const CertTimeInfinity = 1<<64 - 1
|
||||||
|
|
||||||
// An Certificate represents an OpenSSH certificate as defined in
|
// An Certificate represents an OpenSSH certificate as defined in
|
||||||
// [PROTOCOL.certkeys]?rev=1.8.
|
// [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the
|
||||||
|
// PublicKey interface, so it can be unmarshaled using
|
||||||
|
// ParsePublicKey.
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
Nonce []byte
|
Nonce []byte
|
||||||
Key PublicKey
|
Key PublicKey
|
||||||
|
@ -340,10 +342,10 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
||||||
// the signature of the certificate.
|
// the signature of the certificate.
|
||||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
for opt, _ := range cert.CriticalOptions {
|
for opt := range cert.CriticalOptions {
|
||||||
// sourceAddressCriticalOption will be enforced by
|
// sourceAddressCriticalOption will be enforced by
|
||||||
// serverAuthenticate
|
// serverAuthenticate
|
||||||
if opt == sourceAddressCriticalOption {
|
if opt == sourceAddressCriticalOption {
|
||||||
|
|
|
@ -205,32 +205,32 @@ type channel struct {
|
||||||
|
|
||||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
// writePacket sends a packet. If the packet is a channel close, it updates
|
||||||
// sentClose. This method takes the lock c.writeMu.
|
// sentClose. This method takes the lock c.writeMu.
|
||||||
func (c *channel) writePacket(packet []byte) error {
|
func (ch *channel) writePacket(packet []byte) error {
|
||||||
c.writeMu.Lock()
|
ch.writeMu.Lock()
|
||||||
if c.sentClose {
|
if ch.sentClose {
|
||||||
c.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
c.sentClose = (packet[0] == msgChannelClose)
|
ch.sentClose = (packet[0] == msgChannelClose)
|
||||||
err := c.mux.conn.writePacket(packet)
|
err := ch.mux.conn.writePacket(packet)
|
||||||
c.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *channel) sendMessage(msg interface{}) error {
|
func (ch *channel) sendMessage(msg interface{}) error {
|
||||||
if debugMux {
|
if debugMux {
|
||||||
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
|
log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Marshal(msg)
|
p := Marshal(msg)
|
||||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
binary.BigEndian.PutUint32(p[1:], ch.remoteId)
|
||||||
return c.writePacket(p)
|
return ch.writePacket(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteExtended writes data to a specific extended stream. These streams are
|
// WriteExtended writes data to a specific extended stream. These streams are
|
||||||
// used, for example, for stderr.
|
// used, for example, for stderr.
|
||||||
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
||||||
if c.sentEOF {
|
if ch.sentEOF {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
||||||
|
@ -241,16 +241,16 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
||||||
opCode = msgChannelExtendedData
|
opCode = msgChannelExtendedData
|
||||||
}
|
}
|
||||||
|
|
||||||
c.writeMu.Lock()
|
ch.writeMu.Lock()
|
||||||
packet := c.packetPool[extendedCode]
|
packet := ch.packetPool[extendedCode]
|
||||||
// We don't remove the buffer from packetPool, so
|
// We don't remove the buffer from packetPool, so
|
||||||
// WriteExtended calls from different goroutines will be
|
// WriteExtended calls from different goroutines will be
|
||||||
// flagged as errors by the race detector.
|
// flagged as errors by the race detector.
|
||||||
c.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
|
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
space := min(c.maxRemotePayload, len(data))
|
space := min(ch.maxRemotePayload, len(data))
|
||||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
if space, err = ch.remoteWin.reserve(space); err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
if want := headerLength + space; uint32(cap(packet)) < want {
|
||||||
|
@ -262,13 +262,13 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
||||||
todo := data[:space]
|
todo := data[:space]
|
||||||
|
|
||||||
packet[0] = opCode
|
packet[0] = opCode
|
||||||
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
|
binary.BigEndian.PutUint32(packet[1:], ch.remoteId)
|
||||||
if extendedCode > 0 {
|
if extendedCode > 0 {
|
||||||
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
||||||
}
|
}
|
||||||
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
||||||
copy(packet[headerLength:], todo)
|
copy(packet[headerLength:], todo)
|
||||||
if err = c.writePacket(packet); err != nil {
|
if err = ch.writePacket(packet); err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,14 +276,14 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
||||||
data = data[len(todo):]
|
data = data[len(todo):]
|
||||||
}
|
}
|
||||||
|
|
||||||
c.writeMu.Lock()
|
ch.writeMu.Lock()
|
||||||
c.packetPool[extendedCode] = packet
|
ch.packetPool[extendedCode] = packet
|
||||||
c.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *channel) handleData(packet []byte) error {
|
func (ch *channel) handleData(packet []byte) error {
|
||||||
headerLen := 9
|
headerLen := 9
|
||||||
isExtendedData := packet[0] == msgChannelExtendedData
|
isExtendedData := packet[0] == msgChannelExtendedData
|
||||||
if isExtendedData {
|
if isExtendedData {
|
||||||
|
@ -303,7 +303,7 @@ func (c *channel) handleData(packet []byte) error {
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if length > c.maxIncomingPayload {
|
if length > ch.maxIncomingPayload {
|
||||||
// TODO(hanwen): should send Disconnect?
|
// TODO(hanwen): should send Disconnect?
|
||||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
||||||
}
|
}
|
||||||
|
@ -313,21 +313,21 @@ func (c *channel) handleData(packet []byte) error {
|
||||||
return errors.New("ssh: wrong packet length")
|
return errors.New("ssh: wrong packet length")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.windowMu.Lock()
|
ch.windowMu.Lock()
|
||||||
if c.myWindow < length {
|
if ch.myWindow < length {
|
||||||
c.windowMu.Unlock()
|
ch.windowMu.Unlock()
|
||||||
// TODO(hanwen): should send Disconnect with reason?
|
// TODO(hanwen): should send Disconnect with reason?
|
||||||
return errors.New("ssh: remote side wrote too much")
|
return errors.New("ssh: remote side wrote too much")
|
||||||
}
|
}
|
||||||
c.myWindow -= length
|
ch.myWindow -= length
|
||||||
c.windowMu.Unlock()
|
ch.windowMu.Unlock()
|
||||||
|
|
||||||
if extended == 1 {
|
if extended == 1 {
|
||||||
c.extPending.write(data)
|
ch.extPending.write(data)
|
||||||
} else if extended > 0 {
|
} else if extended > 0 {
|
||||||
// discard other extended data.
|
// discard other extended data.
|
||||||
} else {
|
} else {
|
||||||
c.pending.write(data)
|
ch.pending.write(data)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -384,31 +384,31 @@ func (c *channel) close() {
|
||||||
// responseMessageReceived is called when a success or failure message is
|
// responseMessageReceived is called when a success or failure message is
|
||||||
// received on a channel to check that such a message is reasonable for the
|
// received on a channel to check that such a message is reasonable for the
|
||||||
// given channel.
|
// given channel.
|
||||||
func (c *channel) responseMessageReceived() error {
|
func (ch *channel) responseMessageReceived() error {
|
||||||
if c.direction == channelInbound {
|
if ch.direction == channelInbound {
|
||||||
return errors.New("ssh: channel response message received on inbound channel")
|
return errors.New("ssh: channel response message received on inbound channel")
|
||||||
}
|
}
|
||||||
if c.decided {
|
if ch.decided {
|
||||||
return errors.New("ssh: duplicate response received for channel")
|
return errors.New("ssh: duplicate response received for channel")
|
||||||
}
|
}
|
||||||
c.decided = true
|
ch.decided = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *channel) handlePacket(packet []byte) error {
|
func (ch *channel) handlePacket(packet []byte) error {
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgChannelData, msgChannelExtendedData:
|
case msgChannelData, msgChannelExtendedData:
|
||||||
return c.handleData(packet)
|
return ch.handleData(packet)
|
||||||
case msgChannelClose:
|
case msgChannelClose:
|
||||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId})
|
||||||
c.mux.chanList.remove(c.localId)
|
ch.mux.chanList.remove(ch.localId)
|
||||||
c.close()
|
ch.close()
|
||||||
return nil
|
return nil
|
||||||
case msgChannelEOF:
|
case msgChannelEOF:
|
||||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
// RFC 4254 is mute on how EOF affects dataExt messages but
|
||||||
// it is logical to signal EOF at the same time.
|
// it is logical to signal EOF at the same time.
|
||||||
c.extPending.eof()
|
ch.extPending.eof()
|
||||||
c.pending.eof()
|
ch.pending.eof()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,24 +419,24 @@ func (c *channel) handlePacket(packet []byte) error {
|
||||||
|
|
||||||
switch msg := decoded.(type) {
|
switch msg := decoded.(type) {
|
||||||
case *channelOpenFailureMsg:
|
case *channelOpenFailureMsg:
|
||||||
if err := c.responseMessageReceived(); err != nil {
|
if err := ch.responseMessageReceived(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.mux.chanList.remove(msg.PeersId)
|
ch.mux.chanList.remove(msg.PeersID)
|
||||||
c.msg <- msg
|
ch.msg <- msg
|
||||||
case *channelOpenConfirmMsg:
|
case *channelOpenConfirmMsg:
|
||||||
if err := c.responseMessageReceived(); err != nil {
|
if err := ch.responseMessageReceived(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||||
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
||||||
}
|
}
|
||||||
c.remoteId = msg.MyId
|
ch.remoteId = msg.MyID
|
||||||
c.maxRemotePayload = msg.MaxPacketSize
|
ch.maxRemotePayload = msg.MaxPacketSize
|
||||||
c.remoteWin.add(msg.MyWindow)
|
ch.remoteWin.add(msg.MyWindow)
|
||||||
c.msg <- msg
|
ch.msg <- msg
|
||||||
case *windowAdjustMsg:
|
case *windowAdjustMsg:
|
||||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
if !ch.remoteWin.add(msg.AdditionalBytes) {
|
||||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
||||||
}
|
}
|
||||||
case *channelRequestMsg:
|
case *channelRequestMsg:
|
||||||
|
@ -444,12 +444,12 @@ func (c *channel) handlePacket(packet []byte) error {
|
||||||
Type: msg.Request,
|
Type: msg.Request,
|
||||||
WantReply: msg.WantReply,
|
WantReply: msg.WantReply,
|
||||||
Payload: msg.RequestSpecificData,
|
Payload: msg.RequestSpecificData,
|
||||||
ch: c,
|
ch: ch,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.incomingRequests <- &req
|
ch.incomingRequests <- &req
|
||||||
default:
|
default:
|
||||||
c.msg <- msg
|
ch.msg <- msg
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -488,23 +488,23 @@ func (e *extChannel) Read(data []byte) (n int, err error) {
|
||||||
return e.ch.ReadExtended(data, e.code)
|
return e.ch.ReadExtended(data, e.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *channel) Accept() (Channel, <-chan *Request, error) {
|
func (ch *channel) Accept() (Channel, <-chan *Request, error) {
|
||||||
if c.decided {
|
if ch.decided {
|
||||||
return nil, nil, errDecidedAlready
|
return nil, nil, errDecidedAlready
|
||||||
}
|
}
|
||||||
c.maxIncomingPayload = channelMaxPacket
|
ch.maxIncomingPayload = channelMaxPacket
|
||||||
confirm := channelOpenConfirmMsg{
|
confirm := channelOpenConfirmMsg{
|
||||||
PeersId: c.remoteId,
|
PeersID: ch.remoteId,
|
||||||
MyId: c.localId,
|
MyID: ch.localId,
|
||||||
MyWindow: c.myWindow,
|
MyWindow: ch.myWindow,
|
||||||
MaxPacketSize: c.maxIncomingPayload,
|
MaxPacketSize: ch.maxIncomingPayload,
|
||||||
}
|
}
|
||||||
c.decided = true
|
ch.decided = true
|
||||||
if err := c.sendMessage(confirm); err != nil {
|
if err := ch.sendMessage(confirm); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, c.incomingRequests, nil
|
return ch, ch.incomingRequests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||||
|
@ -512,7 +512,7 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||||
return errDecidedAlready
|
return errDecidedAlready
|
||||||
}
|
}
|
||||||
reject := channelOpenFailureMsg{
|
reject := channelOpenFailureMsg{
|
||||||
PeersId: ch.remoteId,
|
PeersID: ch.remoteId,
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
Message: message,
|
Message: message,
|
||||||
Language: "en",
|
Language: "en",
|
||||||
|
@ -541,7 +541,7 @@ func (ch *channel) CloseWrite() error {
|
||||||
}
|
}
|
||||||
ch.sentEOF = true
|
ch.sentEOF = true
|
||||||
return ch.sendMessage(channelEOFMsg{
|
return ch.sendMessage(channelEOFMsg{
|
||||||
PeersId: ch.remoteId})
|
PeersID: ch.remoteId})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *channel) Close() error {
|
func (ch *channel) Close() error {
|
||||||
|
@ -550,7 +550,7 @@ func (ch *channel) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return ch.sendMessage(channelCloseMsg{
|
return ch.sendMessage(channelCloseMsg{
|
||||||
PeersId: ch.remoteId})
|
PeersID: ch.remoteId})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
||||||
|
@ -577,7 +577,7 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := channelRequestMsg{
|
msg := channelRequestMsg{
|
||||||
PeersId: ch.remoteId,
|
PeersID: ch.remoteId,
|
||||||
Request: name,
|
Request: name,
|
||||||
WantReply: wantReply,
|
WantReply: wantReply,
|
||||||
RequestSpecificData: payload,
|
RequestSpecificData: payload,
|
||||||
|
@ -614,11 +614,11 @@ func (ch *channel) ackRequest(ok bool) error {
|
||||||
var msg interface{}
|
var msg interface{}
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = channelRequestFailureMsg{
|
msg = channelRequestFailureMsg{
|
||||||
PeersId: ch.remoteId,
|
PeersID: ch.remoteId,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg = channelRequestSuccessMsg{
|
msg = channelRequestSuccessMsg{
|
||||||
PeersId: ch.remoteId,
|
PeersID: ch.remoteId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ch.sendMessage(msg)
|
return ch.sendMessage(msg)
|
||||||
|
|
|
@ -16,6 +16,9 @@ import (
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/internal/chacha20"
|
||||||
|
"golang.org/x/crypto/poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -53,78 +56,78 @@ func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||||
return rc4.NewCipher(key)
|
return rc4.NewCipher(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamCipherMode struct {
|
type cipherMode struct {
|
||||||
keySize int
|
keySize int
|
||||||
ivSize int
|
ivSize int
|
||||||
skip int
|
create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error)
|
||||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
if len(key) < c.keySize {
|
return func(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
panic("ssh: key length too small for cipher")
|
stream, err := createFunc(key, iv)
|
||||||
}
|
if err != nil {
|
||||||
if len(iv) < c.ivSize {
|
return nil, err
|
||||||
panic("ssh: iv too small for cipher")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var streamDump []byte
|
|
||||||
if c.skip > 0 {
|
|
||||||
streamDump = make([]byte, 512)
|
|
||||||
}
|
|
||||||
|
|
||||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
|
||||||
dumpThisTime := remainingToDump
|
|
||||||
if dumpThisTime > len(streamDump) {
|
|
||||||
dumpThisTime = len(streamDump)
|
|
||||||
}
|
}
|
||||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
|
||||||
remainingToDump -= dumpThisTime
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream, nil
|
var streamDump []byte
|
||||||
|
if skip > 0 {
|
||||||
|
streamDump = make([]byte, 512)
|
||||||
|
}
|
||||||
|
|
||||||
|
for remainingToDump := skip; remainingToDump > 0; {
|
||||||
|
dumpThisTime := remainingToDump
|
||||||
|
if dumpThisTime > len(streamDump) {
|
||||||
|
dumpThisTime = len(streamDump)
|
||||||
|
}
|
||||||
|
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
||||||
|
remainingToDump -= dumpThisTime
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := macModes[algs.MAC].new(macKey)
|
||||||
|
return &streamPacketCipher{
|
||||||
|
mac: mac,
|
||||||
|
etm: macModes[algs.MAC].etm,
|
||||||
|
macResult: make([]byte, mac.Size()),
|
||||||
|
cipher: stream,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||||
// are not supported and will not be negotiated, even if explicitly requested in
|
// are not supported and will not be negotiated, even if explicitly requested in
|
||||||
// ClientConfig.Crypto.Ciphers.
|
// ClientConfig.Crypto.Ciphers.
|
||||||
var cipherModes = map[string]*streamCipherMode{
|
var cipherModes = map[string]*cipherMode{
|
||||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||||
// are defined in the order specified in the RFC.
|
// are defined in the order specified in the RFC.
|
||||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
|
|
||||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||||
// They are defined in the order specified in the RFC.
|
// They are defined in the order specified in the RFC.
|
||||||
"arcfour128": {16, 0, 1536, newRC4},
|
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
|
||||||
"arcfour256": {32, 0, 1536, newRC4},
|
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
|
||||||
|
|
||||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||||
// RC4) has problems with weak keys, and should be used with caution."
|
// RC4) has problems with weak keys, and should be used with caution."
|
||||||
// RFC4345 introduces improved versions of Arcfour.
|
// RFC4345 introduces improved versions of Arcfour.
|
||||||
"arcfour": {16, 0, 0, newRC4},
|
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
|
||||||
|
|
||||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
// AEAD ciphers
|
||||||
// special case. If we add any more non-stream ciphers, we
|
gcmCipherID: {16, 12, newGCMCipher},
|
||||||
// should invest a cleaner way to do this.
|
chacha20Poly1305ID: {64, 0, newChaCha20Cipher},
|
||||||
gcmCipherID: {16, 12, 0, nil},
|
|
||||||
|
|
||||||
// CBC mode is insecure and so is not included in the default config.
|
// CBC mode is insecure and so is not included in the default config.
|
||||||
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
||||||
// needed, it's possible to specify a custom Config to enable it.
|
// needed, it's possible to specify a custom Config to enable it.
|
||||||
// You should expect that an active attacker can recover plaintext if
|
// You should expect that an active attacker can recover plaintext if
|
||||||
// you do.
|
// you do.
|
||||||
aes128cbcID: {16, aes.BlockSize, 0, nil},
|
aes128cbcID: {16, aes.BlockSize, newAESCBCCipher},
|
||||||
|
|
||||||
// 3des-cbc is insecure and is disabled by default.
|
// 3des-cbc is insecure and is not included in the default
|
||||||
tripledescbcID: {24, des.BlockSize, 0, nil},
|
// config.
|
||||||
|
tripledescbcID: {24, des.BlockSize, newTripleDESCBCCipher},
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefixLen is the length of the packet prefix that contains the packet length
|
// prefixLen is the length of the packet prefix that contains the packet length
|
||||||
|
@ -304,7 +307,7 @@ type gcmCipher struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGCMCipher(iv, key []byte) (packetCipher, error) {
|
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -372,7 +375,7 @@ func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||||
}
|
}
|
||||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
length := binary.BigEndian.Uint32(c.prefix[:])
|
||||||
if length > maxPacket {
|
if length > maxPacket {
|
||||||
return nil, errors.New("ssh: max packet length exceeded.")
|
return nil, errors.New("ssh: max packet length exceeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap(c.buf) < int(length+gcmTagSize) {
|
if cap(c.buf) < int(length+gcmTagSize) {
|
||||||
|
@ -422,7 +425,7 @@ type cbcCipher struct {
|
||||||
oracleCamouflage uint32
|
oracleCamouflage uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
cbc := &cbcCipher{
|
cbc := &cbcCipher{
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
mac: macModes[algs.MAC].new(macKey),
|
||||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
decrypter: cipher.NewCBCDecrypter(c, iv),
|
||||||
|
@ -436,13 +439,13 @@ func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorith
|
||||||
return cbc, nil
|
return cbc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -450,13 +453,13 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi
|
||||||
return cbc, nil
|
return cbc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := des.NewTripleDESCipher(key)
|
c, err := des.NewTripleDESCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -548,11 +551,11 @@ func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error)
|
||||||
c.packetData = c.packetData[:entirePacketSize]
|
c.packetData = c.packetData[:entirePacketSize]
|
||||||
}
|
}
|
||||||
|
|
||||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
n, err := io.ReadFull(r, c.packetData[firstBlockLength:])
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
c.oracleCamouflage -= uint32(n)
|
|
||||||
}
|
}
|
||||||
|
c.oracleCamouflage -= uint32(n)
|
||||||
|
|
||||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
||||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
||||||
|
@ -627,3 +630,142 @@ func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, pack
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
|
||||||
|
|
||||||
|
// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com
|
||||||
|
// AEAD, which is described here:
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
|
||||||
|
//
|
||||||
|
// the methods here also implement padding, which RFC4253 Section 6
|
||||||
|
// also requires of stream ciphers.
|
||||||
|
type chacha20Poly1305Cipher struct {
|
||||||
|
lengthKey [32]byte
|
||||||
|
contentKey [32]byte
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
|
||||||
|
if len(key) != 64 {
|
||||||
|
panic(len(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &chacha20Poly1305Cipher{
|
||||||
|
buf: make([]byte, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(c.contentKey[:], key[:32])
|
||||||
|
copy(c.lengthKey[:], key[32:])
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Poly1305 key is obtained by encrypting 32 0-bytes.
|
||||||
|
var chacha20PolyKeyInput [32]byte
|
||||||
|
|
||||||
|
func (c *chacha20Poly1305Cipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||||
|
var counter [16]byte
|
||||||
|
binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
|
||||||
|
|
||||||
|
var polyKey [32]byte
|
||||||
|
chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
encryptedLength := c.buf[:4]
|
||||||
|
if _, err := io.ReadFull(r, encryptedLength); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lenBytes [4]byte
|
||||||
|
chacha20.XORKeyStream(lenBytes[:], encryptedLength, &counter, &c.lengthKey)
|
||||||
|
|
||||||
|
length := binary.BigEndian.Uint32(lenBytes[:])
|
||||||
|
if length > maxPacket {
|
||||||
|
return nil, errors.New("ssh: invalid packet length, packet too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentEnd := 4 + length
|
||||||
|
packetEnd := contentEnd + poly1305.TagSize
|
||||||
|
if uint32(cap(c.buf)) < packetEnd {
|
||||||
|
c.buf = make([]byte, packetEnd)
|
||||||
|
copy(c.buf[:], encryptedLength)
|
||||||
|
} else {
|
||||||
|
c.buf = c.buf[:packetEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(r, c.buf[4:packetEnd]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mac [poly1305.TagSize]byte
|
||||||
|
copy(mac[:], c.buf[contentEnd:packetEnd])
|
||||||
|
if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) {
|
||||||
|
return nil, errors.New("ssh: MAC failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
counter[0] = 1
|
||||||
|
|
||||||
|
plain := c.buf[4:contentEnd]
|
||||||
|
chacha20.XORKeyStream(plain, plain, &counter, &c.contentKey)
|
||||||
|
|
||||||
|
padding := plain[0]
|
||||||
|
if padding < 4 {
|
||||||
|
// padding is a byte, so it automatically satisfies
|
||||||
|
// the maximum size, which is 255.
|
||||||
|
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(padding)+1 >= len(plain) {
|
||||||
|
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
plain = plain[1 : len(plain)-int(padding)]
|
||||||
|
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chacha20Poly1305Cipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error {
|
||||||
|
var counter [16]byte
|
||||||
|
binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
|
||||||
|
|
||||||
|
var polyKey [32]byte
|
||||||
|
chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
// There is no blocksize, so fall back to multiple of 8 byte
|
||||||
|
// padding, as described in RFC 4253, Sec 6.
|
||||||
|
const packetSizeMultiple = 8
|
||||||
|
|
||||||
|
padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple
|
||||||
|
if padding < 4 {
|
||||||
|
padding += packetSizeMultiple
|
||||||
|
}
|
||||||
|
|
||||||
|
// size (4 bytes), padding (1), payload, padding, tag.
|
||||||
|
totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize
|
||||||
|
if cap(c.buf) < totalLength {
|
||||||
|
c.buf = make([]byte, totalLength)
|
||||||
|
} else {
|
||||||
|
c.buf = c.buf[:totalLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding))
|
||||||
|
chacha20.XORKeyStream(c.buf, c.buf[:4], &counter, &c.lengthKey)
|
||||||
|
c.buf[4] = byte(padding)
|
||||||
|
copy(c.buf[5:], payload)
|
||||||
|
packetEnd := 5 + len(payload) + padding
|
||||||
|
if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
counter[0] = 1
|
||||||
|
chacha20.XORKeyStream(c.buf[4:], c.buf[4:packetEnd], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
var mac [poly1305.TagSize]byte
|
||||||
|
poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey)
|
||||||
|
|
||||||
|
copy(c.buf[packetEnd:], mac[:])
|
||||||
|
|
||||||
|
if _, err := w.Write(c.buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type authResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
authFailure authResult = iota
|
||||||
|
authPartialSuccess
|
||||||
|
authSuccess
|
||||||
|
)
|
||||||
|
|
||||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||||
// initiate user auth session
|
// initiate user auth session
|
||||||
|
@ -37,11 +45,12 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ok {
|
if ok == authSuccess {
|
||||||
// success
|
// success
|
||||||
return nil
|
return nil
|
||||||
|
} else if ok == authFailure {
|
||||||
|
tried[auth.method()] = true
|
||||||
}
|
}
|
||||||
tried[auth.method()] = true
|
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = lastMethods
|
methods = lastMethods
|
||||||
}
|
}
|
||||||
|
@ -82,7 +91,7 @@ type AuthMethod interface {
|
||||||
// If authentication is not successful, a []string of alternative
|
// If authentication is not successful, a []string of alternative
|
||||||
// method names is returned. If the slice is nil, it will be ignored
|
// method names is returned. If the slice is nil, it will be ignored
|
||||||
// and the previous set of possible methods will be reused.
|
// and the previous set of possible methods will be reused.
|
||||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
|
||||||
|
|
||||||
// method returns the RFC 4252 method name.
|
// method returns the RFC 4252 method name.
|
||||||
method() string
|
method() string
|
||||||
|
@ -91,13 +100,13 @@ type AuthMethod interface {
|
||||||
// "none" authentication, RFC 4252 section 5.2.
|
// "none" authentication, RFC 4252 section 5.2.
|
||||||
type noneAuth int
|
type noneAuth int
|
||||||
|
|
||||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
||||||
User: user,
|
User: user,
|
||||||
Service: serviceSSH,
|
Service: serviceSSH,
|
||||||
Method: "none",
|
Method: "none",
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
return handleAuthResponse(c)
|
||||||
|
@ -111,7 +120,7 @@ func (n *noneAuth) method() string {
|
||||||
// a function call, e.g. by prompting the user.
|
// a function call, e.g. by prompting the user.
|
||||||
type passwordCallback func() (password string, err error)
|
type passwordCallback func() (password string, err error)
|
||||||
|
|
||||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
type passwordAuthMsg struct {
|
type passwordAuthMsg struct {
|
||||||
User string `sshtype:"50"`
|
User string `sshtype:"50"`
|
||||||
Service string
|
Service string
|
||||||
|
@ -125,7 +134,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
// The program may only find out that the user doesn't have a password
|
// The program may only find out that the user doesn't have a password
|
||||||
// when prompting.
|
// when prompting.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
||||||
|
@ -135,7 +144,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
Reply: false,
|
Reply: false,
|
||||||
Password: pw,
|
Password: pw,
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
return handleAuthResponse(c)
|
||||||
|
@ -178,7 +187,7 @@ func (cb publicKeyCallback) method() string {
|
||||||
return "publickey"
|
return "publickey"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
// Authentication is performed by sending an enquiry to test if a key is
|
// Authentication is performed by sending an enquiry to test if a key is
|
||||||
// acceptable to the remote. If the key is acceptable, the client will
|
// acceptable to the remote. If the key is acceptable, the client will
|
||||||
// attempt to authenticate with the valid key. If not the client will repeat
|
// attempt to authenticate with the valid key. If not the client will repeat
|
||||||
|
@ -186,13 +195,13 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
|
|
||||||
signers, err := cb()
|
signers, err := cb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
var methods []string
|
var methods []string
|
||||||
for _, signer := range signers {
|
for _, signer := range signers {
|
||||||
ok, err := validateKey(signer.PublicKey(), user, c)
|
ok, err := validateKey(signer.PublicKey(), user, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
@ -206,7 +215,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
Method: cb.method(),
|
Method: cb.method(),
|
||||||
}, []byte(pub.Type()), pubKey))
|
}, []byte(pub.Type()), pubKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually wrap the serialized signature in a string
|
// manually wrap the serialized signature in a string
|
||||||
|
@ -224,24 +233,24 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
}
|
}
|
||||||
p := Marshal(&msg)
|
p := Marshal(&msg)
|
||||||
if err := c.writePacket(p); err != nil {
|
if err := c.writePacket(p); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
var success bool
|
var success authResult
|
||||||
success, methods, err = handleAuthResponse(c)
|
success, methods, err = handleAuthResponse(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If authentication succeeds or the list of available methods does not
|
// If authentication succeeds or the list of available methods does not
|
||||||
// contain the "publickey" method, do not attempt to authenticate with any
|
// contain the "publickey" method, do not attempt to authenticate with any
|
||||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||||
// additional authentication methods are required.
|
// additional authentication methods are required.
|
||||||
if success || !containsMethod(methods, cb.method()) {
|
if success == authSuccess || !containsMethod(methods, cb.method()) {
|
||||||
return success, methods, err
|
return success, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, methods, nil
|
return authFailure, methods, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsMethod(methods []string, method string) bool {
|
func containsMethod(methods []string, method string) bool {
|
||||||
|
@ -318,28 +327,31 @@ func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMet
|
||||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
// handleAuthResponse returns whether the preceding authentication request succeeded
|
||||||
// along with a list of remaining authentication methods to try next and
|
// along with a list of remaining authentication methods to try next and
|
||||||
// an error if an unexpected response was received.
|
// an error if an unexpected response was received.
|
||||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
func handleAuthResponse(c packetConn) (authResult, []string, error) {
|
||||||
for {
|
for {
|
||||||
packet, err := c.readPacket()
|
packet, err := c.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgUserAuthBanner:
|
case msgUserAuthBanner:
|
||||||
if err := handleBannerResponse(c, packet); err != nil {
|
if err := handleBannerResponse(c, packet); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
case msgUserAuthFailure:
|
case msgUserAuthFailure:
|
||||||
var msg userAuthFailureMsg
|
var msg userAuthFailureMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
return false, msg.Methods, nil
|
if msg.PartialSuccess {
|
||||||
|
return authPartialSuccess, msg.Methods, nil
|
||||||
|
}
|
||||||
|
return authFailure, msg.Methods, nil
|
||||||
case msgUserAuthSuccess:
|
case msgUserAuthSuccess:
|
||||||
return true, nil, nil
|
return authSuccess, nil, nil
|
||||||
default:
|
default:
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +393,7 @@ func (cb KeyboardInteractiveChallenge) method() string {
|
||||||
return "keyboard-interactive"
|
return "keyboard-interactive"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
type initiateMsg struct {
|
type initiateMsg struct {
|
||||||
User string `sshtype:"50"`
|
User string `sshtype:"50"`
|
||||||
Service string
|
Service string
|
||||||
|
@ -395,20 +407,20 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
Service: serviceSSH,
|
Service: serviceSSH,
|
||||||
Method: "keyboard-interactive",
|
Method: "keyboard-interactive",
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
packet, err := c.readPacket()
|
packet, err := c.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// like handleAuthResponse, but with less options.
|
// like handleAuthResponse, but with less options.
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgUserAuthBanner:
|
case msgUserAuthBanner:
|
||||||
if err := handleBannerResponse(c, packet); err != nil {
|
if err := handleBannerResponse(c, packet); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case msgUserAuthInfoRequest:
|
case msgUserAuthInfoRequest:
|
||||||
|
@ -416,18 +428,21 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
case msgUserAuthFailure:
|
case msgUserAuthFailure:
|
||||||
var msg userAuthFailureMsg
|
var msg userAuthFailureMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
return false, msg.Methods, nil
|
if msg.PartialSuccess {
|
||||||
|
return authPartialSuccess, msg.Methods, nil
|
||||||
|
}
|
||||||
|
return authFailure, msg.Methods, nil
|
||||||
case msgUserAuthSuccess:
|
case msgUserAuthSuccess:
|
||||||
return true, nil, nil
|
return authSuccess, nil, nil
|
||||||
default:
|
default:
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg userAuthInfoRequestMsg
|
var msg userAuthInfoRequestMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually unpack the prompt/echo pairs.
|
// Manually unpack the prompt/echo pairs.
|
||||||
|
@ -437,7 +452,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||||
prompt, r, ok := parseString(rest)
|
prompt, r, ok := parseString(rest)
|
||||||
if !ok || len(r) == 0 {
|
if !ok || len(r) == 0 {
|
||||||
return false, nil, errors.New("ssh: prompt format error")
|
return authFailure, nil, errors.New("ssh: prompt format error")
|
||||||
}
|
}
|
||||||
prompts = append(prompts, string(prompt))
|
prompts = append(prompts, string(prompt))
|
||||||
echos = append(echos, r[0] != 0)
|
echos = append(echos, r[0] != 0)
|
||||||
|
@ -445,16 +460,16 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rest) != 0 {
|
if len(rest) != 0 {
|
||||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
||||||
}
|
}
|
||||||
|
|
||||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answers) != len(prompts) {
|
if len(answers) != len(prompts) {
|
||||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||||
}
|
}
|
||||||
responseLength := 1 + 4
|
responseLength := 1 + 4
|
||||||
for _, a := range answers {
|
for _, a := range answers {
|
||||||
|
@ -470,7 +485,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
if err := c.writePacket(serialized); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,10 +495,10 @@ type retryableAuthMethod struct {
|
||||||
maxTries int
|
maxTries int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
|
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
|
||||||
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
||||||
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
||||||
if ok || err != nil { // either success or error terminate
|
if ok != authFailure || err != nil { // either success, partial success or error terminate
|
||||||
return ok, methods, err
|
return ok, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,21 @@ const (
|
||||||
serviceSSH = "ssh-connection"
|
serviceSSH = "ssh-connection"
|
||||||
)
|
)
|
||||||
|
|
||||||
// supportedCiphers specifies the supported ciphers in preference order.
|
// supportedCiphers lists ciphers we support but might not recommend.
|
||||||
var supportedCiphers = []string{
|
var supportedCiphers = []string{
|
||||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||||
"aes128-gcm@openssh.com",
|
"aes128-gcm@openssh.com",
|
||||||
"arcfour256", "arcfour128",
|
chacha20Poly1305ID,
|
||||||
|
"arcfour256", "arcfour128", "arcfour",
|
||||||
|
aes128cbcID,
|
||||||
|
tripledescbcID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// preferredCiphers specifies the default preference for ciphers.
|
||||||
|
var preferredCiphers = []string{
|
||||||
|
"aes128-gcm@openssh.com",
|
||||||
|
chacha20Poly1305ID,
|
||||||
|
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||||
|
@ -211,7 +221,7 @@ func (c *Config) SetDefaults() {
|
||||||
c.Rand = rand.Reader
|
c.Rand = rand.Reader
|
||||||
}
|
}
|
||||||
if c.Ciphers == nil {
|
if c.Ciphers == nil {
|
||||||
c.Ciphers = supportedCiphers
|
c.Ciphers = preferredCiphers
|
||||||
}
|
}
|
||||||
var ciphers []string
|
var ciphers []string
|
||||||
for _, c := range c.Ciphers {
|
for _, c := range c.Ciphers {
|
||||||
|
@ -242,7 +252,7 @@ func (c *Config) SetDefaults() {
|
||||||
|
|
||||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||||
// possession of a private key. See RFC 4252, section 7.
|
// possession of a private key. See RFC 4252, section 7.
|
||||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||||
data := struct {
|
data := struct {
|
||||||
Session []byte
|
Session []byte
|
||||||
Type byte
|
Type byte
|
||||||
|
@ -253,7 +263,7 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK
|
||||||
Algo []byte
|
Algo []byte
|
||||||
PubKey []byte
|
PubKey []byte
|
||||||
}{
|
}{
|
||||||
sessionId,
|
sessionID,
|
||||||
msgUserAuthRequest,
|
msgUserAuthRequest,
|
||||||
req.User,
|
req.User,
|
||||||
req.Service,
|
req.Service,
|
||||||
|
|
|
@ -119,7 +119,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
ki, err := group.diffieHellman(kexDHReply.Y, x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -129,8 +129,8 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
|
||||||
writeString(h, kexDHReply.HostKey)
|
writeString(h, kexDHReply.HostKey)
|
||||||
writeInt(h, X)
|
writeInt(h, X)
|
||||||
writeInt(h, kexDHReply.Y)
|
writeInt(h, kexDHReply.Y)
|
||||||
K := make([]byte, intLength(kInt))
|
K := make([]byte, intLength(ki))
|
||||||
marshalInt(K, kInt)
|
marshalInt(K, ki)
|
||||||
h.Write(K)
|
h.Write(K)
|
||||||
|
|
||||||
return &kexResult{
|
return &kexResult{
|
||||||
|
@ -164,7 +164,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
|
||||||
}
|
}
|
||||||
|
|
||||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
Y := new(big.Int).Exp(group.g, y, group.p)
|
||||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
ki, err := group.diffieHellman(kexDHInit.X, y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -177,8 +177,8 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
|
||||||
writeInt(h, kexDHInit.X)
|
writeInt(h, kexDHInit.X)
|
||||||
writeInt(h, Y)
|
writeInt(h, Y)
|
||||||
|
|
||||||
K := make([]byte, intLength(kInt))
|
K := make([]byte, intLength(ki))
|
||||||
marshalInt(K, kInt)
|
marshalInt(K, ki)
|
||||||
h.Write(K)
|
h.Write(K)
|
||||||
|
|
||||||
H := h.Sum(nil)
|
H := h.Sum(nil)
|
||||||
|
@ -462,9 +462,9 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
|
||||||
writeString(h, kp.pub[:])
|
writeString(h, kp.pub[:])
|
||||||
writeString(h, reply.EphemeralPubKey)
|
writeString(h, reply.EphemeralPubKey)
|
||||||
|
|
||||||
kInt := new(big.Int).SetBytes(secret[:])
|
ki := new(big.Int).SetBytes(secret[:])
|
||||||
K := make([]byte, intLength(kInt))
|
K := make([]byte, intLength(ki))
|
||||||
marshalInt(K, kInt)
|
marshalInt(K, ki)
|
||||||
h.Write(K)
|
h.Write(K)
|
||||||
|
|
||||||
return &kexResult{
|
return &kexResult{
|
||||||
|
@ -510,9 +510,9 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
|
||||||
writeString(h, kexInit.ClientPubKey)
|
writeString(h, kexInit.ClientPubKey)
|
||||||
writeString(h, kp.pub[:])
|
writeString(h, kp.pub[:])
|
||||||
|
|
||||||
kInt := new(big.Int).SetBytes(secret[:])
|
ki := new(big.Int).SetBytes(secret[:])
|
||||||
K := make([]byte, intLength(kInt))
|
K := make([]byte, intLength(ki))
|
||||||
marshalInt(K, kInt)
|
marshalInt(K, ki)
|
||||||
h.Write(K)
|
h.Write(K)
|
||||||
|
|
||||||
H := h.Sum(nil)
|
H := h.Sum(nil)
|
||||||
|
|
|
@ -276,7 +276,8 @@ type PublicKey interface {
|
||||||
Type() string
|
Type() string
|
||||||
|
|
||||||
// Marshal returns the serialized key data in SSH wire format,
|
// Marshal returns the serialized key data in SSH wire format,
|
||||||
// with the name prefix.
|
// with the name prefix. To unmarshal the returned data, use
|
||||||
|
// the ParsePublicKey function.
|
||||||
Marshal() []byte
|
Marshal() []byte
|
||||||
|
|
||||||
// Verify that sig is a signature on the given data using this
|
// Verify that sig is a signature on the given data using this
|
||||||
|
@ -363,7 +364,7 @@ func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
|
||||||
type dsaPublicKey dsa.PublicKey
|
type dsaPublicKey dsa.PublicKey
|
||||||
|
|
||||||
func (r *dsaPublicKey) Type() string {
|
func (k *dsaPublicKey) Type() string {
|
||||||
return "ssh-dss"
|
return "ssh-dss"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,12 +482,12 @@ func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||||
|
|
||||||
type ecdsaPublicKey ecdsa.PublicKey
|
type ecdsaPublicKey ecdsa.PublicKey
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Type() string {
|
func (k *ecdsaPublicKey) Type() string {
|
||||||
return "ecdsa-sha2-" + key.nistID()
|
return "ecdsa-sha2-" + k.nistID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) nistID() string {
|
func (k *ecdsaPublicKey) nistID() string {
|
||||||
switch key.Params().BitSize {
|
switch k.Params().BitSize {
|
||||||
case 256:
|
case 256:
|
||||||
return "nistp256"
|
return "nistp256"
|
||||||
case 384:
|
case 384:
|
||||||
|
@ -499,7 +500,7 @@ func (key *ecdsaPublicKey) nistID() string {
|
||||||
|
|
||||||
type ed25519PublicKey ed25519.PublicKey
|
type ed25519PublicKey ed25519.PublicKey
|
||||||
|
|
||||||
func (key ed25519PublicKey) Type() string {
|
func (k ed25519PublicKey) Type() string {
|
||||||
return KeyAlgoED25519
|
return KeyAlgoED25519
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,23 +519,23 @@ func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
return (ed25519PublicKey)(key), w.Rest, nil
|
return (ed25519PublicKey)(key), w.Rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key ed25519PublicKey) Marshal() []byte {
|
func (k ed25519PublicKey) Marshal() []byte {
|
||||||
w := struct {
|
w := struct {
|
||||||
Name string
|
Name string
|
||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
}{
|
}{
|
||||||
KeyAlgoED25519,
|
KeyAlgoED25519,
|
||||||
[]byte(key),
|
[]byte(k),
|
||||||
}
|
}
|
||||||
return Marshal(&w)
|
return Marshal(&w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
||||||
if sig.Format != key.Type() {
|
if sig.Format != k.Type() {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
edKey := (ed25519.PublicKey)(key)
|
edKey := (ed25519.PublicKey)(k)
|
||||||
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
|
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
|
||||||
return errors.New("ssh: signature did not verify")
|
return errors.New("ssh: signature did not verify")
|
||||||
}
|
}
|
||||||
|
@ -595,9 +596,9 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
return (*ecdsaPublicKey)(key), w.Rest, nil
|
return (*ecdsaPublicKey)(key), w.Rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
func (k *ecdsaPublicKey) Marshal() []byte {
|
||||||
// See RFC 5656, section 3.1.
|
// See RFC 5656, section 3.1.
|
||||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y)
|
||||||
// ECDSA publickey struct layout should match the struct used by
|
// ECDSA publickey struct layout should match the struct used by
|
||||||
// parseECDSACert in the x/crypto/ssh/agent package.
|
// parseECDSACert in the x/crypto/ssh/agent package.
|
||||||
w := struct {
|
w := struct {
|
||||||
|
@ -605,20 +606,20 @@ func (key *ecdsaPublicKey) Marshal() []byte {
|
||||||
ID string
|
ID string
|
||||||
Key []byte
|
Key []byte
|
||||||
}{
|
}{
|
||||||
key.Type(),
|
k.Type(),
|
||||||
key.nistID(),
|
k.nistID(),
|
||||||
keyBytes,
|
keyBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Marshal(&w)
|
return Marshal(&w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
if sig.Format != key.Type() {
|
if sig.Format != k.Type() {
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ecHash(key.Curve).New()
|
h := ecHash(k.Curve).New()
|
||||||
h.Write(data)
|
h.Write(data)
|
||||||
digest := h.Sum(nil)
|
digest := h.Sum(nil)
|
||||||
|
|
||||||
|
@ -635,7 +636,7 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
|
if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("ssh: signature did not verify")
|
return errors.New("ssh: signature did not verify")
|
||||||
|
@ -758,7 +759,7 @@ func NewPublicKey(key interface{}) (PublicKey, error) {
|
||||||
return (*rsaPublicKey)(key), nil
|
return (*rsaPublicKey)(key), nil
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
if !supportedEllipticCurve(key.Curve) {
|
if !supportedEllipticCurve(key.Curve) {
|
||||||
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
|
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported")
|
||||||
}
|
}
|
||||||
return (*ecdsaPublicKey)(key), nil
|
return (*ecdsaPublicKey)(key), nil
|
||||||
case *dsa.PublicKey:
|
case *dsa.PublicKey:
|
||||||
|
|
|
@ -162,7 +162,7 @@ const msgChannelOpen = 90
|
||||||
|
|
||||||
type channelOpenMsg struct {
|
type channelOpenMsg struct {
|
||||||
ChanType string `sshtype:"90"`
|
ChanType string `sshtype:"90"`
|
||||||
PeersId uint32
|
PeersID uint32
|
||||||
PeersWindow uint32
|
PeersWindow uint32
|
||||||
MaxPacketSize uint32
|
MaxPacketSize uint32
|
||||||
TypeSpecificData []byte `ssh:"rest"`
|
TypeSpecificData []byte `ssh:"rest"`
|
||||||
|
@ -173,7 +173,7 @@ const msgChannelData = 94
|
||||||
|
|
||||||
// Used for debug print outs of packets.
|
// Used for debug print outs of packets.
|
||||||
type channelDataMsg struct {
|
type channelDataMsg struct {
|
||||||
PeersId uint32 `sshtype:"94"`
|
PeersID uint32 `sshtype:"94"`
|
||||||
Length uint32
|
Length uint32
|
||||||
Rest []byte `ssh:"rest"`
|
Rest []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
@ -182,8 +182,8 @@ type channelDataMsg struct {
|
||||||
const msgChannelOpenConfirm = 91
|
const msgChannelOpenConfirm = 91
|
||||||
|
|
||||||
type channelOpenConfirmMsg struct {
|
type channelOpenConfirmMsg struct {
|
||||||
PeersId uint32 `sshtype:"91"`
|
PeersID uint32 `sshtype:"91"`
|
||||||
MyId uint32
|
MyID uint32
|
||||||
MyWindow uint32
|
MyWindow uint32
|
||||||
MaxPacketSize uint32
|
MaxPacketSize uint32
|
||||||
TypeSpecificData []byte `ssh:"rest"`
|
TypeSpecificData []byte `ssh:"rest"`
|
||||||
|
@ -193,7 +193,7 @@ type channelOpenConfirmMsg struct {
|
||||||
const msgChannelOpenFailure = 92
|
const msgChannelOpenFailure = 92
|
||||||
|
|
||||||
type channelOpenFailureMsg struct {
|
type channelOpenFailureMsg struct {
|
||||||
PeersId uint32 `sshtype:"92"`
|
PeersID uint32 `sshtype:"92"`
|
||||||
Reason RejectionReason
|
Reason RejectionReason
|
||||||
Message string
|
Message string
|
||||||
Language string
|
Language string
|
||||||
|
@ -202,7 +202,7 @@ type channelOpenFailureMsg struct {
|
||||||
const msgChannelRequest = 98
|
const msgChannelRequest = 98
|
||||||
|
|
||||||
type channelRequestMsg struct {
|
type channelRequestMsg struct {
|
||||||
PeersId uint32 `sshtype:"98"`
|
PeersID uint32 `sshtype:"98"`
|
||||||
Request string
|
Request string
|
||||||
WantReply bool
|
WantReply bool
|
||||||
RequestSpecificData []byte `ssh:"rest"`
|
RequestSpecificData []byte `ssh:"rest"`
|
||||||
|
@ -212,28 +212,28 @@ type channelRequestMsg struct {
|
||||||
const msgChannelSuccess = 99
|
const msgChannelSuccess = 99
|
||||||
|
|
||||||
type channelRequestSuccessMsg struct {
|
type channelRequestSuccessMsg struct {
|
||||||
PeersId uint32 `sshtype:"99"`
|
PeersID uint32 `sshtype:"99"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4254, section 5.4.
|
// See RFC 4254, section 5.4.
|
||||||
const msgChannelFailure = 100
|
const msgChannelFailure = 100
|
||||||
|
|
||||||
type channelRequestFailureMsg struct {
|
type channelRequestFailureMsg struct {
|
||||||
PeersId uint32 `sshtype:"100"`
|
PeersID uint32 `sshtype:"100"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4254, section 5.3
|
// See RFC 4254, section 5.3
|
||||||
const msgChannelClose = 97
|
const msgChannelClose = 97
|
||||||
|
|
||||||
type channelCloseMsg struct {
|
type channelCloseMsg struct {
|
||||||
PeersId uint32 `sshtype:"97"`
|
PeersID uint32 `sshtype:"97"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4254, section 5.3
|
// See RFC 4254, section 5.3
|
||||||
const msgChannelEOF = 96
|
const msgChannelEOF = 96
|
||||||
|
|
||||||
type channelEOFMsg struct {
|
type channelEOFMsg struct {
|
||||||
PeersId uint32 `sshtype:"96"`
|
PeersID uint32 `sshtype:"96"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4254, section 4
|
// See RFC 4254, section 4
|
||||||
|
@ -263,7 +263,7 @@ type globalRequestFailureMsg struct {
|
||||||
const msgChannelWindowAdjust = 93
|
const msgChannelWindowAdjust = 93
|
||||||
|
|
||||||
type windowAdjustMsg struct {
|
type windowAdjustMsg struct {
|
||||||
PeersId uint32 `sshtype:"93"`
|
PeersID uint32 `sshtype:"93"`
|
||||||
AdditionalBytes uint32
|
AdditionalBytes uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,7 +278,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
|
||||||
|
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||||
failMsg := channelOpenFailureMsg{
|
failMsg := channelOpenFailureMsg{
|
||||||
PeersId: msg.PeersId,
|
PeersID: msg.PeersID,
|
||||||
Reason: ConnectionFailed,
|
Reason: ConnectionFailed,
|
||||||
Message: "invalid request",
|
Message: "invalid request",
|
||||||
Language: "en_US.UTF-8",
|
Language: "en_US.UTF-8",
|
||||||
|
@ -287,7 +287,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
|
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
|
||||||
c.remoteId = msg.PeersId
|
c.remoteId = msg.PeersID
|
||||||
c.maxRemotePayload = msg.MaxPacketSize
|
c.maxRemotePayload = msg.MaxPacketSize
|
||||||
c.remoteWin.add(msg.PeersWindow)
|
c.remoteWin.add(msg.PeersWindow)
|
||||||
m.incomingChannels <- c
|
m.incomingChannels <- c
|
||||||
|
@ -313,7 +313,7 @@ func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
||||||
PeersWindow: ch.myWindow,
|
PeersWindow: ch.myWindow,
|
||||||
MaxPacketSize: ch.maxIncomingPayload,
|
MaxPacketSize: ch.maxIncomingPayload,
|
||||||
TypeSpecificData: extra,
|
TypeSpecificData: extra,
|
||||||
PeersId: ch.localId,
|
PeersID: ch.localId,
|
||||||
}
|
}
|
||||||
if err := m.sendMessage(open); err != nil {
|
if err := m.sendMessage(open); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -166,6 +166,9 @@ type ServerConn struct {
|
||||||
// unsuccessful, it closes the connection and returns an error. The
|
// unsuccessful, it closes the connection and returns an error. The
|
||||||
// Request and NewChannel channels must be serviced, or the connection
|
// Request and NewChannel channels must be serviced, or the connection
|
||||||
// will hang.
|
// will hang.
|
||||||
|
//
|
||||||
|
// The returned error may be of type *ServerAuthError for
|
||||||
|
// authentication errors.
|
||||||
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
|
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
|
||||||
fullConf := *config
|
fullConf := *config
|
||||||
fullConf.SetDefaults()
|
fullConf.SetDefaults()
|
||||||
|
@ -256,7 +259,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
||||||
func isAcceptableAlgo(algo string) bool {
|
func isAcceptableAlgo(algo string) bool {
|
||||||
switch algo {
|
switch algo {
|
||||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
|
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -292,12 +295,13 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
||||||
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerAuthError implements the error interface. It appends any authentication
|
// ServerAuthError represents server authentication errors and is
|
||||||
// errors that may occur, and is returned if all of the authentication methods
|
// sometimes returned by NewServerConn. It appends any authentication
|
||||||
// provided by the user failed to authenticate.
|
// errors that may occur, and is returned if all of the authentication
|
||||||
|
// methods provided by the user failed to authenticate.
|
||||||
type ServerAuthError struct {
|
type ServerAuthError struct {
|
||||||
// Errors contains authentication errors returned by the authentication
|
// Errors contains authentication errors returned by the authentication
|
||||||
// callback methods.
|
// callback methods. The first entry is typically ErrNoAuth.
|
||||||
Errors []error
|
Errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,6 +313,13 @@ func (l ServerAuthError) Error() string {
|
||||||
return "[" + strings.Join(errs, ", ") + "]"
|
return "[" + strings.Join(errs, ", ") + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrNoAuth is the error value returned if no
|
||||||
|
// authentication method has been passed yet. This happens as a normal
|
||||||
|
// part of the authentication loop, since the client first tries
|
||||||
|
// 'none' authentication to discover available methods.
|
||||||
|
// It is returned in ServerAuthError.Errors from NewServerConn.
|
||||||
|
var ErrNoAuth = errors.New("ssh: no auth passed yet")
|
||||||
|
|
||||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||||
sessionID := s.transport.getSessionID()
|
sessionID := s.transport.getSessionID()
|
||||||
var cache pubKeyCache
|
var cache pubKeyCache
|
||||||
|
@ -316,6 +327,7 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
|
||||||
|
|
||||||
authFailures := 0
|
authFailures := 0
|
||||||
var authErrs []error
|
var authErrs []error
|
||||||
|
var displayedBanner bool
|
||||||
|
|
||||||
userAuthLoop:
|
userAuthLoop:
|
||||||
for {
|
for {
|
||||||
|
@ -348,7 +360,8 @@ userAuthLoop:
|
||||||
|
|
||||||
s.user = userAuthReq.User
|
s.user = userAuthReq.User
|
||||||
|
|
||||||
if authFailures == 0 && config.BannerCallback != nil {
|
if !displayedBanner && config.BannerCallback != nil {
|
||||||
|
displayedBanner = true
|
||||||
msg := config.BannerCallback(s)
|
msg := config.BannerCallback(s)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
bannerMsg := &userAuthBannerMsg{
|
bannerMsg := &userAuthBannerMsg{
|
||||||
|
@ -361,7 +374,7 @@ userAuthLoop:
|
||||||
}
|
}
|
||||||
|
|
||||||
perms = nil
|
perms = nil
|
||||||
authErr := errors.New("no auth passed yet")
|
authErr := ErrNoAuth
|
||||||
|
|
||||||
switch userAuthReq.Method {
|
switch userAuthReq.Method {
|
||||||
case "none":
|
case "none":
|
||||||
|
|
|
@ -406,7 +406,7 @@ func (s *Session) Wait() error {
|
||||||
s.stdinPipeWriter.Close()
|
s.stdinPipeWriter.Close()
|
||||||
}
|
}
|
||||||
var copyError error
|
var copyError error
|
||||||
for _ = range s.copyFuncs {
|
for range s.copyFuncs {
|
||||||
if err := <-s.errors; err != nil && copyError == nil {
|
if err := <-s.errors; err != nil && copyError == nil {
|
||||||
copyError = err
|
copyError = err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
@ -76,17 +77,17 @@ type connectionState struct {
|
||||||
// both directions are triggered by reading and writing a msgNewKey packet
|
// both directions are triggered by reading and writing a msgNewKey packet
|
||||||
// respectively.
|
// respectively.
|
||||||
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
||||||
if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
|
ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
t.reader.pendingKeyChange <- ciph
|
|
||||||
}
|
}
|
||||||
|
t.reader.pendingKeyChange <- ciph
|
||||||
|
|
||||||
if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
|
ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
t.writer.pendingKeyChange <- ciph
|
|
||||||
}
|
}
|
||||||
|
t.writer.pendingKeyChange <- ciph
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -139,7 +140,7 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
||||||
case cipher := <-s.pendingKeyChange:
|
case cipher := <-s.pendingKeyChange:
|
||||||
s.packetCipher = cipher
|
s.packetCipher = cipher
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("ssh: got bogus newkeys message.")
|
return nil, errors.New("ssh: got bogus newkeys message")
|
||||||
}
|
}
|
||||||
|
|
||||||
case msgDisconnect:
|
case msgDisconnect:
|
||||||
|
@ -232,52 +233,22 @@ var (
|
||||||
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateKeys generates key material for IV, MAC and encryption.
|
|
||||||
func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
|
|
||||||
cipherMode := cipherModes[algs.Cipher]
|
|
||||||
macMode := macModes[algs.MAC]
|
|
||||||
|
|
||||||
iv = make([]byte, cipherMode.ivSize)
|
|
||||||
key = make([]byte, cipherMode.keySize)
|
|
||||||
macKey = make([]byte, macMode.keySize)
|
|
||||||
|
|
||||||
generateKeyMaterial(iv, d.ivTag, kex)
|
|
||||||
generateKeyMaterial(key, d.keyTag, kex)
|
|
||||||
generateKeyMaterial(macKey, d.macKeyTag, kex)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
||||||
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
||||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||||
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
|
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
|
||||||
iv, key, macKey := generateKeys(d, algs, kex)
|
cipherMode := cipherModes[algs.Cipher]
|
||||||
|
macMode := macModes[algs.MAC]
|
||||||
|
|
||||||
if algs.Cipher == gcmCipherID {
|
iv := make([]byte, cipherMode.ivSize)
|
||||||
return newGCMCipher(iv, key)
|
key := make([]byte, cipherMode.keySize)
|
||||||
}
|
macKey := make([]byte, macMode.keySize)
|
||||||
|
|
||||||
if algs.Cipher == aes128cbcID {
|
generateKeyMaterial(iv, d.ivTag, kex)
|
||||||
return newAESCBCCipher(iv, key, macKey, algs)
|
generateKeyMaterial(key, d.keyTag, kex)
|
||||||
}
|
generateKeyMaterial(macKey, d.macKeyTag, kex)
|
||||||
|
|
||||||
if algs.Cipher == tripledescbcID {
|
return cipherModes[algs.Cipher].create(key, iv, macKey, algs)
|
||||||
return newTripleDESCBCCipher(iv, key, macKey, algs)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &streamPacketCipher{
|
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
|
||||||
etm: macModes[algs.MAC].etm,
|
|
||||||
}
|
|
||||||
c.macResult = make([]byte, c.mac.Size())
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateKeyMaterial fills out with key material generated from tag, K, H
|
// generateKeyMaterial fills out with key material generated from tag, K, H
|
||||||
|
@ -342,7 +313,7 @@ func readVersion(r io.Reader) ([]byte, error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var buf [1]byte
|
var buf [1]byte
|
||||||
|
|
||||||
for len(versionString) < maxVersionStringBytes {
|
for length := 0; length < maxVersionStringBytes; length++ {
|
||||||
_, err := io.ReadFull(r, buf[:])
|
_, err := io.ReadFull(r, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -350,6 +321,13 @@ func readVersion(r io.Reader) ([]byte, error) {
|
||||||
// The RFC says that the version should be terminated with \r\n
|
// The RFC says that the version should be terminated with \r\n
|
||||||
// but several SSH servers actually only send a \n.
|
// but several SSH servers actually only send a \n.
|
||||||
if buf[0] == '\n' {
|
if buf[0] == '\n' {
|
||||||
|
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
|
||||||
|
// RFC 4253 says we need to ignore all version string lines
|
||||||
|
// except the one containing the SSH version (provided that
|
||||||
|
// all the lines do not exceed 255 bytes in total).
|
||||||
|
versionString = versionString[:0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue