This commit is contained in:
regnaio 2022-05-14 17:49:22 -04:00
commit a49eb64c27
54 changed files with 1331 additions and 3828 deletions

View File

@ -19,8 +19,8 @@ eval "$(tmp/homebrew/bin/brew shellenv)"
brew update --force --quiet
chmod -R go-w "$(brew --prefix)/share/zsh"
git config user.name "cloudflare-warp-bot"
git config user.email "warp-bot@cloudflare.com"
git config --global user.name "cloudflare-warp-bot"
git config --global user.email "warp-bot@cloudflare.com"
# bump formula pr
brew bump-formula-pr cloudflared --version="$VERSION"
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse

View File

@ -261,6 +261,10 @@ github-release: cloudflared
github-release-built-pkgs:
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
.PHONY: release-pkgs-linux
release-pkgs-linux:
python3 ./release_pkgs.py
.PHONY: github-message
github-message:
python3 github_message.py --release-version $(VERSION)

View File

@ -1,3 +1,25 @@
2022.5.1
- 2022-05-06 TUN-6146: Release_pkgs is now a generic command line script
- 2022-05-06 TUN-6185: Fix tcpOverWSOriginService not using original scheme for String representation
- 2022-05-05 TUN-6175: Simply debian packaging by structural upload
- 2022-05-05 TUN-5945: Added support for Ubuntu releases
- 2022-05-04 TUN-6054: Create and upload deb packages to R2
- 2022-05-03 TUN-6161: Set git user/email for brew core release
- 2022-05-03 TUN-6166: Fix mocked QUIC transport for UDP proxy manager to return expected error
- 2022-04-27 TUN-6016: Push local managed tunnels configuration to the edge
2022.5.0
- 2022-05-02 TUN-6158: Update golang.org/x/crypto
- 2022-04-20 VULN-8383 Bump yaml.v2 to yaml.v3
- 2022-04-21 TUN-6123: For a given connection with edge, close all datagram sessions through this connection when it's closed
- 2022-04-20 TUN-6015: Add RPC method for pushing local config
- 2022-04-21 TUN-6130: Fix vendoring due to case sensitive typo in package
- 2022-04-27 TUN-6142: Add tunnel details support to RPC
- 2022-04-28 TUN-6014: Add remote config flag as default feature
- 2022-04-12 TUN-6000: Another fix for publishing to brew core
- 2022-04-11 TUN-5990: Add otlp span export to response header
- 2022-04-19 TUN-6070: First connection retries other edge IPs if the error is quic timeout(likely due to firewall blocking UDP)
- 2022-04-11 TUN-6030: Add ttfb span for origin http request
2022.4.1
- 2022-04-11 TUN-6035: Reduce buffer size when proxying data
- 2022-04-11 TUN-6038: Reduce buffer size used for proxying data

View File

@ -40,14 +40,19 @@ stretch: &stretch
- libffi-dev
- python3-setuptools
- python3-pip
- reprepro
- createrepo
pre-cache: &github_release_pkgs_pre_cache
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
- chmod a+x /usr/local/bin/wixl
- pip3 install pynacl==1.4.0
- pip3 install pygithub==1.55
- pip3 install boto3==1.22.9
post-cache:
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
- ./build-packages.sh
# publish packages to linux repos
# TODO TUN-6180: Uncomment this - make release-pkgs-linux
# release the packages built and moved to /cfsetup/built_artifacts
- make github-release-built-pkgs
# handle FIPS separately so that we built with gofips compiler
@ -255,4 +260,4 @@ centos-7:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make publish-rpm
- make publish-rpm

View File

@ -343,13 +343,13 @@ func StartServer(
observer.SendURL(quickTunnelURL)
}
tunnelConfig, dynamicConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
tunnelConfig, orchestratorConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
if err != nil {
log.Err(err).Msg("Couldn't start tunnel")
return err
}
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, tunnelConfig.Log)
if err != nil {
return err
}
@ -388,7 +388,7 @@ func StartServer(
info.Version(),
hostname,
metricsListener.Addr().String(),
dynamicConfig.Ingress,
orchestratorConfig.Ingress,
tunnelConfig.HAConnections,
)
app := tunnelUI.Launch(ctx, log, logTransport)

View File

@ -43,6 +43,8 @@ var (
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders}
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq"}
)
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
@ -348,11 +350,24 @@ func prepareTunnelConfig(
ProtocolSelector: protocolSelector,
EdgeTLSConfigs: edgeTLSConfigs,
}
dynamicConfig := &orchestration.Config{
orchestratorConfig := &orchestration.Config{
Ingress: &ingressRules,
WarpRoutingEnabled: warpRoutingEnabled,
ConfigurationFlags: parseConfigFlags(c),
}
return tunnelConfig, dynamicConfig, nil
return tunnelConfig, orchestratorConfig, nil
}
func parseConfigFlags(c *cli.Context) map[string]string {
result := make(map[string]string)
for _, flag := range configFlags {
if v := c.String(flag); c.IsSet(flag) && v != "" {
result[flag] = v
}
}
return result
}
func gracePeriod(c *cli.Context) (time.Duration, error) {

View File

@ -177,10 +177,10 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
}
type UnvalidatedIngressRule struct {
Hostname string `json:"hostname"`
Path string `json:"path"`
Service string `json:"service"`
Location string `json:"location"`
Hostname string `json:"hostname,omitempty"`
Path string `json:"path,omitempty"`
Service string `json:"service,omitempty"`
Location string `json:"location,omitempty"`
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
}
@ -193,41 +193,41 @@ type UnvalidatedIngressRule struct {
// - To specify a time.Duration in json, use int64 of the nanoseconds
type OriginRequestConfig struct {
// HTTP proxy timeout for establishing a new connection
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout"`
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
// HTTP proxy timeout for completing a TLS handshake
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout"`
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout,omitempty"`
// HTTP proxy TCP keepalive duration
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs,omitempty"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections,omitempty"`
// HTTP proxy timeout for closing an idle connection
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout,omitempty"`
// Sets the HTTP Host header for the local webserver.
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"`
// Hostname on the origin server certificate.
OriginServerName *string `yaml:"originServerName" json:"originServerName"`
OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"`
// Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare.
CAPool *string `yaml:"caPool" json:"caPool"`
CAPool *string `yaml:"caPool" json:"caPool,omitempty"`
// Disables TLS verification of the certificate presented by your origin.
// Will allow any certificate from the origin to be accepted.
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify,omitempty"`
// Disables chunked transfer encoding.
// Useful if you are running a WSGI server.
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding,omitempty"`
// Runs as jump host
BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
BastionMode *bool `yaml:"bastionMode" json:"bastionMode,omitempty"`
// Listen address for the proxy.
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress,omitempty"`
// Listen port for the proxy.
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort,omitempty"`
// Valid options are 'socks' or empty.
ProxyType *string `yaml:"proxyType" json:"proxyType"`
ProxyType *string `yaml:"proxyType" json:"proxyType,omitempty"`
// IP rules for the proxy service
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"`
}
type IngressIPRule struct {

View File

@ -30,6 +30,7 @@ var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols,
type Orchestrator interface {
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
GetConfigJSON() ([]byte, error)
GetOriginProxy() (OriginProxy, error)
}

View File

@ -42,6 +42,10 @@ type mockOrchestrator struct {
originProxy OriginProxy
}
func (mcr *mockOrchestrator) GetConfigJSON() ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}
func (*mockOrchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
return &tunnelpogs.UpdateConfigurationResponse{
LastAppliedVersion: version,

View File

@ -30,11 +30,15 @@ type controlStream struct {
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
type ControlStreamHandler interface {
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
// IsStopped tells whether the method above has finished
IsStopped() bool
}
type TunnelConfigJSONGetter interface {
GetConfigJSON() ([]byte, error)
}
// NewControlStream returns a new instance of ControlStreamHandler
func NewControlStream(
observer *Observer,
@ -63,15 +67,28 @@ func (c *controlStream) ServeControlStream(
ctx context.Context,
rw io.ReadWriteCloser,
connOptions *tunnelpogs.ConnectionOptions,
tunnelConfigGetter TunnelConfigJSONGetter,
) error {
rpcClient := c.newRPCClientFunc(ctx, rw, c.observer.log)
if err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer); err != nil {
registrationDetails, err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer)
if err != nil {
rpcClient.Close()
return err
}
c.connectedFuse.Connected()
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration
if c.connIndex == 0 && !registrationDetails.TunnelIsRemotelyManaged {
if tunnelConfig, err := tunnelConfigGetter.GetConfigJSON(); err == nil {
if err := rpcClient.SendLocalConfiguration(ctx, tunnelConfig, c.observer); err != nil {
c.observer.log.Err(err).Msg("unable to send local configuration")
}
} else {
c.observer.log.Err(err).Msg("failed to obtain current configuration")
}
}
c.waitForUnregister(ctx, rpcClient)
return nil
}

View File

@ -117,7 +117,7 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch connType {
case TypeControlStream:
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions); err != nil {
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator); err != nil {
c.controlStreamErr = err
c.log.Error().Err(err)
respWriter.WriteErrorResponse()

View File

@ -15,6 +15,7 @@ import (
"time"
"github.com/gobwas/ws/wsutil"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -166,18 +167,26 @@ type mockNamedTunnelRPCClient struct {
unregistered chan struct{}
}
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte, observer *Observer) error {
return nil
}
func (mc mockNamedTunnelRPCClient) RegisterConnection(
c context.Context,
properties *NamedTunnelProperties,
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) error {
) (*tunnelpogs.ConnectionDetails, error) {
if mc.shouldFail != nil {
return mc.shouldFail
return nil, mc.shouldFail
}
close(mc.registered)
return nil
return &tunnelpogs.ConnectionDetails{
Location: "LIS",
UUID: uuid.New(),
TunnelIsRemotelyManaged: false,
}, nil
}
func (mc mockNamedTunnelRPCClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
@ -477,7 +486,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
select {
case <-rpcClientFactory.registered:
break //ok
break // ok
case <-time.Tick(time.Second):
t.Fatal("timeout out waiting for registration")
}
@ -487,7 +496,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
select {
case <-rpcClientFactory.unregistered:
break //ok
break // ok
case <-time.Tick(time.Second):
t.Fatal("timeout out waiting for unregistered signal")
}

View File

@ -13,6 +13,7 @@ const (
MetricsNamespace = "cloudflared"
TunnelSubsystem = "tunnel"
muxerSubsystem = "muxer"
configSubsystem = "config"
)
type muxerMetrics struct {
@ -36,6 +37,11 @@ type muxerMetrics struct {
compRateAve *prometheus.GaugeVec
}
type localConfigMetrics struct {
pushes prometheus.Counter
pushesErrors prometheus.Counter
}
type tunnelMetrics struct {
timerRetries prometheus.Gauge
serverLocations *prometheus.GaugeVec
@ -51,6 +57,39 @@ type tunnelMetrics struct {
muxerMetrics *muxerMetrics
tunnelsHA tunnelsForHA
userHostnamesCounts *prometheus.CounterVec
localConfigMetrics *localConfigMetrics
}
func newLocalConfigMetrics() *localConfigMetrics {
pushesMetric := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: configSubsystem,
Name: "local_config_pushes",
Help: "Number of local configuration pushes to the edge",
},
)
pushesErrorsMetric := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: configSubsystem,
Name: "local_config_pushes_errors",
Help: "Number of errors occurred during local configuration pushes",
},
)
prometheus.MustRegister(
pushesMetric,
pushesErrorsMetric,
)
return &localConfigMetrics{
pushes: pushesMetric,
pushesErrors: pushesErrorsMetric,
}
}
func newMuxerMetrics() *muxerMetrics {
@ -386,6 +425,7 @@ func initTunnelMetrics() *tunnelMetrics {
regFail: registerFail,
rpcFail: rpcFail,
userHostnamesCounts: userHostnamesCounts,
localConfigMetrics: newLocalConfigMetrics(),
}
}

View File

@ -111,7 +111,7 @@ func (q *QUICConnection) Serve(ctx context.Context) error {
func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
// This blocks until the control plane is done.
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions)
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
if err != nil {
// Not wrapping error here to be consistent with the http2 message.
return err

View File

@ -163,7 +163,7 @@ type fakeControlStream struct {
ControlStreamHandler
}
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error {
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
<-ctx.Done()
return nil
}

View File

@ -58,6 +58,11 @@ type NamedTunnelRPCClient interface {
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) (*tunnelpogs.ConnectionDetails, error)
SendLocalConfiguration(
c context.Context,
config []byte,
observer *Observer,
) error
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
Close()
@ -90,7 +95,7 @@ func (rsc *registrationServerClient) RegisterConnection(
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) error {
) (*tunnelpogs.ConnectionDetails, error) {
conn, err := rsc.client.RegisterConnection(
ctx,
properties.Credentials.Auth(),
@ -101,10 +106,10 @@ func (rsc *registrationServerClient) RegisterConnection(
if err != nil {
if err.Error() == DuplicateConnectionError {
observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
return errDuplicationConnection
return nil, errDuplicationConnection
}
observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
return serverRegistrationErrorFromRPC(err)
return nil, serverRegistrationErrorFromRPC(err)
}
observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
@ -112,7 +117,18 @@ func (rsc *registrationServerClient) RegisterConnection(
observer.logServerInfo(connIndex, conn.Location, fmt.Sprintf("Connection %s registered", conn.UUID))
observer.sendConnectedEvent(connIndex, conn.Location)
return nil
return conn, nil
}
func (rsc *registrationServerClient) SendLocalConfiguration(ctx context.Context, config []byte, observer *Observer) (err error) {
observer.metrics.localConfigMetrics.pushes.Inc()
defer func() {
if err != nil {
observer.metrics.localConfigMetrics.pushesErrors.Inc()
}
}()
return rsc.client.SendLocalConfiguration(ctx, config)
}
func (rsc *registrationServerClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
@ -274,7 +290,7 @@ func (h *h2muxConnection) registerNamedTunnel(
rpcClient := h.newRPCClientFunc(ctx, stream, h.observer.log)
defer rpcClient.Close()
if err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
if _, err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
return err
}
return nil

View File

@ -255,7 +255,10 @@ func (rc *datagramChannel) Send(ctx context.Context, sessionID uuid.UUID, payloa
case <-ctx.Done():
return ctx.Err()
case <-rc.closedChan:
return fmt.Errorf("datagram channel closed")
return &errClosedSession{
message: fmt.Errorf("datagram channel closed").Error(),
byRemote: true,
}
case rc.datagramChan <- &newDatagram{sessionID: sessionID, payload: payload}:
return nil
}
@ -266,7 +269,11 @@ func (rc *datagramChannel) Receive(ctx context.Context) (uuid.UUID, []byte, erro
case <-ctx.Done():
return uuid.Nil, nil, ctx.Err()
case <-rc.closedChan:
return uuid.Nil, nil, fmt.Errorf("datagram channel closed")
err := &errClosedSession{
message: fmt.Errorf("datagram channel closed").Error(),
byRemote: true,
}
return uuid.Nil, nil, err
case msg := <-rc.datagramChan:
return msg.sessionID, msg.payload, nil
}

2
go.mod
View File

@ -35,7 +35,7 @@ require (
go.opentelemetry.io/otel/trace v1.6.3
go.opentelemetry.io/proto/otlp v0.15.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9

4
go.sum
View File

@ -636,8 +636,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -723,6 +724,7 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=

View File

@ -47,18 +47,26 @@ type RemoteConfig struct {
WarpRouting config.WarpRoutingConfig
}
type remoteConfigJSON struct {
GlobalOriginRequest config.OriginRequestConfig `json:"originRequest"`
type RemoteConfigJSON struct {
GlobalOriginRequest *config.OriginRequestConfig `json:"originRequest,omitempty"`
IngressRules []config.UnvalidatedIngressRule `json:"ingress"`
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
}
func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
var rawConfig remoteConfigJSON
var rawConfig RemoteConfigJSON
if err := json.Unmarshal(b, &rawConfig); err != nil {
return err
}
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest))
// if nil, just assume the default values.
globalOriginRequestConfig := rawConfig.GlobalOriginRequest
if globalOriginRequestConfig == nil {
globalOriginRequestConfig = &config.OriginRequestConfig{}
}
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(*globalOriginRequestConfig))
if err != nil {
return err
}
@ -387,3 +395,91 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
cfg.setIPRules(overrides)
return cfg
}
func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig {
var connectTimeout *config.CustomDuration
var tlsTimeout *config.CustomDuration
var tcpKeepAlive *config.CustomDuration
var keepAliveConnections *int
var keepAliveTimeout *config.CustomDuration
var proxyAddress *string
if c.ConnectTimeout != defaultConnectTimeout {
connectTimeout = &c.ConnectTimeout
}
if c.TLSTimeout != defaultTLSTimeout {
tlsTimeout = &c.TLSTimeout
}
if c.TCPKeepAlive != defaultTCPKeepAlive {
tcpKeepAlive = &c.TCPKeepAlive
}
if c.KeepAliveConnections != defaultKeepAliveConnections {
keepAliveConnections = &c.KeepAliveConnections
}
if c.KeepAliveTimeout != defaultKeepAliveTimeout {
keepAliveTimeout = &c.KeepAliveTimeout
}
if c.ProxyAddress != defaultProxyAddress {
proxyAddress = &c.ProxyAddress
}
return config.OriginRequestConfig{
ConnectTimeout: connectTimeout,
TLSTimeout: tlsTimeout,
TCPKeepAlive: tcpKeepAlive,
NoHappyEyeballs: defaultBoolToNil(c.NoHappyEyeballs),
KeepAliveConnections: keepAliveConnections,
KeepAliveTimeout: keepAliveTimeout,
HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader),
OriginServerName: emptyStringToNil(c.OriginServerName),
CAPool: emptyStringToNil(c.CAPool),
NoTLSVerify: defaultBoolToNil(c.NoTLSVerify),
DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding),
BastionMode: defaultBoolToNil(c.BastionMode),
ProxyAddress: proxyAddress,
ProxyPort: zeroUIntToNil(c.ProxyPort),
ProxyType: emptyStringToNil(c.ProxyType),
IPRules: convertToRawIPRules(c.IPRules),
}
}
func convertToRawIPRules(ipRules []ipaccess.Rule) []config.IngressIPRule {
result := make([]config.IngressIPRule, 0)
for _, r := range ipRules {
cidr := r.StringCIDR()
newRule := config.IngressIPRule{
Prefix: &cidr,
Ports: r.Ports(),
Allow: r.RulePolicy(),
}
result = append(result, newRule)
}
return result
}
func defaultBoolToNil(b bool) *bool {
if b == false {
return nil
}
return &b
}
func emptyStringToNil(s string) *string {
if s == "" {
return nil
}
return &s
}
func zeroUIntToNil(v uint) *uint {
if v == 0 {
return nil
}
return &v
}

View File

@ -109,6 +109,7 @@ func (o rawTCPService) MarshalJSON() ([]byte, error) {
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
// cloudflared access commands.
type tcpOverWSService struct {
scheme string
dest string
isBastion bool
streamHandler streamHandlerFunc
@ -130,7 +131,8 @@ func newTCPOverWSService(url *url.URL) *tcpOverWSService {
addPortIfMissing(url, 7864) // just a random port since there isn't a default in this case
}
return &tcpOverWSService{
dest: url.Host,
scheme: url.Scheme,
dest: url.Host,
}
}
@ -160,7 +162,12 @@ func (o *tcpOverWSService) String() string {
if o.isBastion {
return ServiceBastion
}
return o.dest
if o.scheme != "" {
return fmt.Sprintf("%s://%s", o.scheme, o.dest)
} else {
return o.dest
}
}
func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {

View File

@ -99,3 +99,15 @@ func (ipr *Rule) PortsString() string {
}
return "all"
}
func (ipr *Rule) Ports() []int {
return ipr.ports
}
func (ipr *Rule) RulePolicy() bool {
return ipr.allow
}
func (ipr *Rule) StringCIDR() string {
return ipr.ipNet.String()
}

View File

@ -23,7 +23,7 @@ const (
)
type orchestrator interface {
GetConfigJSON() ([]byte, error)
GetVersionedConfigJSON() ([]byte, error)
}
func newMetricsHandler(
@ -47,7 +47,7 @@ func newMetricsHandler(
})
if orchestrator != nil {
router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
json, err := orchestrator.GetConfigJSON()
json, err := orchestrator.GetVersionedConfigJSON()
if err != nil {
w.WriteHeader(500)
_, _ = fmt.Fprintf(w, "ERR: %v", err)

View File

@ -1,15 +1,66 @@
package orchestration
import (
"encoding/json"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress"
)
type newConfig struct {
type newRemoteConfig struct {
ingress.RemoteConfig
// Add more fields when we support other settings in tunnel orchestration
}
type newLocalConfig struct {
RemoteConfig ingress.RemoteConfig
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
}
// Config is the original config as read and parsed by cloudflared.
type Config struct {
Ingress *ingress.Ingress
WarpRoutingEnabled bool
// Extra settings used to configure this instance but that are not eligible for remotely management
// ie. (--protocol, --loglevel, ...)
ConfigurationFlags map[string]string
}
func (rc *newLocalConfig) MarshalJSON() ([]byte, error) {
var r = struct {
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
ingress.RemoteConfigJSON
}{
ConfigurationFlags: rc.ConfigurationFlags,
RemoteConfigJSON: ingress.RemoteConfigJSON{
// UI doesn't support top level configs, so we reconcile to individual ingress configs.
GlobalOriginRequest: nil,
IngressRules: convertToUnvalidatedIngressRules(rc.RemoteConfig.Ingress),
WarpRouting: rc.RemoteConfig.WarpRouting,
},
}
return json.Marshal(r)
}
func convertToUnvalidatedIngressRules(i ingress.Ingress) []config.UnvalidatedIngressRule {
result := make([]config.UnvalidatedIngressRule, 0)
for _, rule := range i.Rules {
var path string
if rule.Path != nil {
path = rule.Path.String()
}
newRule := config.UnvalidatedIngressRule{
Hostname: rule.Hostname,
Path: path,
Service: rule.Service.String(),
OriginRequest: ingress.ConvertToRawOriginConfig(rule.Config),
}
result = append(result, newRule)
}
return result
}

View File

@ -0,0 +1,82 @@
package orchestration
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/ingress"
)
// TestNewLocalConfig_MarshalJSON tests that we are able to converte a compiled and validated config back
// into an "unvalidated" format which is compatible with Remote Managed configurations.
func TestNewLocalConfig_MarshalJSON(t *testing.T) {
rawConfig := []byte(`
{
"originRequest": {
"connectTimeout": 160,
"httpHostHeader": "default"
},
"ingress": [
{
"hostname": "tun.example.com",
"service": "https://localhost:8000"
},
{
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 121,
"tlsTimeout": 2,
"noHappyEyeballs": false,
"tcpKeepAlive": 2,
"keepAliveConnections": 2,
"keepAliveTimeout": 2,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
"noTLSVerify": false,
"disableChunkedEncoding": false,
"bastionMode": false,
"proxyAddress": "interface",
"proxyPort": 200,
"proxyType": "",
"ipRules": [
{
"prefix": "10.0.0.0/16",
"ports": [3000, 3030],
"allow": false
},
{
"prefix": "192.16.0.0/24",
"ports": [5000, 5050],
"allow": true
}
]
}
}
]
}
`)
var expectedConfig ingress.RemoteConfig
err := json.Unmarshal(rawConfig, &expectedConfig)
require.NoError(t, err)
c := &newLocalConfig{
RemoteConfig: expectedConfig,
ConfigurationFlags: nil,
}
jsonSerde, err := json.Marshal(c)
require.NoError(t, err)
var config ingress.RemoteConfig
err = json.Unmarshal(jsonSerde, &config)
require.NoError(t, err)
require.Equal(t, config.WarpRouting.Enabled, false)
require.Equal(t, config.Ingress.Rules, expectedConfig.Ingress.Rules)
}

View File

@ -54,7 +54,7 @@ func NewOrchestrator(ctx context.Context, config *Config, tags []tunnelpogs.Tag,
return o, nil
}
// Update creates a new proxy with the new ingress rules
// UpdateConfig creates a new proxy with the new ingress rules
func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
o.lock.Lock()
defer o.lock.Unlock()
@ -63,12 +63,12 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
o.log.Debug().
Int32("current_version", o.currentVersion).
Int32("received_version", version).
Msg("Current version is equal or newer than receivied version")
Msg("Current version is equal or newer than received version")
return &tunnelpogs.UpdateConfigurationResponse{
LastAppliedVersion: o.currentVersion,
}
}
var newConf newConfig
var newConf newRemoteConfig
if err := json.Unmarshal(config, &newConf); err != nil {
o.log.Err(err).
Int32("version", version).
@ -131,10 +131,26 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEn
return nil
}
// GetConfigJSON returns the current version and configuration as JSON
// GetConfigJSON returns the current json serialization of the config as the edge understands it
func (o *Orchestrator) GetConfigJSON() ([]byte, error) {
o.lock.RLock()
defer o.lock.RUnlock()
c := &newLocalConfig{
RemoteConfig: ingress.RemoteConfig{
Ingress: *o.config.Ingress,
WarpRouting: config.WarpRoutingConfig{Enabled: o.config.WarpRoutingEnabled},
},
ConfigurationFlags: o.config.ConfigurationFlags,
}
return json.Marshal(c)
}
// GetVersionedConfigJSON returns the current version and configuration as JSON
func (o *Orchestrator) GetVersionedConfigJSON() ([]byte, error) {
o.lock.RLock()
defer o.lock.RUnlock()
var currentConfiguration = struct {
Version int32 `json:"version"`
Config struct {

View File

@ -2,6 +2,7 @@ package orchestration
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@ -641,6 +642,19 @@ func TestPersistentConnection(t *testing.T) {
wg.Wait()
}
func TestSerializeLocalConfig(t *testing.T) {
c := &newLocalConfig{
RemoteConfig: ingress.RemoteConfig{
Ingress: ingress.Ingress{},
WarpRouting: config.WarpRoutingConfig{},
},
ConfigurationFlags: map[string]string{"a": "b"},
}
result, _ := json.Marshal(c)
fmt.Println(string(result))
}
func wsEcho(w http.ResponseWriter, r *http.Request) {
upgrader := gows.Upgrader{}

264
release_pkgs.py Normal file
View File

@ -0,0 +1,264 @@
"""
This is a utility for creating deb and rpm packages, signing them
and uploading them to a storage and adding metadata to workers KV.
It has two over-arching responsiblities:
1. Create deb and yum repositories from .deb and .rpm files.
This is also responsible for signing the packages and generally preparing
them to be in an uploadable state.
2. Upload these packages to a storage in a format that apt and yum expect.
"""
import subprocess
import os
import argparse
import logging
import shutil
from hashlib import sha256
import boto3
from botocore.client import Config
from botocore.exceptions import ClientError
# The front facing R2 URL to access assets from.
R2_ASSET_URL = 'https://demo-r2-worker.cloudflare-tunnel.workers.dev/'
class PkgUploader:
def __init__(self, account_id, bucket_name, client_id, client_secret):
self.account_id = account_id
self.bucket_name = bucket_name
self.client_id = client_id
self.client_secret = client_secret
def upload_pkg_to_r2(self, filename, upload_file_path):
endpoint_url = f"https://{self.account_id}.r2.cloudflarestorage.com"
token_secret_hash = sha256(self.client_secret.encode()).hexdigest()
config = Config(
region_name = 'auto',
s3={
"addressing_style": "path",
}
)
r2 = boto3.client(
"s3",
endpoint_url=endpoint_url,
aws_access_key_id=self.client_id,
aws_secret_access_key=token_secret_hash,
config=config,
)
print(f"uploading asset: {filename} to {upload_file_path}...")
try:
r2.upload_file(filename, self.bucket_name, upload_file_path)
except ClientError as e:
raise e
class PkgCreator:
"""
The distribution conf is what dictates to reprepro, the debian packaging building
and signing tool we use, what distros to support, what GPG key to use for signing
and what to call the debian binary etc. This function creates it "./conf/distributions".
origin - name of your package (String)
label - label of your package (could be same as the name) (String)
release - release you want this to be distributed for (List of Strings)
components - could be a channel like main/stable/beta
archs - Architecture (List of Strings)
description - (String)
gpg_key_id - gpg key id of what you want to use to sign the packages.(String)
"""
def create_distribution_conf(self,
file_path,
origin,
label,
releases,
archs,
components,
description,
gpg_key_id ):
with open(file_path, "w") as distributions_file:
for release in releases:
distributions_file.write(f"Origin: {origin}\n")
distributions_file.write(f"Label: {label}\n")
distributions_file.write(f"Codename: {release}\n")
archs_list = " ".join(archs)
distributions_file.write(f"Architectures: {archs_list}\n")
distributions_file.write(f"Components: {components}\n")
distributions_file.write(f"Description: {description} - {release}\n")
distributions_file.write(f"SignWith: {gpg_key_id}\n")
distributions_file.write("\n")
return distributions_file
"""
Uses the reprepro tool to generate packages, sign them and create the InRelease as specified
by the distribution_conf file.
This function creates three folders db, pool and dist.
db and pool contain information and metadata about builds. We can ignore these.
dist: contains all the pkgs and signed releases that are necessary for an apt download.
"""
def create_deb_pkgs(self, release, deb_file):
self._clean_build_resources()
subprocess.call(("reprepro", "includedeb", release, deb_file), timeout=120)
"""
This is mostly useful to clear previously built db, dist and pool resources.
"""
def _clean_build_resources(self):
subprocess.call(("reprepro", "clearvanished"), timeout=120)
# TODO https://jira.cfops.it/browse/TUN-6209 : Sign these packages.
def create_rpm_pkgs(self, artifacts_path):
self._setup_rpm_pkg_directories(artifacts_path)
subprocess.call(("createrepo", "./rpm"), timeout=120)
"""
sets up the RPM directories in the following format:
- rpm
- aarch64
- x86_64
- 386
this assumes the assets are in the format <prefix>-<aarch64/x86_64/386>.rpm
"""
def _setup_rpm_pkg_directories(self, artifacts_path, archs=["aarch64", "x86_64", "386"]):
for arch in archs:
for root, _ , files in os.walk(artifacts_path):
for file in files:
if file.endswith(f"{arch}.rpm"):
new_dir = f"./rpm/{arch}"
os.makedirs(new_dir, exist_ok=True)
old_path = os.path.join(root, file)
new_path = os.path.join(new_dir, file)
shutil.copyfile(old_path, new_path)
"""
Walks through a directory and uploads it's assets to R2.
directory : root directory to walk through (String).
release: release string. If this value is none, a specific release path will not be created
and the release will be uploaded to the default path.
binary: name of the binary to upload
"""
def upload_from_directories(pkg_uploader, directory, release, binary):
for root, _ , files in os.walk(directory):
for file in files:
upload_file_name = os.path.join(binary, root, file)
if release:
upload_file_name = os.path.join(release, upload_file_name)
filename = os.path.join(root,file)
try:
pkg_uploader.upload_pkg_to_r2(filename, upload_file_name)
except ClientError as e:
logging.error(e)
return
"""
1. looks into a built_artifacts folder for cloudflared debs
2. creates Packages.gz, InRelease (signed) files
3. uploads them to Cloudflare R2
pkg_creator, pkg_uploader: are instantiations of the two classes above.
gpg_key_id: is an id indicating the key the package should be signed with. The public key of this id will be
uploaded to R2 so it can be presented to apt downloaders.
release_version: is the cloudflared release version.
"""
def create_deb_packaging(pkg_creator, pkg_uploader, releases, gpg_key_id, binary_name, archs, package_component, release_version):
# set configuration for package creation.
print(f"initialising configuration for {binary_name} , {archs}")
pkg_creator.create_distribution_conf(
"./conf/distributions",
binary_name,
binary_name,
releases,
archs,
package_component,
f"apt repository for {binary_name}",
gpg_key_id)
# create deb pkgs
for release in releases:
for arch in archs:
print(f"creating deb pkgs for {release} and {arch}...")
pkg_creator.create_deb_pkgs(release, f"./built_artifacts/cloudflared-linux-{arch}.deb")
print("uploading latest to r2...")
upload_from_directories(pkg_uploader, "dists", None, binary_name)
upload_from_directories(pkg_uploader, "pool", None, binary_name)
print(f"uploading versioned release {release_version} to r2...")
upload_from_directories(pkg_uploader, "dists", release_version, binary_name)
upload_from_directories(pkg_uploader, "pool", release_version, binary_name)
def create_rpm_packaging(pkg_creator, pkg_uploader, artifacts_path, release_version, binary_name):
print(f"creating rpm pkgs...")
pkg_creator.create_rpm_pkgs(artifacts_path)
print("uploading latest to r2...")
upload_from_directories(pkg_uploader, "rpm", None, binary_name)
print(f"uploading versioned release {release_version} to r2...")
upload_from_directories(pkg_uploader, "rpm", release_version, binary_name)
def parse_args():
parser = argparse.ArgumentParser(
description="Creates linux releases and uploads them in a packaged format"
)
parser.add_argument(
"--bucket", default=os.environ.get("R2_BUCKET_NAME"), help="R2 Bucket name"
)
parser.add_argument(
"--id", default=os.environ.get("R2_CLIENT_ID"), help="R2 Client ID"
)
parser.add_argument(
"--secret", default=os.environ.get("R2_CLIENT_SECRET"), help="R2 Client Secret"
)
parser.add_argument(
"--account", default=os.environ.get("R2_ACCOUNT_ID"), help="R2 Account Tag"
)
parser.add_argument(
"--release-tag", default=os.environ.get("RELEASE_VERSION"), help="Release version you want your pkgs to be\
prefixed with"
)
parser.add_argument(
"--binary", default=os.environ.get("BINARY_NAME"), help="The name of the binary the packages are for"
)
parser.add_argument(
"--gpg-key-id", default=os.environ.get("GPG_KEY_ID"), help="gpg key ID that's being used to sign release\
packages."
)
parser.add_argument(
"--deb-based-releases", default=["bookworm", "bullseye", "buster", "jammy", "impish", "focal", "bionic"],
help="list of debian based releases that need to be packaged for"
)
parser.add_argument(
"--archs", default=["amd64", "386", "arm64"], help="list of architectures we want to package for. Note that\
it is the caller's responsiblity to ensure that these debs are already present in a directory. This script\
will not build binaries or create their debs."
)
args = parser.parse_args()
return args
if __name__ == "__main__":
try:
args = parse_args()
except Exception as e:
logging.exception(e)
exit(1)
pkg_creator = PkgCreator()
pkg_uploader = PkgUploader(args.account, args.bucket, args.id, args.secret)
create_deb_packaging(pkg_creator, pkg_uploader, args.deb_based_releases, args.gpg_key_id, args.binary,
args.archs, "main", args.release_tag)
create_rpm_packaging(pkg_creator, pkg_uploader, "./built_artifacts", args.release_tag, args.binary )

View File

@ -175,6 +175,24 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
return nil, newRPCError("unknown result which %d", result.Which())
}
func (c RegistrationServer_PogsClient) SendLocalConfiguration(ctx context.Context, config []byte) error {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.UpdateLocalConfiguration(ctx, func(p tunnelrpc.RegistrationServer_updateLocalConfiguration_Params) error {
if err := p.SetConfig(config); err != nil {
return err
}
return nil
})
_, err := promise.Struct()
if err != nil {
return wrapRPCError(err)
}
return nil
}
func (c RegistrationServer_PogsClient) UnregisterConnection(ctx context.Context) error {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.UnregisterConnection(ctx, func(p tunnelrpc.RegistrationServer_unregisterConnection_Params) error {

View File

@ -15,6 +15,7 @@ const bufSize = 256
// xorKeyStreamVX is an assembly implementation of XORKeyStream. It must only
// be called when the vector facility is available. Implementation in asm_s390x.s.
//
//go:noescape
func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32)

View File

@ -407,7 +407,12 @@ func (s *String) ReadASN1Enum(out *int) bool {
func (s *String) readBase128Int(out *int) bool {
ret := 0
for i := 0; len(*s) > 0; i++ {
if i == 4 {
if i == 5 {
return false
}
// Avoid overflowing int on a 32-bit platform.
// We don't want different behavior based on the architecture.
if ret >= 1<<(31-7) {
return false
}
ret <<= 7

View File

@ -106,13 +106,13 @@ func (b *Builder) AddBytes(v []byte) {
// supplied to them. The child builder passed to the continuation can be used
// to build the content of the length-prefixed sequence. For example:
//
// parent := cryptobyte.NewBuilder()
// parent.AddUint8LengthPrefixed(func (child *Builder) {
// child.AddUint8(42)
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
// grandchild.AddUint8(5)
// })
// })
// parent := cryptobyte.NewBuilder()
// parent.AddUint8LengthPrefixed(func (child *Builder) {
// child.AddUint8(42)
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
// grandchild.AddUint8(5)
// })
// })
//
// It is an error to write more bytes to the child than allowed by the reserved
// length prefix. After the continuation returns, the child must be considered

View File

@ -1,13 +1,16 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
package field
// feMul sets out = a * b. It works like feMulGeneric.
//
//go:noescape
func feMul(out *Element, a *Element, b *Element)
// feSquare sets out = a * a. It works like feSquareGeneric.
//
//go:noescape
func feSquare(out *Element, a *Element)

View File

@ -1,13 +1,7 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// In Go 1.13, the ed25519 package was promoted to the standard library as
// crypto/ed25519, and this package became a wrapper for the standard library one.
//
//go:build !go1.13
// +build !go1.13
// Package ed25519 implements the Ed25519 signature algorithm. See
// https://ed25519.cr.yp.to/.
//
@ -16,21 +10,15 @@
// representation includes a public key suffix to make multiple signing
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the “seed”.
//
// Beginning with Go 1.13, the functionality of this package was moved to the
// standard library as crypto/ed25519. This package only acts as a compatibility
// wrapper.
package ed25519
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
import (
"bytes"
"crypto"
cryptorand "crypto/rand"
"crypto/sha512"
"errors"
"crypto/ed25519"
"io"
"strconv"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
const (
@ -45,57 +33,21 @@ const (
)
// PublicKey is the type of Ed25519 public keys.
type PublicKey []byte
//
// This type is an alias for crypto/ed25519's PublicKey type.
// See the crypto/ed25519 package for the methods on this type.
type PublicKey = ed25519.PublicKey
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
type PrivateKey []byte
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[32:])
return PublicKey(publicKey)
}
// Seed returns the private key seed corresponding to priv. It is provided for
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
// in this package.
func (priv PrivateKey) Seed() []byte {
seed := make([]byte, SeedSize)
copy(seed, priv[:32])
return seed
}
// Sign signs the given message with priv.
// Ed25519 performs two passes over messages to be signed and therefore cannot
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
// indicate the message hasn't been hashed. This can be achieved by passing
// crypto.Hash(0) as the value for opts.
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
if opts.HashFunc() != crypto.Hash(0) {
return nil, errors.New("ed25519: cannot sign hashed message")
}
return Sign(priv, message), nil
}
//
// This type is an alias for crypto/ed25519's PrivateKey type.
// See the crypto/ed25519 package for the methods on this type.
type PrivateKey = ed25519.PrivateKey
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
if rand == nil {
rand = cryptorand.Reader
}
seed := make([]byte, SeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
privateKey := NewKeyFromSeed(seed)
publicKey := make([]byte, PublicKeySize)
copy(publicKey, privateKey[32:])
return publicKey, privateKey, nil
return ed25519.GenerateKey(rand)
}
// NewKeyFromSeed calculates a private key from a seed. It will panic if
@ -103,121 +55,17 @@ func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
// package.
func NewKeyFromSeed(seed []byte) PrivateKey {
if l := len(seed); l != SeedSize {
panic("ed25519: bad seed length: " + strconv.Itoa(l))
}
digest := sha512.Sum512(seed)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
var A edwards25519.ExtendedGroupElement
var hBytes [32]byte
copy(hBytes[:], digest[:])
edwards25519.GeScalarMultBase(&A, &hBytes)
var publicKeyBytes [32]byte
A.ToBytes(&publicKeyBytes)
privateKey := make([]byte, PrivateKeySize)
copy(privateKey, seed)
copy(privateKey[32:], publicKeyBytes[:])
return privateKey
return ed25519.NewKeyFromSeed(seed)
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) []byte {
if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}
h := sha512.New()
h.Write(privateKey[:32])
var digest1, messageDigest, hramDigest [64]byte
var expandedSecretKey [32]byte
h.Sum(digest1[:0])
copy(expandedSecretKey[:], digest1[:])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
h.Reset()
h.Write(digest1[32:])
h.Write(message)
h.Sum(messageDigest[:0])
var messageDigestReduced [32]byte
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
var R edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
var encodedR [32]byte
R.ToBytes(&encodedR)
h.Reset()
h.Write(encodedR[:])
h.Write(privateKey[32:])
h.Write(message)
h.Sum(hramDigest[:0])
var hramDigestReduced [32]byte
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
var s [32]byte
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
signature := make([]byte, SignatureSize)
copy(signature[:], encodedR[:])
copy(signature[32:], s[:])
return signature
return ed25519.Sign(privateKey, message)
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool {
if l := len(publicKey); l != PublicKeySize {
panic("ed25519: bad public key length: " + strconv.Itoa(l))
}
if len(sig) != SignatureSize || sig[63]&224 != 0 {
return false
}
var A edwards25519.ExtendedGroupElement
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
if !A.FromBytes(&publicKeyBytes) {
return false
}
edwards25519.FeNeg(&A.X, &A.X)
edwards25519.FeNeg(&A.T, &A.T)
h := sha512.New()
h.Write(sig[:32])
h.Write(publicKey[:])
h.Write(message)
var digest [64]byte
h.Sum(digest[:0])
var hReduced [32]byte
edwards25519.ScReduce(&hReduced, &digest)
var R edwards25519.ProjectiveGroupElement
var s [32]byte
copy(s[:], sig[32:])
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
// the range [0, order) in order to prevent signature malleability.
if !edwards25519.ScMinimal(&s) {
return false
}
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
var checkR [32]byte
R.ToBytes(&checkR)
return bytes.Equal(sig[:32], checkR[:])
return ed25519.Verify(publicKey, message, sig)
}

View File

@ -1,74 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13
// +build go1.13
// Package ed25519 implements the Ed25519 signature algorithm. See
// https://ed25519.cr.yp.to/.
//
// These functions are also compatible with the “Ed25519” function defined in
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
// representation includes a public key suffix to make multiple signing
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the “seed”.
//
// Beginning with Go 1.13, the functionality of this package was moved to the
// standard library as crypto/ed25519. This package only acts as a compatibility
// wrapper.
package ed25519
import (
"crypto/ed25519"
"io"
)
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
SignatureSize = 64
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
SeedSize = 32
)
// PublicKey is the type of Ed25519 public keys.
//
// This type is an alias for crypto/ed25519's PublicKey type.
// See the crypto/ed25519 package for the methods on this type.
type PublicKey = ed25519.PublicKey
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
//
// This type is an alias for crypto/ed25519's PrivateKey type.
// See the crypto/ed25519 package for the methods on this type.
type PrivateKey = ed25519.PrivateKey
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
return ed25519.GenerateKey(rand)
}
// NewKeyFromSeed calculates a private key from a seed. It will panic if
// len(seed) is not SeedSize. This function is provided for interoperability
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
// package.
func NewKeyFromSeed(seed []byte) PrivateKey {
return ed25519.NewKeyFromSeed(seed)
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) []byte {
return ed25519.Sign(privateKey, message)
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool {
return ed25519.Verify(publicKey, message, sig)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -136,7 +136,7 @@ func shiftRightBy2(a uint128) uint128 {
// updateGeneric absorbs msg into the state.h accumulator. For each chunk m of
// 128 bits of message, it computes
//
// h₊ = (h + m) * r mod 2¹³⁰ - 5
// h₊ = (h + m) * r mod 2¹³⁰ - 5
//
// If the msg length is not a multiple of TagSize, it assumes the last
// incomplete chunk is the final one.
@ -278,8 +278,7 @@ const (
// finalize completes the modular reduction of h and computes
//
// out = h + s mod 2¹²⁸
//
// out = h + s mod 2¹²⁸
func finalize(out *[TagSize]byte, h *[3]uint64, s *[2]uint64) {
h0, h1, h2 := h[0], h[1], h[2]

View File

@ -14,6 +14,7 @@ import (
// updateVX is an assembly implementation of Poly1305 that uses vector
// instructions. It must only be called if the vector facility (vx) is
// available.
//
//go:noescape
func updateVX(state *macState, msg []byte)

View File

@ -32,7 +32,7 @@ import (
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
// doing:
//
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
//
// Remember to get a good random salt. At least 8 bytes is recommended by the
// RFC.

View File

@ -14,8 +14,10 @@ import (
"time"
)
// These constants from [PROTOCOL.certkeys] represent the algorithm names
// for certificate types supported by this package.
// Certificate algorithm names from [PROTOCOL.certkeys]. These values can appear
// in Certificate.Type, PublicKey.Type, and ClientConfig.HostKeyAlgorithms.
// Unlike key algorithm names, these are not passed to AlgorithmSigner and don't
// appear in the Signature.Format field.
const (
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
@ -25,6 +27,21 @@ const (
CertAlgoSKECDSA256v01 = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
CertAlgoSKED25519v01 = "sk-ssh-ed25519-cert-v01@openssh.com"
// CertAlgoRSASHA256v01 and CertAlgoRSASHA512v01 can't appear as a
// Certificate.Type (or PublicKey.Type), but only in
// ClientConfig.HostKeyAlgorithms.
CertAlgoRSASHA256v01 = "rsa-sha2-256-cert-v01@openssh.com"
CertAlgoRSASHA512v01 = "rsa-sha2-512-cert-v01@openssh.com"
)
const (
// Deprecated: use CertAlgoRSAv01.
CertSigAlgoRSAv01 = CertAlgoRSAv01
// Deprecated: use CertAlgoRSASHA256v01.
CertSigAlgoRSASHA2256v01 = CertAlgoRSASHA256v01
// Deprecated: use CertAlgoRSASHA512v01.
CertSigAlgoRSASHA2512v01 = CertAlgoRSASHA512v01
)
// Certificate types distinguish between host and user
@ -423,6 +440,16 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
}
c.SignatureKey = authority.PublicKey()
// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), KeyAlgoRSASHA512)
if err != nil {
return err
}
c.Signature = sig
return nil
}
sig, err := authority.Sign(rand, c.bytesForSigning())
if err != nil {
return err
@ -431,26 +458,40 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
return nil
}
var certAlgoNames = map[string]string{
KeyAlgoRSA: CertAlgoRSAv01,
KeyAlgoDSA: CertAlgoDSAv01,
KeyAlgoECDSA256: CertAlgoECDSA256v01,
KeyAlgoECDSA384: CertAlgoECDSA384v01,
KeyAlgoECDSA521: CertAlgoECDSA521v01,
KeyAlgoSKECDSA256: CertAlgoSKECDSA256v01,
KeyAlgoED25519: CertAlgoED25519v01,
KeyAlgoSKED25519: CertAlgoSKED25519v01,
// certKeyAlgoNames is a mapping from known certificate algorithm names to the
// corresponding public key signature algorithm.
var certKeyAlgoNames = map[string]string{
CertAlgoRSAv01: KeyAlgoRSA,
CertAlgoRSASHA256v01: KeyAlgoRSASHA256,
CertAlgoRSASHA512v01: KeyAlgoRSASHA512,
CertAlgoDSAv01: KeyAlgoDSA,
CertAlgoECDSA256v01: KeyAlgoECDSA256,
CertAlgoECDSA384v01: KeyAlgoECDSA384,
CertAlgoECDSA521v01: KeyAlgoECDSA521,
CertAlgoSKECDSA256v01: KeyAlgoSKECDSA256,
CertAlgoED25519v01: KeyAlgoED25519,
CertAlgoSKED25519v01: KeyAlgoSKED25519,
}
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
// Panics if a non-certificate algorithm is passed.
func certToPrivAlgo(algo string) string {
for privAlgo, pubAlgo := range certAlgoNames {
if pubAlgo == algo {
return privAlgo
// underlyingAlgo returns the signature algorithm associated with algo (which is
// an advertised or negotiated public key or host key algorithm). These are
// usually the same, except for certificate algorithms.
func underlyingAlgo(algo string) string {
if a, ok := certKeyAlgoNames[algo]; ok {
return a
}
return algo
}
// certificateAlgo returns the certificate algorithms that uses the provided
// underlying signature algorithm.
func certificateAlgo(algo string) (certAlgo string, ok bool) {
for certName, algoName := range certKeyAlgoNames {
if algoName == algo {
return certName, true
}
}
panic("unknown cert algorithm")
return "", false
}
func (cert *Certificate) bytesForSigning() []byte {
@ -494,13 +535,13 @@ func (c *Certificate) Marshal() []byte {
return result
}
// Type returns the key name. It is part of the PublicKey interface.
// Type returns the certificate algorithm name. It is part of the PublicKey interface.
func (c *Certificate) Type() string {
algo, ok := certAlgoNames[c.Key.Type()]
certName, ok := certificateAlgo(c.Key.Type())
if !ok {
panic("unknown cert key type " + c.Key.Type())
panic("unknown certificate type for key type " + c.Key.Type())
}
return algo
return certName
}
// Verify verifies a signature against the certificate's public

View File

@ -394,6 +394,10 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
}
c.incIV()
if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}
padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
@ -636,7 +640,7 @@ 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
// 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.
@ -710,6 +714,10 @@ func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([
plain := c.buf[4:contentEnd]
s.XORKeyStream(plain, plain)
if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}
padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies

View File

@ -113,14 +113,18 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
return c.clientAuthenticate(config)
}
// verifyHostKeySignature verifies the host key obtained in the key
// exchange.
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
// verifyHostKeySignature verifies the host key obtained in the key exchange.
// algo is the negotiated algorithm, and may be a certificate type.
func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error {
sig, rest, ok := parseSignatureBody(result.Signature)
if len(rest) > 0 || !ok {
return errors.New("ssh: signature parse error")
}
if a := underlyingAlgo(algo); sig.Format != a {
return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, a)
}
return hostKey.Verify(result.H, sig)
}
@ -224,11 +228,11 @@ type ClientConfig struct {
// be used for the connection. If empty, a reasonable default is used.
ClientVersion string
// HostKeyAlgorithms lists the key types that the client will
// accept from the server as host key, in order of
// HostKeyAlgorithms lists the public key algorithms that the client will
// accept from the server for host key authentication, in order of
// preference. If empty, a reasonable default is used. Any
// string returned from PublicKey.Type method may be used, or
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
// string returned from a PublicKey.Type method may be used, or
// any of the CertAlgo and KeyAlgo constants.
HostKeyAlgorithms []string
// Timeout is the maximum amount of time for the TCP connection to establish.

View File

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"strings"
)
type authResult int
@ -29,6 +30,33 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
if err != nil {
return err
}
// The server may choose to send a SSH_MSG_EXT_INFO at this point (if we
// advertised willingness to receive one, which we always do) or not. See
// RFC 8308, Section 2.4.
extensions := make(map[string][]byte)
if len(packet) > 0 && packet[0] == msgExtInfo {
var extInfo extInfoMsg
if err := Unmarshal(packet, &extInfo); err != nil {
return err
}
payload := extInfo.Payload
for i := uint32(0); i < extInfo.NumExtensions; i++ {
name, rest, ok := parseString(payload)
if !ok {
return parseError(msgExtInfo)
}
value, rest, ok := parseString(rest)
if !ok {
return parseError(msgExtInfo)
}
extensions[string(name)] = value
payload = rest
}
packet, err = c.transport.readPacket()
if err != nil {
return err
}
}
var serviceAccept serviceAcceptMsg
if err := Unmarshal(packet, &serviceAccept); err != nil {
return err
@ -41,7 +69,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
sessionID := c.transport.getSessionID()
for auth := AuthMethod(new(noneAuth)); auth != nil; {
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
if err != nil {
return err
}
@ -93,7 +121,7 @@ type AuthMethod interface {
// If authentication is not successful, a []string of alternative
// method names is returned. If the slice is nil, it will be ignored
// and the previous set of possible methods will be reused.
auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
auth(session []byte, user string, p packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error)
// method returns the RFC 4252 method name.
method() string
@ -102,7 +130,7 @@ type AuthMethod interface {
// "none" authentication, RFC 4252 section 5.2.
type noneAuth int
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
if err := c.writePacket(Marshal(&userAuthRequestMsg{
User: user,
Service: serviceSSH,
@ -122,7 +150,7 @@ func (n *noneAuth) method() string {
// a function call, e.g. by prompting the user.
type passwordCallback func() (password string, err error)
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
type passwordAuthMsg struct {
User string `sshtype:"50"`
Service string
@ -189,7 +217,46 @@ func (cb publicKeyCallback) method() string {
return "publickey"
}
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (as AlgorithmSigner, algo string) {
keyFormat := signer.PublicKey().Type()
// Like in sendKexInit, if the public key implements AlgorithmSigner we
// assume it supports all algorithms, otherwise only the key format one.
as, ok := signer.(AlgorithmSigner)
if !ok {
return algorithmSignerWrapper{signer}, keyFormat
}
extPayload, ok := extensions["server-sig-algs"]
if !ok {
// If there is no "server-sig-algs" extension, fall back to the key
// format algorithm.
return as, keyFormat
}
// The server-sig-algs extension only carries underlying signature
// algorithm, but we are trying to select a protocol-level public key
// algorithm, which might be a certificate type. Extend the list of server
// supported algorithms to include the corresponding certificate algorithms.
serverAlgos := strings.Split(string(extPayload), ",")
for _, algo := range serverAlgos {
if certAlgo, ok := certificateAlgo(algo); ok {
serverAlgos = append(serverAlgos, certAlgo)
}
}
keyAlgos := algorithmsForKeyFormat(keyFormat)
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos)
if err != nil {
// If there is no overlap, try the key anyway with the key format
// algorithm, to support servers that fail to list all supported
// algorithms.
return as, keyFormat
}
return as, algo
}
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
// 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
// attempt to authenticate with the valid key. If not the client will repeat
@ -201,7 +268,10 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
}
var methods []string
for _, signer := range signers {
ok, err := validateKey(signer.PublicKey(), user, c)
pub := signer.PublicKey()
as, algo := pickSignatureAlgorithm(signer, extensions)
ok, err := validateKey(pub, algo, user, c)
if err != nil {
return authFailure, nil, err
}
@ -209,13 +279,13 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
continue
}
pub := signer.PublicKey()
pubKey := pub.Marshal()
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
data := buildDataSignedForAuth(session, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: cb.method(),
}, []byte(pub.Type()), pubKey))
}, algo, pubKey)
sign, err := as.SignWithAlgorithm(rand, data, underlyingAlgo(algo))
if err != nil {
return authFailure, nil, err
}
@ -229,7 +299,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
Service: serviceSSH,
Method: cb.method(),
HasSig: true,
Algoname: pub.Type(),
Algoname: algo,
PubKey: pubKey,
Sig: sig,
}
@ -266,26 +336,25 @@ func containsMethod(methods []string, method string) bool {
}
// validateKey validates the key provided is acceptable to the server.
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, error) {
pubKey := key.Marshal()
msg := publickeyAuthMsg{
User: user,
Service: serviceSSH,
Method: "publickey",
HasSig: false,
Algoname: key.Type(),
Algoname: algo,
PubKey: pubKey,
}
if err := c.writePacket(Marshal(&msg)); err != nil {
return false, err
}
return confirmKeyAck(key, c)
return confirmKeyAck(key, algo, c)
}
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) {
pubKey := key.Marshal()
algoname := key.Type()
for {
packet, err := c.readPacket()
@ -302,14 +371,14 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
if err := Unmarshal(packet, &msg); err != nil {
return false, err
}
if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
if msg.Algo != algo || !bytes.Equal(msg.PubKey, pubKey) {
return false, nil
}
return true, nil
case msgUserAuthFailure:
return false, nil
default:
return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
return false, unexpectedMessageError(msgUserAuthPubKeyOk, packet[0])
}
}
}
@ -330,6 +399,7 @@ func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMet
// along with a list of remaining authentication methods to try next and
// an error if an unexpected response was received.
func handleAuthResponse(c packetConn) (authResult, []string, error) {
gotMsgExtInfo := false
for {
packet, err := c.readPacket()
if err != nil {
@ -341,6 +411,12 @@ func handleAuthResponse(c packetConn) (authResult, []string, error) {
if err := handleBannerResponse(c, packet); err != nil {
return authFailure, nil, err
}
case msgExtInfo:
// Ignore post-authentication RFC 8308 extensions, once.
if gotMsgExtInfo {
return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
}
gotMsgExtInfo = true
case msgUserAuthFailure:
var msg userAuthFailureMsg
if err := Unmarshal(packet, &msg); err != nil {
@ -380,10 +456,10 @@ func handleBannerResponse(c packetConn, packet []byte) error {
// disabling echoing (e.g. for passwords), and return all the answers.
// Challenge may be called multiple times in a single session. After
// successful authentication, the server may send a challenge with no
// questions, for which the user and instruction messages should be
// questions, for which the name and instruction messages should be
// printed. RFC 4256 section 3.3 details how the UI should behave for
// both CLI and GUI environments.
type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
type KeyboardInteractiveChallenge func(name, instruction string, questions []string, echos []bool) (answers []string, err error)
// KeyboardInteractive returns an AuthMethod using a prompt/response
// sequence controlled by the server.
@ -395,7 +471,7 @@ func (cb KeyboardInteractiveChallenge) method() string {
return "keyboard-interactive"
}
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
type initiateMsg struct {
User string `sshtype:"50"`
Service string
@ -412,6 +488,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
return authFailure, nil, err
}
gotMsgExtInfo := false
for {
packet, err := c.readPacket()
if err != nil {
@ -425,6 +502,13 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
return authFailure, nil, err
}
continue
case msgExtInfo:
// Ignore post-authentication RFC 8308 extensions, once.
if gotMsgExtInfo {
return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
}
gotMsgExtInfo = true
continue
case msgUserAuthInfoRequest:
// OK
case msgUserAuthFailure:
@ -465,7 +549,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
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.Name, msg.Instruction, prompts, echos)
if err != nil {
return authFailure, nil, err
}
@ -497,9 +581,9 @@ type retryableAuthMethod struct {
maxTries int
}
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (ok authResult, methods []string, err error) {
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, extensions)
if ok != authFailure || err != nil { // either success, partial success or error terminate
return ok, methods, err
}
@ -542,7 +626,7 @@ type gssAPIWithMICCallback struct {
target string
}
func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
m := &userAuthRequestMsg{
User: user,
Service: serviceSSH,

View File

@ -44,11 +44,11 @@ var preferredCiphers = []string{
// supportedKexAlgos specifies the supported key-exchange algorithms in
// preference order.
var supportedKexAlgos = []string{
kexAlgoCurve25519SHA256,
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
// P384 and P521 are not constant-time yet, but since we don't
// reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
kexAlgoDH14SHA256, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
}
// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden
@ -61,18 +61,20 @@ var serverForbiddenKexAlgos = map[string]struct{}{
// preferredKexAlgos specifies the default preference for key-exchange algorithms
// in preference order.
var preferredKexAlgos = []string{
kexAlgoCurve25519SHA256,
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA1,
kexAlgoDH14SHA256, kexAlgoDH14SHA1,
}
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
// of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{
CertAlgoRSASHA512v01, CertAlgoRSASHA256v01,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSASHA512, KeyAlgoRSASHA256,
KeyAlgoRSA, KeyAlgoDSA,
KeyAlgoED25519,
@ -87,19 +89,33 @@ var supportedMACs = []string{
var supportedCompressions = []string{compressionNone}
// hashFuncs keeps the mapping of supported algorithms to their respective
// hashes needed for signature verification.
// hashFuncs keeps the mapping of supported signature algorithms to their
// respective hashes needed for signing and verification.
var hashFuncs = map[string]crypto.Hash{
KeyAlgoRSA: crypto.SHA1,
KeyAlgoDSA: crypto.SHA1,
KeyAlgoECDSA256: crypto.SHA256,
KeyAlgoECDSA384: crypto.SHA384,
KeyAlgoECDSA521: crypto.SHA512,
CertAlgoRSAv01: crypto.SHA1,
CertAlgoDSAv01: crypto.SHA1,
CertAlgoECDSA256v01: crypto.SHA256,
CertAlgoECDSA384v01: crypto.SHA384,
CertAlgoECDSA521v01: crypto.SHA512,
KeyAlgoRSA: crypto.SHA1,
KeyAlgoRSASHA256: crypto.SHA256,
KeyAlgoRSASHA512: crypto.SHA512,
KeyAlgoDSA: crypto.SHA1,
KeyAlgoECDSA256: crypto.SHA256,
KeyAlgoECDSA384: crypto.SHA384,
KeyAlgoECDSA521: crypto.SHA512,
// KeyAlgoED25519 doesn't pre-hash.
KeyAlgoSKECDSA256: crypto.SHA256,
KeyAlgoSKED25519: crypto.SHA256,
}
// algorithmsForKeyFormat returns the supported signature algorithms for a given
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
// Section 2. See also the note in sendKexInit on backwards compatibility.
func algorithmsForKeyFormat(keyFormat string) []string {
switch keyFormat {
case KeyAlgoRSA:
return []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA}
case CertAlgoRSAv01:
return []string{CertAlgoRSASHA256v01, CertAlgoRSASHA512v01, CertAlgoRSAv01}
default:
return []string{keyFormat}
}
}
// unexpectedMessageError results when the SSH message that we received didn't
@ -146,6 +162,11 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
return 1 << 30
}
var aeadCiphers = map[string]bool{
gcmCipherID: true,
chacha20Poly1305ID: true,
}
type algorithms struct {
kex string
hostKey string
@ -181,14 +202,18 @@ func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMs
return
}
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if err != nil {
return
if !aeadCiphers[ctos.Cipher] {
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if err != nil {
return
}
}
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if err != nil {
return
if !aeadCiphers[stoc.Cipher] {
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if err != nil {
return
}
}
ctos.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
@ -272,8 +297,9 @@ func (c *Config) SetDefaults() {
}
// buildDataSignedForAuth returns the data that is signed in order to prove
// possession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
// possession of a private key. See RFC 4252, section 7. algo is the advertised
// algorithm, and may be a certificate type.
func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo string, pubKey []byte) []byte {
data := struct {
Session []byte
Type byte
@ -281,7 +307,7 @@ func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubK
Service string
Method string
Sign bool
Algo []byte
Algo string
PubKey []byte
}{
sessionID,

View File

@ -12,8 +12,9 @@ the multiplexed nature of SSH is exposed to users that wish to support
others.
References:
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
This package does not fall under the stability promise of the Go language itself,
so its API may be changed when pressing needs arise.

View File

@ -455,14 +455,38 @@ func (t *handshakeTransport) sendKexInit() error {
}
io.ReadFull(rand.Reader, msg.Cookie[:])
if len(t.hostKeys) > 0 {
isServer := len(t.hostKeys) > 0
if isServer {
for _, k := range t.hostKeys {
msg.ServerHostKeyAlgos = append(
msg.ServerHostKeyAlgos, k.PublicKey().Type())
// If k is an AlgorithmSigner, presume it supports all signature algorithms
// associated with the key format. (Ideally AlgorithmSigner would have a
// method to advertise supported algorithms, but it doesn't. This means that
// adding support for a new algorithm is a breaking change, as we will
// immediately negotiate it even if existing implementations don't support
// it. If that ever happens, we'll have to figure something out.)
// If k is not an AlgorithmSigner, we can only assume it only supports the
// algorithms that matches the key format. (This means that Sign can't pick
// a different default.)
keyFormat := k.PublicKey().Type()
if _, ok := k.(AlgorithmSigner); ok {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...)
} else {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
}
}
} else {
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
// As a client we opt in to receiving SSH_MSG_EXT_INFO so we know what
// algorithms the server supports for public key authentication. See RFC
// 8308, Section 2.1.
if firstKeyExchange := t.sessionID == nil; firstKeyExchange {
msg.KexAlgos = make([]string, 0, len(t.config.KeyExchanges)+1)
msg.KexAlgos = append(msg.KexAlgos, t.config.KeyExchanges...)
msg.KexAlgos = append(msg.KexAlgos, "ext-info-c")
}
}
packet := Marshal(msg)
// writePacket destroys the contents, so save a copy.
@ -582,9 +606,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
var result *kexResult
if len(t.hostKeys) > 0 {
result, err = t.server(kex, t.algorithms, &magics)
result, err = t.server(kex, &magics)
} else {
result, err = t.client(kex, t.algorithms, &magics)
result, err = t.client(kex, &magics)
}
if err != nil {
@ -611,19 +635,52 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
return nil
}
func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
var hostKey Signer
for _, k := range t.hostKeys {
if algs.hostKey == k.PublicKey().Type() {
hostKey = k
// algorithmSignerWrapper is an AlgorithmSigner that only supports the default
// key format algorithm.
//
// This is technically a violation of the AlgorithmSigner interface, but it
// should be unreachable given where we use this. Anyway, at least it returns an
// error instead of panicing or producing an incorrect signature.
type algorithmSignerWrapper struct {
Signer
}
func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
if algorithm != underlyingAlgo(a.PublicKey().Type()) {
return nil, errors.New("ssh: internal error: algorithmSignerWrapper invoked with non-default algorithm")
}
return a.Sign(rand, data)
}
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
for _, k := range hostKeys {
if algo == k.PublicKey().Type() {
return algorithmSignerWrapper{k}
}
k, ok := k.(AlgorithmSigner)
if !ok {
continue
}
for _, a := range algorithmsForKeyFormat(k.PublicKey().Type()) {
if algo == a {
return k
}
}
}
return nil
}
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey)
func (t *handshakeTransport) server(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
hostKey := pickHostKey(t.hostKeys, t.algorithms.hostKey)
if hostKey == nil {
return nil, errors.New("ssh: internal error: negotiated unsupported signature type")
}
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.hostKey)
return r, err
}
func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
func (t *handshakeTransport) client(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
result, err := kex.Client(t.conn, t.config.Rand, magics)
if err != nil {
return nil, err
@ -634,7 +691,7 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
return nil, err
}
if err := verifyHostKeySignature(hostKey, result); err != nil {
if err := verifyHostKeySignature(hostKey, t.algorithms.hostKey, result); err != nil {
return nil, err
}

186
vendor/golang.org/x/crypto/ssh/kex.go generated vendored
View File

@ -20,12 +20,14 @@ import (
)
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256LibSSH = "curve25519-sha256@libssh.org"
kexAlgoCurve25519SHA256 = "curve25519-sha256"
// For the following kex only the client half contains a production
// ready implementation. The server half only consists of a minimal
@ -75,8 +77,9 @@ func (m *handshakeMagics) write(w io.Writer) {
// kexAlgorithm abstracts different key exchange algorithms.
type kexAlgorithm interface {
// Server runs server-side key agreement, signing the result
// with a hostkey.
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
// with a hostkey. algo is the negotiated algorithm, and may
// be a certificate type.
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s AlgorithmSigner, algo string) (*kexResult, error)
// Client runs the client-side key agreement. Caller is
// responsible for verifying the host key signature.
@ -86,6 +89,7 @@ type kexAlgorithm interface {
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
type dhGroup struct {
g, p, pMinus1 *big.Int
hashFunc crypto.Hash
}
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
@ -96,8 +100,6 @@ func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int,
}
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
hashFunc := crypto.SHA1
var x *big.Int
for {
var err error
@ -132,7 +134,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
return nil, err
}
h := hashFunc.New()
h := group.hashFunc.New()
magics.write(h)
writeString(h, kexDHReply.HostKey)
writeInt(h, X)
@ -146,12 +148,11 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
K: K,
HostKey: kexDHReply.HostKey,
Signature: kexDHReply.Signature,
Hash: crypto.SHA1,
Hash: group.hashFunc,
}, nil
}
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
hashFunc := crypto.SHA1
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) {
packet, err := c.readPacket()
if err != nil {
return
@ -179,7 +180,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
hostKeyBytes := priv.PublicKey().Marshal()
h := hashFunc.New()
h := group.hashFunc.New()
magics.write(h)
writeString(h, hostKeyBytes)
writeInt(h, kexDHInit.X)
@ -193,7 +194,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, randSource, H)
sig, err := signAndMarshal(priv, randSource, H, algo)
if err != nil {
return nil, err
}
@ -211,7 +212,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
K: K,
HostKey: hostKeyBytes,
Signature: sig,
Hash: crypto.SHA1,
Hash: group.hashFunc,
}, err
}
@ -314,7 +315,7 @@ func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
return true
}
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) {
packet, err := c.readPacket()
if err != nil {
return nil, err
@ -359,7 +360,7 @@ func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, p
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, rand, H)
sig, err := signAndMarshal(priv, rand, H, algo)
if err != nil {
return nil, err
}
@ -384,39 +385,62 @@ func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, p
}, nil
}
// ecHash returns the hash to match the given elliptic curve, see RFC
// 5656, section 6.2.1
func ecHash(curve elliptic.Curve) crypto.Hash {
bitSize := curve.Params().BitSize
switch {
case bitSize <= 256:
return crypto.SHA256
case bitSize <= 384:
return crypto.SHA384
}
return crypto.SHA512
}
var kexAlgoMap = map[string]kexAlgorithm{}
func init() {
// This is the group called diffie-hellman-group1-sha1 in RFC
// 4253 and Oakley Group 2 in RFC 2409.
// This is the group called diffie-hellman-group1-sha1 in
// RFC 4253 and Oakley Group 2 in RFC 2409.
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
pMinus1: new(big.Int).Sub(p, bigOne),
hashFunc: crypto.SHA1,
}
// This are the groups called diffie-hellman-group14-sha1 and
// diffie-hellman-group14-sha256 in RFC 4253 and RFC 8268,
// and Oakley Group 14 in RFC 3526.
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
group14 := &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
pMinus1: new(big.Int).Sub(p, bigOne),
}
// This is the group called diffie-hellman-group14-sha1 in RFC
// 4253 and Oakley Group 14 in RFC 3526.
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
pMinus1: new(big.Int).Sub(p, bigOne),
g: group14.g, p: group14.p, pMinus1: group14.pMinus1,
hashFunc: crypto.SHA1,
}
kexAlgoMap[kexAlgoDH14SHA256] = &dhGroup{
g: group14.g, p: group14.p, pMinus1: group14.pMinus1,
hashFunc: crypto.SHA256,
}
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
kexAlgoMap[kexAlgoCurve25519SHA256LibSSH] = &curve25519sha256{}
kexAlgoMap[kexAlgoDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
kexAlgoMap[kexAlgoDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256}
}
// curve25519sha256 implements the curve25519-sha256@libssh.org key
// agreement protocol, as described in
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
// curve25519sha256 implements the curve25519-sha256 (formerly known as
// curve25519-sha256@libssh.org) key exchange method, as described in RFC 8731.
type curve25519sha256 struct{}
type curve25519KeyPair struct {
@ -486,7 +510,7 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
}, nil
}
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) {
packet, err := c.readPacket()
if err != nil {
return
@ -527,7 +551,7 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
H := h.Sum(nil)
sig, err := signAndMarshal(priv, rand, H)
sig, err := signAndMarshal(priv, rand, H, algo)
if err != nil {
return nil, err
}
@ -553,7 +577,6 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
// diffie-hellman-group-exchange-sha256 key agreement protocols,
// as described in RFC 4419
type dhGEXSHA struct {
g, p *big.Int
hashFunc crypto.Hash
}
@ -563,14 +586,7 @@ const (
dhGroupExchangeMaximumBits = 8192
)
func (gex *dhGEXSHA) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
if theirPublic.Sign() <= 0 || theirPublic.Cmp(gex.p) >= 0 {
return nil, fmt.Errorf("ssh: DH parameter out of bounds")
}
return new(big.Int).Exp(theirPublic, myPrivate, gex.p), nil
}
func (gex dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
// Send GexRequest
kexDHGexRequest := kexDHGexRequestMsg{
MinBits: dhGroupExchangeMinimumBits,
@ -587,35 +603,29 @@ func (gex dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshake
return nil, err
}
var kexDHGexGroup kexDHGexGroupMsg
if err = Unmarshal(packet, &kexDHGexGroup); err != nil {
var msg kexDHGexGroupMsg
if err = Unmarshal(packet, &msg); err != nil {
return nil, err
}
// reject if p's bit length < dhGroupExchangeMinimumBits or > dhGroupExchangeMaximumBits
if kexDHGexGroup.P.BitLen() < dhGroupExchangeMinimumBits || kexDHGexGroup.P.BitLen() > dhGroupExchangeMaximumBits {
return nil, fmt.Errorf("ssh: server-generated gex p is out of range (%d bits)", kexDHGexGroup.P.BitLen())
if msg.P.BitLen() < dhGroupExchangeMinimumBits || msg.P.BitLen() > dhGroupExchangeMaximumBits {
return nil, fmt.Errorf("ssh: server-generated gex p is out of range (%d bits)", msg.P.BitLen())
}
gex.p = kexDHGexGroup.P
gex.g = kexDHGexGroup.G
// Check if g is safe by verifing that g > 1 and g < p - 1
one := big.NewInt(1)
var pMinusOne = &big.Int{}
pMinusOne.Sub(gex.p, one)
if gex.g.Cmp(one) != 1 && gex.g.Cmp(pMinusOne) != -1 {
// Check if g is safe by verifying that 1 < g < p-1
pMinusOne := new(big.Int).Sub(msg.P, bigOne)
if msg.G.Cmp(bigOne) <= 0 || msg.G.Cmp(pMinusOne) >= 0 {
return nil, fmt.Errorf("ssh: server provided gex g is not safe")
}
// Send GexInit
var pHalf = &big.Int{}
pHalf.Rsh(gex.p, 1)
pHalf := new(big.Int).Rsh(msg.P, 1)
x, err := rand.Int(randSource, pHalf)
if err != nil {
return nil, err
}
X := new(big.Int).Exp(gex.g, x, gex.p)
X := new(big.Int).Exp(msg.G, x, msg.P)
kexDHGexInit := kexDHGexInitMsg{
X: X,
}
@ -634,13 +644,13 @@ func (gex dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshake
return nil, err
}
kInt, err := gex.diffieHellman(kexDHGexReply.Y, x)
if err != nil {
return nil, err
if kexDHGexReply.Y.Cmp(bigOne) <= 0 || kexDHGexReply.Y.Cmp(pMinusOne) >= 0 {
return nil, errors.New("ssh: DH parameter out of bounds")
}
kInt := new(big.Int).Exp(kexDHGexReply.Y, x, msg.P)
// Check if k is safe by verifing that k > 1 and k < p - 1
if kInt.Cmp(one) != 1 && kInt.Cmp(pMinusOne) != -1 {
// Check if k is safe by verifying that k > 1 and k < p - 1
if kInt.Cmp(bigOne) <= 0 || kInt.Cmp(pMinusOne) >= 0 {
return nil, fmt.Errorf("ssh: derived k is not safe")
}
@ -650,8 +660,8 @@ func (gex dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshake
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits))
writeInt(h, gex.p)
writeInt(h, gex.g)
writeInt(h, msg.P)
writeInt(h, msg.G)
writeInt(h, X)
writeInt(h, kexDHGexReply.Y)
K := make([]byte, intLength(kInt))
@ -670,7 +680,7 @@ func (gex dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshake
// Server half implementation of the Diffie Hellman Key Exchange with SHA1 and SHA256.
//
// This is a minimal implementation to satisfy the automated tests.
func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) {
// Receive GexRequest
packet, err := c.readPacket()
if err != nil {
@ -681,35 +691,17 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
return
}
// smoosh the user's preferred size into our own limits
if kexDHGexRequest.PreferedBits > dhGroupExchangeMaximumBits {
kexDHGexRequest.PreferedBits = dhGroupExchangeMaximumBits
}
if kexDHGexRequest.PreferedBits < dhGroupExchangeMinimumBits {
kexDHGexRequest.PreferedBits = dhGroupExchangeMinimumBits
}
// fix min/max if they're inconsistent. technically, we could just pout
// and hang up, but there's no harm in giving them the benefit of the
// doubt and just picking a bitsize for them.
if kexDHGexRequest.MinBits > kexDHGexRequest.PreferedBits {
kexDHGexRequest.MinBits = kexDHGexRequest.PreferedBits
}
if kexDHGexRequest.MaxBits < kexDHGexRequest.PreferedBits {
kexDHGexRequest.MaxBits = kexDHGexRequest.PreferedBits
}
// Send GexGroup
// This is the group called diffie-hellman-group14-sha1 in RFC
// 4253 and Oakley Group 14 in RFC 3526.
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
gex.p = p
gex.g = big.NewInt(2)
g := big.NewInt(2)
kexDHGexGroup := kexDHGexGroupMsg{
P: gex.p,
G: gex.g,
msg := &kexDHGexGroupMsg{
P: p,
G: g,
}
if err := c.writePacket(Marshal(&kexDHGexGroup)); err != nil {
if err := c.writePacket(Marshal(msg)); err != nil {
return nil, err
}
@ -723,19 +715,19 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
return
}
var pHalf = &big.Int{}
pHalf.Rsh(gex.p, 1)
pHalf := new(big.Int).Rsh(p, 1)
y, err := rand.Int(randSource, pHalf)
if err != nil {
return
}
Y := new(big.Int).Exp(g, y, p)
Y := new(big.Int).Exp(gex.g, y, gex.p)
kInt, err := gex.diffieHellman(kexDHGexInit.X, y)
if err != nil {
return nil, err
pMinusOne := new(big.Int).Sub(p, bigOne)
if kexDHGexInit.X.Cmp(bigOne) <= 0 || kexDHGexInit.X.Cmp(pMinusOne) >= 0 {
return nil, errors.New("ssh: DH parameter out of bounds")
}
kInt := new(big.Int).Exp(kexDHGexInit.X, y, p)
hostKeyBytes := priv.PublicKey().Marshal()
@ -745,8 +737,8 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits))
writeInt(h, gex.p)
writeInt(h, gex.g)
writeInt(h, p)
writeInt(h, g)
writeInt(h, kexDHGexInit.X)
writeInt(h, Y)
@ -758,7 +750,7 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, randSource, H)
sig, err := signAndMarshal(priv, randSource, H, algo)
if err != nil {
return nil, err
}

View File

@ -30,8 +30,9 @@ import (
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
)
// These constants represent the algorithm names for key types supported by this
// package.
// Public key algorithms names. These values can appear in PublicKey.Type,
// ClientConfig.HostKeyAlgorithms, Signature.Format, or as AlgorithmSigner
// arguments.
const (
KeyAlgoRSA = "ssh-rsa"
KeyAlgoDSA = "ssh-dss"
@ -41,16 +42,21 @@ const (
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
KeyAlgoED25519 = "ssh-ed25519"
KeyAlgoSKED25519 = "sk-ssh-ed25519@openssh.com"
// KeyAlgoRSASHA256 and KeyAlgoRSASHA512 are only public key algorithms, not
// public key formats, so they can't appear as a PublicKey.Type. The
// corresponding PublicKey.Type is KeyAlgoRSA. See RFC 8332, Section 2.
KeyAlgoRSASHA256 = "rsa-sha2-256"
KeyAlgoRSASHA512 = "rsa-sha2-512"
)
// These constants represent non-default signature algorithms that are supported
// as algorithm parameters to AlgorithmSigner.SignWithAlgorithm methods. See
// [PROTOCOL.agent] section 4.5.1 and
// https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-10
const (
SigAlgoRSA = "ssh-rsa"
SigAlgoRSASHA2256 = "rsa-sha2-256"
SigAlgoRSASHA2512 = "rsa-sha2-512"
// Deprecated: use KeyAlgoRSA.
SigAlgoRSA = KeyAlgoRSA
// Deprecated: use KeyAlgoRSASHA256.
SigAlgoRSASHA2256 = KeyAlgoRSASHA256
// Deprecated: use KeyAlgoRSASHA512.
SigAlgoRSASHA2512 = KeyAlgoRSASHA512
)
// parsePubKey parses a public key of the given algorithm.
@ -70,7 +76,7 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
case KeyAlgoSKED25519:
return parseSKEd25519(in)
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
cert, err := parseCert(in, certToPrivAlgo(algo))
cert, err := parseCert(in, certKeyAlgoNames[algo])
if err != nil {
return nil, nil, err
}
@ -289,18 +295,21 @@ func MarshalAuthorizedKey(key PublicKey) []byte {
return b.Bytes()
}
// PublicKey is an abstraction of different types of public keys.
// PublicKey represents a public key using an unspecified algorithm.
//
// Some PublicKeys provided by this package also implement CryptoPublicKey.
type PublicKey interface {
// Type returns the key's type, e.g. "ssh-rsa".
// Type returns the key format name, e.g. "ssh-rsa".
Type() string
// Marshal returns the serialized key data in SSH wire format,
// with the name prefix. To unmarshal the returned data, use
// the ParsePublicKey function.
// Marshal returns the serialized key data in SSH wire format, with the name
// prefix. To unmarshal the returned data, use the ParsePublicKey function.
Marshal() []byte
// Verify that sig is a signature on the given data using this
// key. This function will hash the data appropriately first.
// Verify that sig is a signature on the given data using this key. This
// method will hash the data appropriately first. sig.Format is allowed to
// be any signature algorithm compatible with the key type, the caller
// should check if it has more stringent requirements.
Verify(data []byte, sig *Signature) error
}
@ -311,25 +320,32 @@ type CryptoPublicKey interface {
}
// A Signer can create signatures that verify against a public key.
//
// Some Signers provided by this package also implement AlgorithmSigner.
type Signer interface {
// PublicKey returns an associated PublicKey instance.
// PublicKey returns the associated PublicKey.
PublicKey() PublicKey
// Sign returns raw signature for the given data. This method
// will apply the hash specified for the keytype to the data.
// Sign returns a signature for the given data. This method will hash the
// data appropriately first. The signature algorithm is expected to match
// the key format returned by the PublicKey.Type method (and not to be any
// alternative algorithm supported by the key format).
Sign(rand io.Reader, data []byte) (*Signature, error)
}
// A AlgorithmSigner is a Signer that also supports specifying a specific
// algorithm to use for signing.
// An AlgorithmSigner is a Signer that also supports specifying an algorithm to
// use for signing.
//
// An AlgorithmSigner can't advertise the algorithms it supports, so it should
// be prepared to be invoked with every algorithm supported by the public key
// format.
type AlgorithmSigner interface {
Signer
// SignWithAlgorithm is like Signer.Sign, but allows specification of a
// non-default signing algorithm. See the SigAlgo* constants in this
// package for signature algorithms supported by this package. Callers may
// pass an empty string for the algorithm in which case the AlgorithmSigner
// will use its default algorithm.
// SignWithAlgorithm is like Signer.Sign, but allows specifying a desired
// signing algorithm. Callers may pass an empty string for the algorithm in
// which case the AlgorithmSigner will use a default algorithm. This default
// doesn't currently control any behavior in this package.
SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error)
}
@ -381,17 +397,11 @@ func (r *rsaPublicKey) Marshal() []byte {
}
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
var hash crypto.Hash
switch sig.Format {
case SigAlgoRSA:
hash = crypto.SHA1
case SigAlgoRSASHA2256:
hash = crypto.SHA256
case SigAlgoRSASHA2512:
hash = crypto.SHA512
default:
supportedAlgos := algorithmsForKeyFormat(r.Type())
if !contains(supportedAlgos, sig.Format) {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
}
hash := hashFuncs[sig.Format]
h := hash.New()
h.Write(data)
digest := h.Sum(nil)
@ -466,7 +476,7 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
if sig.Format != k.Type() {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
}
h := crypto.SHA1.New()
h := hashFuncs[sig.Format].New()
h.Write(data)
digest := h.Sum(nil)
@ -499,7 +509,7 @@ func (k *dsaPrivateKey) PublicKey() PublicKey {
}
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
return k.SignWithAlgorithm(rand, data, "")
return k.SignWithAlgorithm(rand, data, k.PublicKey().Type())
}
func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
@ -507,7 +517,7 @@ func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
}
h := crypto.SHA1.New()
h := hashFuncs[k.PublicKey().Type()].New()
h.Write(data)
digest := h.Sum(nil)
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
@ -603,19 +613,6 @@ func supportedEllipticCurve(curve elliptic.Curve) bool {
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
}
// ecHash returns the hash to match the given elliptic curve, see RFC
// 5656, section 6.2.1
func ecHash(curve elliptic.Curve) crypto.Hash {
bitSize := curve.Params().BitSize
switch {
case bitSize <= 256:
return crypto.SHA256
case bitSize <= 384:
return crypto.SHA384
}
return crypto.SHA512
}
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
var w struct {
@ -671,7 +668,7 @@ func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
}
h := ecHash(k.Curve).New()
h := hashFuncs[sig.Format].New()
h.Write(data)
digest := h.Sum(nil)
@ -775,7 +772,7 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
}
h := ecHash(k.Curve).New()
h := hashFuncs[sig.Format].New()
h.Write([]byte(k.application))
appDigest := h.Sum(nil)
@ -874,7 +871,7 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
}
h := sha256.New()
h := hashFuncs[sig.Format].New()
h.Write([]byte(k.application))
appDigest := h.Sum(nil)
@ -961,44 +958,20 @@ func (s *wrappedSigner) PublicKey() PublicKey {
}
func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
return s.SignWithAlgorithm(rand, data, "")
return s.SignWithAlgorithm(rand, data, s.pubKey.Type())
}
func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
var hashFunc crypto.Hash
if _, ok := s.pubKey.(*rsaPublicKey); ok {
// RSA keys support a few hash functions determined by the requested signature algorithm
switch algorithm {
case "", SigAlgoRSA:
algorithm = SigAlgoRSA
hashFunc = crypto.SHA1
case SigAlgoRSASHA2256:
hashFunc = crypto.SHA256
case SigAlgoRSASHA2512:
hashFunc = crypto.SHA512
default:
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
}
} else {
// The only supported algorithm for all other key types is the same as the type of the key
if algorithm == "" {
algorithm = s.pubKey.Type()
} else if algorithm != s.pubKey.Type() {
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
}
switch key := s.pubKey.(type) {
case *dsaPublicKey:
hashFunc = crypto.SHA1
case *ecdsaPublicKey:
hashFunc = ecHash(key.Curve)
case ed25519PublicKey:
default:
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
}
if algorithm == "" {
algorithm = s.pubKey.Type()
}
supportedAlgos := algorithmsForKeyFormat(s.pubKey.Type())
if !contains(supportedAlgos, algorithm) {
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
}
hashFunc := hashFuncs[algorithm]
var digest []byte
if hashFunc != 0 {
h := hashFunc.New()

View File

@ -141,6 +141,14 @@ type serviceAcceptMsg struct {
Service string `sshtype:"6"`
}
// See RFC 8308, section 2.3
const msgExtInfo = 7
type extInfoMsg struct {
NumExtensions uint32 `sshtype:"7"`
Payload []byte `ssh:"rest"`
}
// See RFC 4252, section 5.
const msgUserAuthRequest = 50
@ -180,11 +188,11 @@ const msgUserAuthInfoRequest = 60
const msgUserAuthInfoResponse = 61
type userAuthInfoRequestMsg struct {
User string `sshtype:"60"`
Instruction string
DeprecatedLanguage string
NumPrompts uint32
Prompts []byte `ssh:"rest"`
Name string `sshtype:"60"`
Instruction string
Language string
NumPrompts uint32
Prompts []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
@ -782,6 +790,8 @@ func decode(packet []byte) (interface{}, error) {
msg = new(serviceRequestMsg)
case msgServiceAccept:
msg = new(serviceAcceptMsg)
case msgExtInfo:
msg = new(extInfoMsg)
case msgKexInit:
msg = new(kexInitMsg)
case msgKexDHInit:
@ -843,6 +853,7 @@ var packetTypeNames = map[byte]string{
msgDisconnect: "disconnectMsg",
msgServiceRequest: "serviceRequestMsg",
msgServiceAccept: "serviceAcceptMsg",
msgExtInfo: "extInfoMsg",
msgKexInit: "kexInitMsg",
msgKexDHInit: "kexDHInitMsg",
msgKexDHReply: "kexDHReplyMsg",

View File

@ -120,7 +120,7 @@ type ServerConfig struct {
}
// AddHostKey adds a private key as a host key. If an existing host
// key exists with the same algorithm, it is overwritten. Each server
// key exists with the same public key format, it is replaced. Each server
// config must have at least one host key.
func (s *ServerConfig) AddHostKey(key Signer) {
for i, k := range s.hostKeys {
@ -212,9 +212,10 @@ func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewCha
}
// signAndMarshal signs the data with the appropriate algorithm,
// and serializes the result in SSH wire format.
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
sig, err := k.Sign(rand, data)
// and serializes the result in SSH wire format. algo is the negotiate
// algorithm and may be a certificate type.
func signAndMarshal(k AlgorithmSigner, rand io.Reader, data []byte, algo string) ([]byte, error) {
sig, err := k.SignWithAlgorithm(rand, data, underlyingAlgo(algo))
if err != nil {
return nil, err
}
@ -284,7 +285,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
func isAcceptableAlgo(algo string) bool {
switch algo {
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
case KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
return true
}
@ -553,6 +554,7 @@ userAuthLoop:
if !ok || len(payload) > 0 {
return nil, parseError(msgUserAuthRequest)
}
// Ensure the public key algo and signature algo
// are supported. Compare the private key
// algorithm name that corresponds to algo with
@ -562,7 +564,12 @@ userAuthLoop:
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
break
}
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData)
if underlyingAlgo(algo) != sig.Format {
authErr = fmt.Errorf("ssh: signature %q not compatible with selected algorithm %q", sig.Format, algo)
break
}
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
if err := pubKey.Verify(signedData, sig); err != nil {
return nil, err
@ -633,6 +640,30 @@ userAuthLoop:
}
authFailures++
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
// If we have hit the max attempts, don't bother sending the
// final SSH_MSG_USERAUTH_FAILURE message, since there are
// no more authentication methods which can be attempted,
// and this message may cause the client to re-attempt
// authentication while we send the disconnect message.
// Continue, and trigger the disconnect at the start of
// the loop.
//
// The SSH specification is somewhat confusing about this,
// RFC 4252 Section 5.1 requires each authentication failure
// be responded to with a respective SSH_MSG_USERAUTH_FAILURE
// message, but Section 4 says the server should disconnect
// after some number of attempts, but it isn't explicit which
// message should take precedence (i.e. should there be a failure
// message than a disconnect message, or if we are going to
// disconnect, should we only send that message.)
//
// Either way, OpenSSH disconnects immediately after the last
// failed authnetication attempt, and given they are typically
// considered the golden implementation it seems reasonable
// to match that behavior.
continue
}
var failureMsg userAuthFailureMsg
if config.PasswordCallback != nil {
@ -670,7 +701,7 @@ type sshClientKeyboardInteractive struct {
*connection
}
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
func (c *sshClientKeyboardInteractive) Challenge(name, instruction string, questions []string, echos []bool) (answers []string, err error) {
if len(questions) != len(echos) {
return nil, errors.New("ssh: echos and questions must have equal length")
}
@ -682,6 +713,7 @@ func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, quest
}
if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
Name: name,
Instruction: instruction,
NumPrompts: uint32(len(questions)),
Prompts: prompts,

View File

@ -85,6 +85,7 @@ const (
IXANY = 39
IXOFF = 40
IMAXBEL = 41
IUTF8 = 42 // RFC 8160
ISIG = 50
ICANON = 51
XCASE = 52

View File

@ -238,15 +238,19 @@ var (
// (to setup server->client keys) or clientKeys (for client->server keys).
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
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)
var macKey []byte
if !aeadCiphers[algs.Cipher] {
macMode := macModes[algs.MAC]
macKey = make([]byte, macMode.keySize)
generateKeyMaterial(macKey, d.macKeyTag, kex)
}
return cipherModes[algs.Cipher].create(key, iv, macKey, algs)
}

3
vendor/modules.txt vendored
View File

@ -384,7 +384,7 @@ go.opentelemetry.io/proto/otlp/trace/v1
go.uber.org/automaxprocs/internal/cgroups
go.uber.org/automaxprocs/internal/runtime
go.uber.org/automaxprocs/maxprocs
# golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
# golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
## explicit; go 1.17
golang.org/x/crypto/blake2b
golang.org/x/crypto/blowfish
@ -395,7 +395,6 @@ golang.org/x/crypto/cryptobyte/asn1
golang.org/x/crypto/curve25519
golang.org/x/crypto/curve25519/internal/field
golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519
golang.org/x/crypto/hkdf
golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/internal/subtle