Skip to content

Commit

Permalink
tris/DelegatedCredentials: CredentialsConfig struct; credentials are …
Browse files Browse the repository at this point in the history
…renewed using goroutine
  • Loading branch information
tsusanka committed Aug 22, 2017
1 parent b2c51dc commit c853649
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 55 deletions.
89 changes: 46 additions & 43 deletions delegated_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (
"time"
)

// After only renewThreshold of credential's validTime is left a new one is created
const renewThreshold = 0.2

// The Delegation Usage x509 extension identifier - temporary, TBD
var delegatedCredentialsIdentifier = asn1.ObjectIdentifier{2, 5, 29, 99}

type GetCertificate func(*ClientHelloInfo) (*Certificate, error)
Expand All @@ -30,78 +34,84 @@ type cachedCredential struct {
credential []byte
validTime int64
privateKey crypto.PrivateKey
renewAfter time.Time
}

type credentialsSetup struct {
scheme SignatureScheme
cert *Certificate
version uint16
validity time.Duration
timeFunc func() time.Time
type CredentialsConfig struct {
Cert *Certificate
Validity time.Duration
scheme SignatureScheme
version uint16
timeFunc func() time.Time
renewChan chan struct{}
}

var cachedCredentials map[SignatureScheme]*cachedCredential

// Creates new GetCertificate function which modifies the certificate
// to include a delegated credential.
func NewDelegatedCredentialsGetCertificate(cert *Certificate, validity time.Duration, timeFunc func() time.Time) GetCertificate {
func NewDelegatedCredentialsGetCertificate(config *CredentialsConfig) GetCertificate {
return func(clientHelloInfo *ClientHelloInfo) (*Certificate, error) {
if !isCertificateValidForDelegationUsage(cert.Leaf) {
if !isCertificateValidForDelegationUsage(config.Cert.Leaf) {
return nil, errors.New("tls: Delegated Credentials not supported by the certificate (DelegationUsage extension missing)")
}
selectedScheme := selectSignatureScheme(clientHelloInfo.SignatureSchemes, cert.Leaf)
selectedScheme := selectSignatureScheme(clientHelloInfo.SignatureSchemes, config.Cert.Leaf)
if selectedScheme == 0 {
return nil, errors.New("tls: No valid signature scheme found for Delegated Credentials")
}
version := selectVersion(clientHelloInfo.SupportedVersions)
if version == 0 {
return nil, errors.New("tls: Only TLS 1.2 or 1.3 are supported")
}
if config.timeFunc == nil {
config.timeFunc = time.Now
}

err := fetchCredentialFromCache(&credentialsSetup{
scheme: selectedScheme,
cert: cert,
version: version,
validity: validity,
timeFunc: timeFunc,
})
config.scheme = selectedScheme
config.version = version
err := fetchCredentialFromCache(config)
if err != nil {
return nil, err
}

cert.DelegatedCredential = cachedCredentials[selectedScheme].credential
cert.PrivateKey = cachedCredentials[selectedScheme].privateKey
return cert, nil
config.Cert.DelegatedCredential = cachedCredentials[selectedScheme].credential
config.Cert.PrivateKey = cachedCredentials[selectedScheme].privateKey
return config.Cert, nil
}
}

// Checks if a credential is already stored in memory. If not or the credential
// is already expired a new credential is created.
func fetchCredentialFromCache(setup *credentialsSetup) (err error) {
func fetchCredentialFromCache(config *CredentialsConfig) (err error) {
if cachedCredentials == nil {
cachedCredentials = make(map[SignatureScheme]*cachedCredential)
}
if cachedCredentials[setup.scheme] == nil {
cachedCredentials[setup.scheme], err = newCredential(setup)
if cachedCredentials[config.scheme] == nil {
cachedCredentials[config.scheme], err = newCredential(config)
} else {
err := checkValidity(cachedCredentials[setup.scheme].validTime, setup.cert.Leaf, setup.timeFunc)
if err != nil {
cachedCredentials[setup.scheme], err = newCredential(setup)
if config.timeFunc().After(cachedCredentials[config.scheme].renewAfter) {
go func() {
cachedCredentials[config.scheme], err = newCredential(config)
if config.renewChan != nil {
config.renewChan <- struct{}{}
}
}()
}
}
return
}

func newCredential(setup *credentialsSetup) (*cachedCredential, error) {
credential, privateKey, err := createDelegatedCredential(setup)
credentialBytes, err := credential.marshalAndSign(setup.cert, setup.scheme, setup.version)
func newCredential(config *CredentialsConfig) (*cachedCredential, error) {
credential, privateKey, renewAfter, err := createDelegatedCredential(config)
credentialBytes, err := credential.marshalAndSign(config.Cert, config.scheme, config.version)
if err != nil {
return nil, fmt.Errorf("tls: creating Delegated Credential failed (%s)", err)
}
return &cachedCredential{
credential: credentialBytes,
validTime: credential.ValidTime,
privateKey: privateKey,
renewAfter: renewAfter,
}, nil
}

Expand Down Expand Up @@ -134,14 +144,15 @@ func selectSignatureScheme(signatureSchemes []SignatureScheme, cert *x509.Certif

// Creates new Delegated Credential. The type of the credential is decided based on the selected
// SignatureScheme to ensure client's support.
func createDelegatedCredential(setup *credentialsSetup) (credential DelegatedCredential, privateKey crypto.PrivateKey, err error) {
validTill := setup.timeFunc().Add(setup.validity)
relativeToCert := validTill.Sub(setup.cert.Leaf.NotBefore)
func createDelegatedCredential(config *CredentialsConfig) (credential DelegatedCredential, privateKey crypto.PrivateKey, renewAfter time.Time, err error) {
validTill := config.timeFunc().Add(config.Validity)
relativeToCert := validTill.Sub(config.Cert.Leaf.NotBefore)
renewAfter = validTill.Add(-time.Duration(config.Validity.Seconds()*renewThreshold) * time.Second)
credential = DelegatedCredential{
ValidTime: int64(relativeToCert.Seconds()),
}

if setup.scheme == ECDSAWithP256AndSHA256 {
if config.scheme == ECDSAWithP256AndSHA256 {
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
credential.PublicKey = privateKey.(crypto.Signer).Public()
} else {
Expand Down Expand Up @@ -192,14 +203,6 @@ func (dc *DelegatedCredential) marshalAndSign(cert *Certificate, scheme Signatur
return cred[0 : publicKeyLength+11+signatureLength], err
}

func checkValidity(validTime int64, certificate *x509.Certificate, now func() time.Time) error {
validTill := certificate.NotBefore.Add(time.Duration(validTime) * time.Second)
if validTill.Before(now()) {
return errors.New("expired")
}
return nil
}

// Unmarshals the credential and verifies its signature and its validity.
func unmarshalAndVerify(credentialBytes []byte, certificate *x509.Certificate, version uint16, now func() time.Time) (dc DelegatedCredential, err error) {
dc = DelegatedCredential{}
Expand All @@ -212,9 +215,9 @@ func unmarshalAndVerify(credentialBytes []byte, certificate *x509.Certificate, v
PublicKeyBytes := make([]byte, publicKeyLength)
copy(PublicKeyBytes, credentialBytes[7:7+publicKeyLength])
dc.PublicKey, err = x509.ParsePKIXPublicKey(PublicKeyBytes)
err = checkValidity(dc.ValidTime, certificate, now)
if err != nil {
return dc, err
validTill := certificate.NotBefore.Add(time.Duration(dc.ValidTime) * time.Second)
if validTill.Before(now()) {
return dc, errors.New("expired")
}

signatureScheme := SignatureScheme(credentialBytes[publicKeyLength+7])<<8 + SignatureScheme(credentialBytes[publicKeyLength+8])
Expand Down
73 changes: 62 additions & 11 deletions delegated_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestGetCertificateFunctionWithInvalidSignatureScheme(t *testing.T) {
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
SupportedVersions: []uint16{VersionTLS12},
}
getCertificateFn := NewDelegatedCredentialsGetCertificate(cert, 5*time.Minute, testConfig.Time)
getCertificateFn := NewDelegatedCredentialsGetCertificate(&CredentialsConfig{Cert: cert, Validity: 5 * time.Minute})

_, err := getCertificateFn(clientHelloInfo)
expectError(err, "No valid signature scheme", t)
Expand Down Expand Up @@ -54,7 +54,7 @@ func TestGetCertificateFunctionWithInvalidCertificate(t *testing.T) {
SignatureSchemes: []SignatureScheme{ECDSAWithP256AndSHA256},
SupportedVersions: []uint16{VersionTLS12},
}
getCertificateFn := NewDelegatedCredentialsGetCertificate(cert, 5*time.Minute, testConfig.Time)
getCertificateFn := NewDelegatedCredentialsGetCertificate(&CredentialsConfig{Cert: cert, Validity: 5 * time.Minute})

_, err := getCertificateFn(clientHelloInfo)
expectError(err, "Delegated Credentials not supported by the certificate (DelegationUsage extension missing)", t)
Expand All @@ -67,7 +67,7 @@ func TestGetCertificateFunction(t *testing.T) {
SignatureSchemes: []SignatureScheme{ECDSAWithP256AndSHA256},
SupportedVersions: []uint16{VersionTLS12},
}
getCertificateFn := NewDelegatedCredentialsGetCertificate(cert, 5*time.Minute, testConfig.Time)
getCertificateFn := NewDelegatedCredentialsGetCertificate(&CredentialsConfig{Cert: cert, Validity: 5 * time.Minute})

delCredCert, err := getCertificateFn(clientHelloInfo)
expectError(err, "", t)
Expand Down Expand Up @@ -138,6 +138,40 @@ func TestMarshalling(t *testing.T) {
}
}

func TestNewCredential(t *testing.T) {
cert := getTLSCertificate(testDelUsageECCertificate, testDelUsageECPrivateKey)
now := func() time.Time {
t, _ := time.Parse("Aug 2 15:00:00 2017", "Aug 2 15:00:00 2017")
return t
}
run := func(d time.Duration) *cachedCredential {
setup := CredentialsConfig{
scheme: ECDSAWithP256AndSHA256,
Cert: cert,
version: VersionTLS12,
Validity: d,
timeFunc: now,
}
cred, err := newCredential(&setup)
if err != nil || cred.credential == nil {
t.Error("Unexpected error")
}
return cred
}

result := map[string]*cachedCredential{
"2017-08-02 15:04:00 +0000 UTC": run(5 * time.Minute),
"2017-08-02 15:00:08 +0000 UTC": run(10 * time.Second),
"2017-08-02 15:00:48 +0000 UTC": run(59 * time.Second),
"2017-08-02 18:12:00 +0000 UTC": run(4 * time.Hour),
}
for expected, actual := range result {
if actual.renewAfter.String() != expected {
t.Errorf("Expected cachedCredential's renewAfter time to be equal to %s, but was %s", expected, actual.renewAfter.String())
}
}
}

// ---------------- integration tests ---------------- //

// TLS 1.2 RSA with DC
Expand All @@ -159,15 +193,16 @@ func TestDelegatedCredentialsHandshake12RSA(t *testing.T) {
PrivateKey: key,
}
testCase.ServerConfig.Certificates = nil
testCase.ServerConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(certificate, 5*time.Minute, testCase.ServerConfig.Time)
testCase.ServerConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(&CredentialsConfig{Cert: certificate, Validity: 5 * time.Minute})
testCase.doHandshake(t)
}

func TestDelegatedCredentialCache(t *testing.T) {
timeCounter := time.Unix(0, 0)
timeCounter, _ := time.Parse("Aug 2 15:00:00 2017", "Aug 2 15:00:00 2017")
now := func() time.Time {
return timeCounter
}
cachedCredentials = nil // clear all previously saved credentials (if any)

serverConfig := testConfig.Clone()
cert, key := getCertAndKey(testDelUsageRSACertificate, testDelUsageRSAPrivateKey)
Expand All @@ -177,7 +212,13 @@ func TestDelegatedCredentialCache(t *testing.T) {
PrivateKey: key,
}
serverConfig.Certificates = nil
serverConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(certificate, 5*time.Minute, now)
config := &CredentialsConfig{
Cert: certificate,
Validity: 5 * time.Minute,
timeFunc: now,
renewChan: make(chan struct{}),
}
serverConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(config)

clientHello := &clientHelloMsg{
vers: VersionTLS12,
Expand Down Expand Up @@ -213,13 +254,23 @@ func TestDelegatedCredentialCache(t *testing.T) {
}

a := getCredentialFromServer()
timeCounter = timeCounter.Add(1 * time.Minute) // will not expire yet
b := getCredentialFromServer()
if !bytes.Equal(a, b) {
timeCounter = timeCounter.Add(3 * time.Minute) // will not expire yet
c := getCredentialFromServer()
if !bytes.Equal(a, b) || !bytes.Equal(b, c) {
t.Errorf("The delegated credentials should match (its validity should be ok)")
}
timeCounter = timeCounter.Add(10 * time.Minute) // will expire -> new DC
c := getCredentialFromServer()
if bytes.Equal(a, c) {

timeCounter = timeCounter.Add(10 * time.Minute) // will expire -> new DC for next run
d := getCredentialFromServer()
if !bytes.Equal(a, d) {
t.Errorf("The delegated credentials should still match (new DC created for next run)")
}
<-config.renewChan

e := getCredentialFromServer() // new DC
if bytes.Equal(a, e) {
t.Errorf("The delegated credentials should not match (first one is expired)")
}
}
Expand All @@ -243,7 +294,7 @@ func TestDelegatedCredentialsHandshake12EC(t *testing.T) {
PrivateKey: key,
}
testCase.ServerConfig.Certificates = nil
testCase.ServerConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(certificate, 5*time.Minute, testCase.ServerConfig.Time)
testCase.ServerConfig.GetCertificate = NewDelegatedCredentialsGetCertificate(&CredentialsConfig{Cert: certificate, Validity: 5 * time.Minute})
testCase.doHandshake(t)
}

Expand Down
1 change: 0 additions & 1 deletion handshake_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,6 @@ Curves:
}

if hs.clientHello.delegatedCredentials {
// todo "caching"?
hs.hello.delegatedCredential = hs.cert.DelegatedCredential
}

Expand Down

0 comments on commit c853649

Please sign in to comment.