Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce SHA-512 for RSA SSH signatures #3777

Merged
merged 9 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,10 @@ buildbox:
# proto generates GRPC defs from service definitions
.PHONY: grpc
grpc: buildbox
docker run -v $(shell pwd):/go/src/github.com/gravitational/teleport $(BUILDBOX_TAG) make -C /go/src/github.com/gravitational/teleport buildbox-grpc
docker run \
--rm \
-v $(shell pwd):/go/src/github.com/gravitational/teleport $(BUILDBOX_TAG) \
make -C /go/src/github.com/gravitational/teleport buildbox-grpc

# proto generates GRPC stuff inside buildbox
.PHONY: buildbox-grpc
Expand Down
19 changes: 17 additions & 2 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func NewInstance(cfg InstanceConfig) *TeleInstance {

cert, err := keygen.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: cfg.Priv,
CASigningAlg: defaults.CASignatureAlgorithm,
PublicHostKey: cfg.Pub,
HostID: cfg.HostID,
NodeName: cfg.NodeName,
Expand Down Expand Up @@ -255,11 +256,25 @@ func (s *InstanceSecrets) GetRoles() []services.Role {
// case we always return hard-coded userCA + hostCA (and they share keys
// for simplicity)
func (s *InstanceSecrets) GetCAs() []services.CertAuthority {
hostCA := services.NewCertAuthority(services.HostCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{})
hostCA := services.NewCertAuthority(
services.HostCA,
s.SiteName,
[][]byte{s.PrivKey},
[][]byte{s.PubKey},
[]string{},
services.CertAuthoritySpecV2_RSA_SHA2_512,
)
hostCA.SetTLSKeyPairs([]services.TLSKeyPair{{Cert: s.TLSCACert, Key: s.PrivKey}})
return []services.CertAuthority{
hostCA,
services.NewCertAuthority(services.UserCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{services.RoleNameForCertAuthority(s.SiteName)}),
services.NewCertAuthority(
services.UserCA,
s.SiteName,
[][]byte{s.PrivKey},
[][]byte{s.PubKey},
[]string{services.RoleNameForCertAuthority(s.SiteName)},
services.CertAuthoritySpecV2_RSA_SHA2_512,
),
}
}

Expand Down
147 changes: 147 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3442,6 +3442,153 @@ func (s *IntSuite) TestRotateTrustedClusters(c *check.C) {
}
}

// TestRotateChangeSigningAlg tests the change of CA signing algorithm on
// manual rotation.
func (s *IntSuite) TestRotateChangeSigningAlg(c *check.C) {
// Start with an instance using default signing alg.
tconf := rotationConfig(true)
t := NewInstance(InstanceConfig{ClusterName: Site, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
logins := []string{s.me.Username}
for _, login := range logins {
t.AddUser(login, []string{login})
}
config, err := t.GenerateConfig(nil, tconf)
c.Assert(err, check.IsNil)

serviceC := make(chan *service.TeleportProcess, 20)
runErrCh := make(chan error, 1)

restart := func(svc *service.TeleportProcess, cancel func()) (*service.TeleportProcess, func()) {
if svc != nil && cancel != nil {
// shut down the service
cancel()
// close the service without waiting for the connections to drain
err := svc.Close()
c.Assert(err, check.IsNil)
err = svc.Wait()
c.Assert(err, check.IsNil)

select {
case err := <-runErrCh:
c.Assert(err, check.IsNil)
case <-time.After(20 * time.Second):
c.Fatalf("failed to shut down the server")
}
}

ctx, cancel := context.WithCancel(context.Background())
go func() {
runErrCh <- service.Run(ctx, *config, func(cfg *service.Config) (service.Process, error) {
svc, err := service.NewTeleport(cfg)
if err == nil {
serviceC <- svc
}
return svc, err
})
}()

svc, err = waitForProcessStart(serviceC)
c.Assert(err, check.IsNil)
return svc, cancel
}

assertSigningAlg := func(svc *service.TeleportProcess, alg string) {
hostCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
c.Assert(err, check.IsNil)
c.Assert(hostCA.GetSigningAlg(), check.Equals, alg)

userCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.UserCA, DomainName: Site}, false)
c.Assert(err, check.IsNil)
c.Assert(userCA.GetSigningAlg(), check.Equals, alg)
}

rotate := func(svc *service.TeleportProcess, mode string) *service.TeleportProcess {
c.Logf("rotation phase: %q", services.RotationPhaseInit)
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
TargetPhase: services.RotationPhaseInit,
Mode: mode,
})
c.Assert(err, check.IsNil)

// wait until service phase update to be broadcasted (init phase does not trigger reload)
err = waitForProcessEvent(svc, service.TeleportPhaseChangeEvent, 10*time.Second)
c.Assert(err, check.IsNil)

c.Logf("rotation phase: %q", services.RotationPhaseUpdateClients)
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
TargetPhase: services.RotationPhaseUpdateClients,
Mode: mode,
})
c.Assert(err, check.IsNil)

// wait until service reload
svc, err = waitForReload(serviceC, svc)
c.Assert(err, check.IsNil)

c.Logf("rotation phase: %q", services.RotationPhaseUpdateServers)
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
TargetPhase: services.RotationPhaseUpdateServers,
Mode: mode,
})
c.Assert(err, check.IsNil)

// wait until service reloaded
svc, err = waitForReload(serviceC, svc)
c.Assert(err, check.IsNil)

c.Logf("rotation phase: %q", services.RotationPhaseStandby)
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
TargetPhase: services.RotationPhaseStandby,
Mode: mode,
})
c.Assert(err, check.IsNil)

// wait until service reloaded
svc, err = waitForReload(serviceC, svc)
c.Assert(err, check.IsNil)

return svc
}

// Start the instance.
svc, cancel := restart(nil, nil)

c.Log("default signature algorithm due to empty config value")
// Verify the default signing algorithm with config value empty.
assertSigningAlg(svc, defaults.CASignatureAlgorithm)

c.Log("change signature algorithm with custom config value and manual rotation")
// Change the signing algorithm in config file.
signingAlg := ssh.SigAlgoRSA
config.CASignatureAlgorithm = &signingAlg
svc, cancel = restart(svc, cancel)
// Do a manual rotation - this should change the signing algorithm.
svc = rotate(svc, services.RotationModeManual)
assertSigningAlg(svc, ssh.SigAlgoRSA)

c.Log("preserve signature algorithm with empty config value and manual rotation")
// Unset the config value.
config.CASignatureAlgorithm = nil
svc, cancel = restart(svc, cancel)

// Do a manual rotation - this should leave the signing algorithm
// unaffected because config value is not set.
svc = rotate(svc, services.RotationModeManual)
assertSigningAlg(svc, ssh.SigAlgoRSA)

// shut down the service
cancel()
// close the service without waiting for the connections to drain
svc.Close()

select {
case err := <-runErrCh:
c.Assert(err, check.IsNil)
case <-time.After(20 * time.Second):
c.Fatalf("failed to shut down the server")
}
}

// rotationConfig sets up default config used for CA rotation tests
func rotationConfig(disableWebService bool) *service.Config {
tconf := service.MakeDefaultConfig()
Expand Down
7 changes: 7 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func NewAuthServer(cfg *InitConfig, opts ...AuthServerOption) (*AuthServer, erro
oidcClients: make(map[string]*oidcClient),
samlProviders: make(map[string]*samlProvider),
githubClients: make(map[string]*githubClient),
caSigningAlg: cfg.CASigningAlg,
cancelFunc: cancelFunc,
closeCtx: closeCtx,
AuthServices: AuthServices{
Expand Down Expand Up @@ -207,6 +208,9 @@ type AuthServer struct {
// cipherSuites is a list of ciphersuites that the auth server supports.
cipherSuites []uint16

// caSigningAlg is an SSH signing algorithm to use when generating new CAs.
caSigningAlg *string

// cache is a fast cache that allows auth server
// to use cache for most frequent operations,
// if not set, cache uses itself
Expand Down Expand Up @@ -370,6 +374,7 @@ func (s *AuthServer) GenerateHostCert(hostPublicKey []byte, hostID, nodeName str
// create and sign!
return s.Authority.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: caPrivateKey,
CASigningAlg: ca.GetSigningAlg(),
PublicHostKey: hostPublicKey,
HostID: hostID,
NodeName: nodeName,
Expand Down Expand Up @@ -507,6 +512,7 @@ func (s *AuthServer) generateUserCert(req certRequest) (*certs, error) {
}
sshCert, err := s.Authority.GenerateUserCert(services.UserCertParams{
PrivateCASigningKey: privateKey,
CASigningAlg: ca.GetSigningAlg(),
PublicUserKey: req.publicKey,
Username: req.user.GetName(),
AllowedLogins: allowedLogins,
Expand Down Expand Up @@ -1013,6 +1019,7 @@ func (s *AuthServer) GenerateServerKeys(req GenerateServerKeysRequest) (*PackedK
// generate hostSSH certificate
hostSSHCert, err := s.Authority.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: caPrivateKey,
CASigningAlg: ca.GetSigningAlg(),
PublicHostKey: pubSSHKey,
HostID: req.HostID,
NodeName: req.NodeName,
Expand Down
16 changes: 16 additions & 0 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ type InitConfig struct {

// CipherSuites is a list of ciphersuites that the auth server supports.
CipherSuites []uint16

// CASigningAlg is a signing algorithm used for SSH (certificate and
// handshake) signatures for both host and user CAs. This option only
// affects newly-created CAs.
CASigningAlg *string
}

// Init instantiates and configures an instance of AuthServer
Expand Down Expand Up @@ -315,6 +320,10 @@ func Init(cfg InitConfig, opts ...AuthServerOption) (*AuthServer, error) {
if err != nil {
return nil, trace.Wrap(err)
}
sigAlg := defaults.CASignatureAlgorithm
if cfg.CASigningAlg != nil && *cfg.CASigningAlg != "" {
sigAlg = *cfg.CASigningAlg
}

userCA := &services.CertAuthorityV2{
Kind: services.KindCertAuthority,
Expand All @@ -327,6 +336,7 @@ func Init(cfg InitConfig, opts ...AuthServerOption) (*AuthServer, error) {
ClusterName: cfg.ClusterName.GetClusterName(),
Type: services.UserCA,
SigningKeys: [][]byte{priv},
SigningAlg: services.ParseSigningAlg(sigAlg),
CheckingKeys: [][]byte{pub},
TLSKeyPairs: []services.TLSKeyPair{{Cert: certPEM, Key: keyPEM}},
},
Expand Down Expand Up @@ -370,6 +380,11 @@ func Init(cfg InitConfig, opts ...AuthServerOption) (*AuthServer, error) {
if err != nil {
return nil, trace.Wrap(err)
}
sigAlg := defaults.CASignatureAlgorithm
if cfg.CASigningAlg != nil && *cfg.CASigningAlg != "" {
sigAlg = *cfg.CASigningAlg
}

hostCA = &services.CertAuthorityV2{
Kind: services.KindCertAuthority,
Version: services.V2,
Expand All @@ -381,6 +396,7 @@ func Init(cfg InitConfig, opts ...AuthServerOption) (*AuthServer, error) {
ClusterName: cfg.ClusterName.GetClusterName(),
Type: services.HostCA,
SigningKeys: [][]byte{priv},
SigningAlg: services.ParseSigningAlg(sigAlg),
CheckingKeys: [][]byte{pub},
TLSKeyPairs: []services.TLSKeyPair{{Cert: certPEM, Key: keyPEM}},
},
Expand Down
Loading