Certificate TTL improvements

1. Server now always uses UTC timestamps for certificates it ussues
2. Client doesn't store cert validBefore time in separate files, it
   parses the cert itself.

Fixes #370
This commit is contained in:
Ev Kontsevoy 2016-04-19 16:05:28 -07:00
parent b349ea9340
commit e28f21922c
8 changed files with 47 additions and 100 deletions

View file

@ -230,10 +230,9 @@ func (this *TeleInstance) Create(trustedSecrets []*InstanceSecrets, enableSSH bo
return err
}
user.Key = &client.Key{
Priv: priv,
Pub: pub,
Cert: cert,
Deadline: time.Now().Add(ttl),
Priv: priv,
Pub: pub,
Cert: cert,
}
}
return nil

View file

@ -143,7 +143,7 @@ func (n *nauth) GenerateHostCert(privateSigningKey, publicKey []byte, hostname,
}
validBefore := uint64(ssh.CertTimeInfinity)
if ttl != 0 {
b := time.Now().Add(ttl)
b := time.Now().UTC().Add(ttl)
validBefore = uint64(b.UnixNano())
}
cert := &ssh.Certificate{
@ -179,7 +179,7 @@ func (n *nauth) GenerateUserCert(pkey, key []byte, teleportUsername string, allo
}
validBefore := uint64(ssh.CertTimeInfinity)
if ttl != 0 {
b := time.Now().Add(ttl)
b := time.Now().UTC().Add(ttl)
validBefore = uint64(b.Unix())
}
// we do not use any extensions in users certs because of this:

View file

@ -78,7 +78,7 @@ func (n *Keygen) GenerateHostCert(pkey, key []byte, hostname, authDomain string,
}
validBefore := uint64(ssh.CertTimeInfinity)
if ttl != 0 {
b := time.Now().Add(ttl)
b := time.Now().UTC().Add(ttl)
validBefore = uint64(b.UnixNano())
}
cert := &ssh.Certificate{
@ -107,7 +107,7 @@ func (n *Keygen) GenerateUserCert(pkey, key []byte, teleportUsername string, all
}
validBefore := uint64(ssh.CertTimeInfinity)
if ttl != 0 {
b := time.Now().Add(ttl)
b := time.Now().UTC().Add(ttl)
validBefore = uint64(b.UnixNano())
}
cert := &ssh.Certificate{

View file

@ -781,9 +781,7 @@ func (tc *TeleportClient) AddTrustedCA(ca *services.CertAuthority) error {
// MakeKey generates a new unsigned key. It's useless by itself until a
// trusted CA signs it
func (tc *TeleportClient) MakeKey() (key *Key, err error) {
key = &Key{
Deadline: time.Now().Add(tc.KeyTTL),
}
key = &Key{}
keygen := native.New()
defer keygen.Close()
key.Priv, key.Pub, err = keygen.GenerateKeyPair("")

View file

@ -8,15 +8,11 @@ import (
"golang.org/x/crypto/ssh/agent"
)
// Key describes a stored client key
// Key describes a complete (signed) client key
type Key struct {
Priv []byte `json:"Priv,omitempty"`
Pub []byte `json:"Pub,omitempty"`
Cert []byte `json:"Cert,omitempty"`
// Deadline AKA TTL is the time when this key is safe to be discarded
// for garbage collection purposes
Deadline time.Time `json:"Deadline,omitempty"`
}
// LocalKeyStore interface allows for different storage back-ends for TSH to load/save its keys
@ -50,3 +46,13 @@ func (k *Key) AsAgentKey() (*agent.AddedKey, error) {
ConfirmBeforeUse: false,
}, nil
}
// CertValidBefore returns UTC time of the cert expiration
func (k *Key) CertValidBefore() (time.Time, error) {
pcert, _, _, _, err := ssh.ParseAuthorizedKey(k.Cert)
if err != nil {
return time.Now().UTC(), trace.Wrap(err)
}
cert := pcert.(*ssh.Certificate)
return time.Unix(0, int64(cert.ValidBefore)).UTC(), nil
}

View file

@ -39,7 +39,6 @@ const (
fileNameCert = "cert"
fileNameKey = "key"
fileNamePub = "pub"
fileNameTTL = ".ttl"
fileNameKnownHosts = "known_hosts"
)
@ -129,10 +128,6 @@ func (fs *FSLocalKeyStore) AddKey(host string, key *Key) error {
if err = writeBytes(fileNameKey, key.Priv); err != nil {
return trace.Wrap(err)
}
ttl, _ := key.Deadline.UTC().MarshalJSON()
if err = writeBytes(fileNameTTL, ttl); err != nil {
return trace.Wrap(err)
}
log.Infof("keystore.AddKey(%s)", host)
return nil
}
@ -144,23 +139,8 @@ func (fs *FSLocalKeyStore) GetKey(host string) (*Key, error) {
if err != nil {
return nil, trace.Wrap(err)
}
ttl, err := ioutil.ReadFile(filepath.Join(dirPath, fileNameTTL))
if err != nil {
log.Error(err)
return nil, trace.Wrap(err)
}
var deadline time.Time
if err = deadline.UnmarshalJSON(ttl); err != nil {
log.Error(err)
return nil, trace.Wrap(err)
}
// this session key is expired
if deadline.Before(time.Now().UTC()) {
os.RemoveAll(dirPath)
log.Infof("TTL expired for session key %v", dirPath)
return nil, trace.NotFound("session keys for %s are not found", host)
}
cert, err := ioutil.ReadFile(filepath.Join(dirPath, fileNameCert))
certFile := filepath.Join(dirPath, fileNameCert)
cert, err := ioutil.ReadFile(certFile)
if err != nil {
log.Error(err)
return nil, trace.Wrap(err)
@ -175,13 +155,21 @@ func (fs *FSLocalKeyStore) GetKey(host string) (*Key, error) {
log.Error(err)
return nil, trace.Wrap(err)
}
log.Infof("keystore.Get(%v)", host)
return &Key{
Pub: pub,
Priv: priv,
Cert: cert,
Deadline: deadline,
}, nil
key := &Key{Pub: pub, Priv: priv, Cert: cert}
// expired certificate? this key won't be accepted anymore, lets delete it:
certExpiration, err := key.CertValidBefore()
if err != nil {
return nil, trace.Wrap(err)
}
log.Infof("returning cert %v valid until %v", certFile, certExpiration)
if certExpiration.Before(time.Now().UTC()) {
os.RemoveAll(dirPath)
log.Infof("TTL expired for session key %v", dirPath)
return nil, trace.NotFound("session keys for %s are not found", host)
}
return key, nil
}
// AddKnownHost adds a new entry to 'known_CAs' file

View file

@ -60,7 +60,7 @@ func (s *KeyStoreTestSuite) TestListKeys(c *check.C) {
// add 5 keys:
keys := make([]Key, keyNum)
for i := 0; i < keyNum; i++ {
key := s.makeSignedKey(c)
key := s.makeSignedKey(c, false)
s.store.AddKey(fmt.Sprintf("host-%v", i), key)
keys[i] = *key
}
@ -72,7 +72,7 @@ func (s *KeyStoreTestSuite) TestListKeys(c *check.C) {
}
func (s *KeyStoreTestSuite) TestKeySaveLoad(c *check.C) {
key := s.makeSignedKey(c)
key := s.makeSignedKey(c, false)
// add key:
err := s.store.AddKey("host.a", key)
@ -85,9 +85,8 @@ func (s *KeyStoreTestSuite) TestKeySaveLoad(c *check.C) {
func (s *KeyStoreTestSuite) TestKeyExpiration(c *check.C) {
// make two keys: one is current, and the expire one
good := s.makeSignedKey(c)
expired := s.makeSignedKey(c)
expired.Deadline = time.Now().Add(-time.Hour)
good := s.makeSignedKey(c, false)
expired := s.makeSignedKey(c, true)
s.store.AddKey("good.host", good)
s.store.AddKey("expired.host", expired)
@ -120,7 +119,7 @@ func (s *KeyStoreTestSuite) TestKnownHosts(c *check.C) {
}
// makeSIgnedKey helper returns all 3 components of a user key (signed by CAPriv key)
func (s *KeyStoreTestSuite) makeSignedKey(c *check.C) *Key {
func (s *KeyStoreTestSuite) makeSignedKey(c *check.C, makeExpired bool) *Key {
var (
err error
priv, pub, cert []byte
@ -129,13 +128,15 @@ func (s *KeyStoreTestSuite) makeSignedKey(c *check.C) *Key {
username := "vincento"
allowedLogins := []string{username, "root"}
ttl := time.Duration(time.Minute * 20)
if makeExpired {
ttl = -ttl
}
cert, err = s.keygen.GenerateUserCert(CAPriv, pub, username, allowedLogins, ttl)
c.Assert(err, check.IsNil)
return &Key{
Priv: priv,
Pub: pub,
Cert: cert,
Deadline: time.Now().UTC().Add(ttl),
Priv: priv,
Pub: pub,
Cert: cert,
}
}

View file

@ -1,45 +0,0 @@
/*
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"time"
)
type Clock interface {
Now() time.Time
}
type TestClock struct {
N time.Time
}
func (t *TestClock) Now() time.Time {
return t.N
}
func (t *TestClock) Advance(d time.Duration) {
t.N = t.N.Add(d)
}
type WallClock struct {
}
func (*WallClock) Now() time.Time {
return time.Now()
}
var RealTime = &WallClock{}