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 }