github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/obfuscator/passthrough.go (about)

     1  /*
     2   * Copyright (c) 2020, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package obfuscator
    21  
    22  import (
    23  	"crypto/hmac"
    24  	"crypto/rand"
    25  	"crypto/sha256"
    26  	"crypto/subtle"
    27  	"encoding/binary"
    28  	"io"
    29  	"time"
    30  
    31  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    32  	"golang.org/x/crypto/hkdf"
    33  )
    34  
    35  const (
    36  	TLS_PASSTHROUGH_NONCE_SIZE   = 16
    37  	TLS_PASSTHROUGH_KEY_SIZE     = 32
    38  	TLS_PASSTHROUGH_TIME_PERIOD  = 20 * time.Minute
    39  	TLS_PASSTHROUGH_MESSAGE_SIZE = 32
    40  )
    41  
    42  // MakeTLSPassthroughMessage generates a unique TLS passthrough message
    43  // using the passthrough key derived from a master obfuscated key.
    44  //
    45  // The passthrough message demonstrates knowledge of the obfuscated key.
    46  // When useTimeFactor is set, the message will also reflect the current
    47  // time period, limiting how long it remains valid.
    48  //
    49  // The configurable useTimeFactor enables support for legacy clients and
    50  // servers which don't use the time factor.
    51  func MakeTLSPassthroughMessage(
    52  	useTimeFactor bool, obfuscatedKey string) ([]byte, error) {
    53  
    54  	passthroughKey, err := derivePassthroughKey(useTimeFactor, obfuscatedKey)
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  
    59  	message := make([]byte, TLS_PASSTHROUGH_MESSAGE_SIZE)
    60  
    61  	_, err = rand.Read(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
    62  	if err != nil {
    63  		return nil, errors.Trace(err)
    64  	}
    65  
    66  	h := hmac.New(sha256.New, passthroughKey)
    67  	h.Write(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
    68  	copy(message[TLS_PASSTHROUGH_NONCE_SIZE:], h.Sum(nil))
    69  
    70  	return message, nil
    71  }
    72  
    73  // VerifyTLSPassthroughMessage checks that the specified passthrough message
    74  // was generated using the passthrough key.
    75  //
    76  // useTimeFactor must be set to the same value used in
    77  // MakeTLSPassthroughMessage.
    78  func VerifyTLSPassthroughMessage(
    79  	useTimeFactor bool, obfuscatedKey string, message []byte) bool {
    80  
    81  	// If the message is the wrong length, continue processing with a stub
    82  	// message of the correct length. This is to avoid leaking the existence of
    83  	// passthrough via timing differences.
    84  	if len(message) != TLS_PASSTHROUGH_MESSAGE_SIZE {
    85  		var stub [TLS_PASSTHROUGH_MESSAGE_SIZE]byte
    86  		message = stub[:]
    87  	}
    88  
    89  	passthroughKey, err := derivePassthroughKey(useTimeFactor, obfuscatedKey)
    90  	if err != nil {
    91  		// TODO: log error
    92  		return false
    93  	}
    94  
    95  	h := hmac.New(sha256.New, passthroughKey)
    96  	h.Write(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
    97  
    98  	return 1 == subtle.ConstantTimeCompare(
    99  		message[TLS_PASSTHROUGH_NONCE_SIZE:],
   100  		h.Sum(nil)[0:TLS_PASSTHROUGH_MESSAGE_SIZE-TLS_PASSTHROUGH_NONCE_SIZE])
   101  }
   102  
   103  // timePeriodSeconds is variable, to enable overriding the value in
   104  // TestTLSPassthrough. This value should not be overridden outside of test
   105  // cases.
   106  var timePeriodSeconds = int64(TLS_PASSTHROUGH_TIME_PERIOD / time.Second)
   107  
   108  func derivePassthroughKey(
   109  	useTimeFactor bool, obfuscatedKey string) ([]byte, error) {
   110  
   111  	secret := []byte(obfuscatedKey)
   112  
   113  	salt := []byte("passthrough-obfuscation-key")
   114  
   115  	if useTimeFactor {
   116  
   117  		// Include a time factor, so messages created with this key remain valid
   118  		// only for a limited time period. The current time is rounded, allowing the
   119  		// client clock to be slightly ahead of or behind of the server clock.
   120  		//
   121  		// This time factor mechanism is used in concert with SeedHistory to detect
   122  		// passthrough message replay. SeedHistory, a history of recent passthrough
   123  		// messages, is used to detect duplicate passthrough messages. The time
   124  		// factor bounds the necessary history length: passthrough messages older
   125  		// than the time period no longer need to be retained in history.
   126  		//
   127  		// We _always_ derive the passthrough key for each
   128  		// MakeTLSPassthroughMessage, even for multiple calls in the same time
   129  		// factor period, to avoid leaking the presense of passthough via timing
   130  		// differences at time boundaries. We assume that the server always or never
   131  		// sets useTimeFactor.
   132  
   133  		roundedTimePeriod := (time.Now().Unix() + (timePeriodSeconds / 2)) / timePeriodSeconds
   134  
   135  		var timeFactor [8]byte
   136  		binary.LittleEndian.PutUint64(timeFactor[:], uint64(roundedTimePeriod))
   137  		salt = append(salt, timeFactor[:]...)
   138  	}
   139  
   140  	key := make([]byte, TLS_PASSTHROUGH_KEY_SIZE)
   141  
   142  	_, err := io.ReadFull(hkdf.New(sha256.New, secret, salt, nil), key)
   143  	if err != nil {
   144  		return nil, errors.Trace(err)
   145  	}
   146  
   147  	return key, nil
   148  }