@@ -501,15 +501,10 @@ type Config struct {
501
501
// If GetConfigForClient is nil, the Config passed to Server() will be
502
502
// used for all connections.
503
503
//
504
- // Uniquely for the fields in the returned Config, session ticket keys
505
- // will be duplicated from the original Config if not set.
506
- // Specifically, if SetSessionTicketKeys was called on the original
507
- // config but not on the returned config then the ticket keys from the
508
- // original config will be copied into the new config before use.
509
- // Otherwise, if SessionTicketKey was set in the original config but
510
- // not in the returned config then it will be copied into the returned
511
- // config before use. If neither of those cases applies then the key
512
- // material from the returned config will be used for session tickets.
504
+ // If SessionTicketKey was explicitly set on the returned Config, or if
505
+ // SetSessionTicketKeys was called on the returned Config, those keys will
506
+ // be used. Otherwise, the original Config keys will be used (and possibly
507
+ // rotated if they are automatically managed).
513
508
GetConfigForClient func (* ClientHelloInfo ) (* Config , error )
514
509
515
510
// VerifyPeerCertificate, if not nil, is called after normal
@@ -579,10 +574,10 @@ type Config struct {
579
574
// See RFC 5077 and the PSK mode of RFC 8446. If zero, it will be filled
580
575
// with random data before the first server handshake.
581
576
//
582
- // If multiple servers are terminating connections for the same host
583
- // they should all have the same SessionTicketKey. If the
584
- // SessionTicketKey leaks, previously recorded and future TLS
585
- // connections using that key might be compromised .
577
+ // Deprecated: if this field is left at zero, session ticket keys will be
578
+ // automatically rotated every day and dropped after seven days. For
579
+ // customizing the rotation schedule or synchronizing servers that are
580
+ // terminating connections for the same host, use SetSessionTicketKeys .
586
581
SessionTicketKey [32 ]byte
587
582
588
583
// ClientSessionCache is a cache of ClientSessionState entries for TLS
@@ -622,20 +617,32 @@ type Config struct {
622
617
// used for debugging.
623
618
KeyLogWriter io.Writer
624
619
625
- serverInitOnce sync.Once // guards calling (*Config).serverInit
626
-
627
- // mutex protects sessionTicketKeys.
620
+ // mutex protects sessionTicketKeys and autoSessionTicketKeys.
628
621
mutex sync.RWMutex
629
- // sessionTicketKeys contains zero or more ticket keys. If the length
630
- // is zero, SessionTicketsDisabled must be true. The first key is used
631
- // for new tickets and any subsequent keys can be used to decrypt old
632
- // tickets.
622
+ // sessionTicketKeys contains zero or more ticket keys. If set, it means the
623
+ // the keys were set with SessionTicketKey or SetSessionTicketKeys. The
624
+ // first key is used for new tickets and any subsequent keys can be used to
625
+ // decrypt old tickets. The slice contents are not protected by the mutex
626
+ // and are immutable.
633
627
sessionTicketKeys []ticketKey
628
+ // autoSessionTicketKeys is like sessionTicketKeys but is owned by the
629
+ // auto-rotation logic. See Config.ticketKeys.
630
+ autoSessionTicketKeys []ticketKey
634
631
}
635
632
636
- // ticketKeyNameLen is the number of bytes of identifier that is prepended to
637
- // an encrypted session ticket in order to identify the key used to encrypt it.
638
- const ticketKeyNameLen = 16
633
+ const (
634
+ // ticketKeyNameLen is the number of bytes of identifier that is prepended to
635
+ // an encrypted session ticket in order to identify the key used to encrypt it.
636
+ ticketKeyNameLen = 16
637
+
638
+ // ticketKeyLifetime is how long a ticket key remains valid and can be used to
639
+ // resume a client connection.
640
+ ticketKeyLifetime = 7 * 24 * time .Hour // 7 days
641
+
642
+ // ticketKeyRotation is how often the server should rotate the session ticket key
643
+ // that is used for new tickets.
644
+ ticketKeyRotation = 24 * time .Hour
645
+ )
639
646
640
647
// ticketKey is the internal representation of a session ticket key.
641
648
type ticketKey struct {
@@ -644,16 +651,19 @@ type ticketKey struct {
644
651
keyName [ticketKeyNameLen ]byte
645
652
aesKey [16 ]byte
646
653
hmacKey [16 ]byte
654
+ // created is the time at which this ticket key was created. See Config.ticketKeys.
655
+ created time.Time
647
656
}
648
657
649
658
// ticketKeyFromBytes converts from the external representation of a session
650
659
// ticket key to a ticketKey. Externally, session ticket keys are 32 random
651
660
// bytes and this function expands that into sufficient name and key material.
652
- func ticketKeyFromBytes (b [32 ]byte ) (key ticketKey ) {
661
+ func ( c * Config ) ticketKeyFromBytes (b [32 ]byte ) (key ticketKey ) {
653
662
hashed := sha512 .Sum512 (b [:])
654
663
copy (key .keyName [:], hashed [:ticketKeyNameLen ])
655
664
copy (key .aesKey [:], hashed [ticketKeyNameLen :ticketKeyNameLen + 16 ])
656
665
copy (key .hmacKey [:], hashed [ticketKeyNameLen + 16 :ticketKeyNameLen + 32 ])
666
+ key .created = c .time ()
657
667
return key
658
668
}
659
669
@@ -664,15 +674,8 @@ const maxSessionTicketLifetime = 7 * 24 * time.Hour
664
674
// Clone returns a shallow clone of c. It is safe to clone a Config that is
665
675
// being used concurrently by a TLS client or server.
666
676
func (c * Config ) Clone () * Config {
667
- // Running serverInit ensures that it's safe to read
668
- // SessionTicketsDisabled.
669
- c .serverInitOnce .Do (func () { c .serverInit (nil ) })
670
-
671
- var sessionTicketKeys []ticketKey
672
677
c .mutex .RLock ()
673
- sessionTicketKeys = c .sessionTicketKeys
674
- c .mutex .RUnlock ()
675
-
678
+ defer c .mutex .RUnlock ()
676
679
return & Config {
677
680
Rand : c .Rand ,
678
681
Time : c .Time ,
@@ -699,66 +702,130 @@ func (c *Config) Clone() *Config {
699
702
DynamicRecordSizingDisabled : c .DynamicRecordSizingDisabled ,
700
703
Renegotiation : c .Renegotiation ,
701
704
KeyLogWriter : c .KeyLogWriter ,
702
- sessionTicketKeys : sessionTicketKeys ,
705
+ sessionTicketKeys : c .sessionTicketKeys ,
706
+ autoSessionTicketKeys : c .autoSessionTicketKeys ,
703
707
}
704
708
}
705
709
706
- // serverInit is run under c.serverInitOnce to do initialization of c. If c was
707
- // returned by a GetConfigForClient callback then the argument should be the
708
- // Config that was passed to Server, otherwise it should be nil.
709
- func (c * Config ) serverInit (originalConfig * Config ) {
710
- if c .SessionTicketsDisabled || len (c .ticketKeys ()) != 0 {
710
+ // deprecatedSessionTicketKey is set as the prefix of SessionTicketKey if it was
711
+ // randomized for backwards compatibility but is not in use.
712
+ var deprecatedSessionTicketKey = []byte ("DEPRECATED" )
713
+
714
+ // initLegacySessionTicketKeyRLocked ensures the legacy SessionTicketKey field is
715
+ // randomized if empty, and that sessionTicketKeys is populated from it otherwise.
716
+ func (c * Config ) initLegacySessionTicketKeyRLocked () {
717
+ // Don't write if SessionTicketKey is already defined as our deprecated string,
718
+ // or if it is defined by the user but sessionTicketKeys is already set.
719
+ if c .SessionTicketKey != [32 ]byte {} &&
720
+ (bytes .HasPrefix (c .SessionTicketKey [:], deprecatedSessionTicketKey ) || len (c .sessionTicketKeys ) > 0 ) {
711
721
return
712
722
}
713
723
714
- alreadySet := false
715
- for _ , b := range c .SessionTicketKey {
716
- if b != 0 {
717
- alreadySet = true
718
- break
724
+ // We need to write some data, so get an exclusive lock and re-check any conditions.
725
+ c .mutex .RUnlock ()
726
+ defer c .mutex .RLock ()
727
+ c .mutex .Lock ()
728
+ defer c .mutex .Unlock ()
729
+ if c .SessionTicketKey == [32 ]byte {} {
730
+ if _ , err := io .ReadFull (c .rand (), c .SessionTicketKey [:]); err != nil {
731
+ panic (fmt .Sprintf ("tls: unable to generate random session ticket key: %v" , err ))
719
732
}
733
+ // Write the deprecated prefix at the beginning so we know we created
734
+ // it. This key with the DEPRECATED prefix isn't used as an actual
735
+ // session ticket key, and is only randomized in case the application
736
+ // reuses it for some reason.
737
+ copy (c .SessionTicketKey [:], deprecatedSessionTicketKey )
738
+ } else if ! bytes .HasPrefix (c .SessionTicketKey [:], deprecatedSessionTicketKey ) && len (c .sessionTicketKeys ) == 0 {
739
+ c .sessionTicketKeys = []ticketKey {c .ticketKeyFromBytes (c .SessionTicketKey )}
720
740
}
721
741
722
- if ! alreadySet {
723
- if originalConfig != nil {
724
- copy (c .SessionTicketKey [:], originalConfig .SessionTicketKey [:])
725
- } else if _ , err := io .ReadFull (c .rand (), c .SessionTicketKey [:]); err != nil {
726
- c .SessionTicketsDisabled = true
727
- return
742
+ }
743
+
744
+ // ticketKeys returns the ticketKeys for this connection.
745
+ // If configForClient has explicitly set keys, those will
746
+ // be returned. Otherwise, the keys on c will be used and
747
+ // may be rotated if auto-managed.
748
+ // During rotation, any expired session ticket keys are deleted from
749
+ // c.sessionTicketKeys. If the session ticket key that is currently
750
+ // encrypting tickets (ie. the first ticketKey in c.sessionTicketKeys)
751
+ // is not fresh, then a new session ticket key will be
752
+ // created and prepended to c.sessionTicketKeys.
753
+ func (c * Config ) ticketKeys (configForClient * Config ) []ticketKey {
754
+ // If the ConfigForClient callback returned a Config with explicitly set
755
+ // keys, use those, otherwise just use the original Config.
756
+ if configForClient != nil {
757
+ configForClient .mutex .RLock ()
758
+ if configForClient .SessionTicketsDisabled {
759
+ return nil
760
+ }
761
+ configForClient .initLegacySessionTicketKeyRLocked ()
762
+ if len (configForClient .sessionTicketKeys ) != 0 {
763
+ ret := configForClient .sessionTicketKeys
764
+ configForClient .mutex .RUnlock ()
765
+ return ret
728
766
}
767
+ configForClient .mutex .RUnlock ()
729
768
}
730
769
731
- if originalConfig != nil {
732
- originalConfig .mutex .RLock ()
733
- c .sessionTicketKeys = originalConfig .sessionTicketKeys
734
- originalConfig .mutex .RUnlock ()
735
- } else {
736
- c .sessionTicketKeys = []ticketKey {ticketKeyFromBytes (c .SessionTicketKey )}
770
+ c .mutex .RLock ()
771
+ defer c .mutex .RUnlock ()
772
+ if c .SessionTicketsDisabled {
773
+ return nil
774
+ }
775
+ c .initLegacySessionTicketKeyRLocked ()
776
+ if len (c .sessionTicketKeys ) != 0 {
777
+ return c .sessionTicketKeys
778
+ }
779
+ // Fast path for the common case where the key is fresh enough.
780
+ if len (c .autoSessionTicketKeys ) > 0 && c .time ().Sub (c .autoSessionTicketKeys [0 ].created ) < ticketKeyRotation {
781
+ return c .autoSessionTicketKeys
737
782
}
738
- }
739
783
740
- func (c * Config ) ticketKeys () []ticketKey {
741
- c .mutex .RLock ()
742
- // c.sessionTicketKeys is constant once created. SetSessionTicketKeys
743
- // will only update it by replacing it with a new value.
744
- ret := c .sessionTicketKeys
784
+ // autoSessionTicketKeys are managed by auto-rotation.
745
785
c .mutex .RUnlock ()
746
- return ret
786
+ defer c .mutex .RLock ()
787
+ c .mutex .Lock ()
788
+ defer c .mutex .Unlock ()
789
+ // Re-check the condition in case it changed since obtaining the new lock.
790
+ if len (c .autoSessionTicketKeys ) == 0 || c .time ().Sub (c .autoSessionTicketKeys [0 ].created ) >= ticketKeyRotation {
791
+ var newKey [32 ]byte
792
+ if _ , err := io .ReadFull (c .rand (), newKey [:]); err != nil {
793
+ panic (fmt .Sprintf ("unable to generate random session ticket key: %v" , err ))
794
+ }
795
+ valid := make ([]ticketKey , 0 , len (c .autoSessionTicketKeys )+ 1 )
796
+ valid = append (valid , c .ticketKeyFromBytes (newKey ))
797
+ for _ , k := range c .autoSessionTicketKeys {
798
+ // While rotating the current key, also remove any expired ones.
799
+ if c .time ().Sub (k .created ) < ticketKeyLifetime {
800
+ valid = append (valid , k )
801
+ }
802
+ }
803
+ c .autoSessionTicketKeys = valid
804
+ }
805
+ return c .autoSessionTicketKeys
747
806
}
748
807
749
- // SetSessionTicketKeys updates the session ticket keys for a server. The first
750
- // key will be used when creating new tickets, while all keys can be used for
751
- // decrypting tickets. It is safe to call this function while the server is
752
- // running in order to rotate the session ticket keys. The function will panic
753
- // if keys is empty.
808
+ // SetSessionTicketKeys updates the session ticket keys for a server.
809
+ //
810
+ // The first key will be used when creating new tickets, while all keys can be
811
+ // used for decrypting tickets. It is safe to call this function while the
812
+ // server is running in order to rotate the session ticket keys. The function
813
+ // will panic if keys is empty.
814
+ //
815
+ // Calling this function will turn off automatic session ticket key rotation.
816
+ //
817
+ // If multiple servers are terminating connections for the same host they should
818
+ // all have the same session ticket keys. If the session ticket keys leaks,
819
+ // previously recorded and future TLS connections using those keys might be
820
+ // compromised.
754
821
func (c * Config ) SetSessionTicketKeys (keys [][32 ]byte ) {
755
822
if len (keys ) == 0 {
756
823
panic ("tls: keys must have at least one key" )
757
824
}
758
825
759
826
newKeys := make ([]ticketKey , len (keys ))
760
827
for i , bytes := range keys {
761
- newKeys [i ] = ticketKeyFromBytes (bytes )
828
+ newKeys [i ] = c . ticketKeyFromBytes (bytes )
762
829
}
763
830
764
831
c .mutex .Lock ()
0 commit comments