github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/protocol/customTLSProfiles.go (about) 1 /* 2 * Copyright (c) 2019, 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 protocol 21 22 import ( 23 "crypto/sha256" 24 "encoding/json" 25 26 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 27 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 28 utls "github.com/Psiphon-Labs/utls" 29 ) 30 31 // CustomTLSProfile specifies custom TLS profile. This is used to deploy 32 // custom ClientHellos as tactics data. 33 type CustomTLSProfile struct { 34 Name string 35 UTLSSpec *UTLSSpec 36 } 37 38 type CustomTLSProfiles []*CustomTLSProfile 39 40 // Validate checks that the profiles in CustomTLSProfiles are initialized and 41 // have no name conflicts. 42 func (profiles CustomTLSProfiles) Validate() error { 43 names := make(map[string]bool) 44 for _, p := range profiles { 45 if p.Name == "" { 46 return errors.Tracef("custom TLS profile missing name: %s", p.Name) 47 } 48 if p.UTLSSpec == nil { 49 return errors.Tracef("custom TLS profile missing utls spec: %s", p.Name) 50 } 51 if common.Contains(SupportedTLSProfiles, p.Name) || 52 common.Contains(legacyTLSProfiles, p.Name) { 53 return errors.Tracef("invalid custom TLS profile name: %s", p.Name) 54 } 55 if _, ok := names[p.Name]; ok { 56 return errors.Tracef("duplicate custom TLS profile name: %s", p.Name) 57 } 58 names[p.Name] = true 59 } 60 return nil 61 } 62 63 // GetClientHelloSpec creates a new utls.ClientHelloSpec from the ClientHello 64 // definition in UTLSpec. 65 // 66 // A new utls.ClientHelloSpec, with no shared data, is created for each call, 67 // as per: 68 // https://github.com/refraction-networking/utls/blob/4da67951864128358459681399dd208c49d5d001/u_parrots.go#L483 69 func (profile *CustomTLSProfile) GetClientHelloSpec() (*utls.ClientHelloSpec, error) { 70 71 spec := &utls.ClientHelloSpec{} 72 73 spec.TLSVersMin = profile.UTLSSpec.TLSVersMin 74 spec.TLSVersMax = profile.UTLSSpec.TLSVersMax 75 spec.CipherSuites = append([]uint16(nil), profile.UTLSSpec.CipherSuites...) 76 spec.CompressionMethods = append([]uint8(nil), profile.UTLSSpec.CompressionMethods...) 77 78 spec.Extensions = make([]utls.TLSExtension, len(profile.UTLSSpec.Extensions)) 79 for i, extension := range profile.UTLSSpec.Extensions { 80 var err error 81 spec.Extensions[i], err = extension.GetUTLSExtension() 82 if err != nil { 83 return nil, errors.Trace(err) 84 } 85 } 86 87 if profile.UTLSSpec.GetSessionID == "SHA-256" { 88 spec.GetSessionID = sha256.Sum256 89 } 90 91 return spec, nil 92 } 93 94 // UTLSSpec is a parallel data structure mirroring utls.ClientHelloSpec. Note 95 // that utls.ClientHelloSpec cannot be directly marshaled with encoding/json 96 // nor encoding/gob due to various type restrictions which 97 // utls.ClientHelloSpec does not meet. Nor can we simply transmit a static, 98 // raw ClientHello since concrete utls extension types must be instantiated in 99 // order for related functionality to be enabled. 100 101 // UTLSSpec specifies a utls.ClientHelloSpec. 102 type UTLSSpec struct { 103 TLSVersMin uint16 104 TLSVersMax uint16 105 CipherSuites []uint16 106 CompressionMethods []uint8 107 Extensions []*UTLSExtension 108 GetSessionID string 109 } 110 111 // UTLSExtension specifies one of the several utls.TLSExtension concrete 112 // implementations. 113 type UTLSExtension struct { 114 Name string 115 Data json.RawMessage 116 } 117 118 // GetUTLSExtension instantiates the specified utls.TLSExtension concrete 119 // implementation. 120 func (e *UTLSExtension) GetUTLSExtension() (utls.TLSExtension, error) { 121 switch e.Name { 122 case "NPN": 123 var extension *utls.NPNExtension 124 err := json.Unmarshal(e.Data, &extension) 125 if err != nil { 126 return nil, errors.Trace(err) 127 } 128 return extension, nil 129 case "SNI": 130 return &utls.SNIExtension{}, nil 131 case "StatusRequest": 132 return &utls.StatusRequestExtension{}, nil 133 case "SupportedCurves": 134 var extension *utls.SupportedCurvesExtension 135 err := json.Unmarshal(e.Data, &extension) 136 if err != nil { 137 return nil, errors.Trace(err) 138 } 139 return extension, nil 140 case "SupportedPoints": 141 var extension *utls.SupportedPointsExtension 142 err := json.Unmarshal(e.Data, &extension) 143 if err != nil { 144 return nil, errors.Trace(err) 145 } 146 return extension, nil 147 case "SignatureAlgorithms": 148 var extension *utls.SignatureAlgorithmsExtension 149 err := json.Unmarshal(e.Data, &extension) 150 if err != nil { 151 return nil, errors.Trace(err) 152 } 153 return extension, nil 154 case "RenegotiationInfo": 155 var extension *utls.RenegotiationInfoExtension 156 err := json.Unmarshal(e.Data, &extension) 157 if err != nil { 158 return nil, errors.Trace(err) 159 } 160 return extension, nil 161 case "ALPN": 162 var extension *utls.ALPNExtension 163 err := json.Unmarshal(e.Data, &extension) 164 if err != nil { 165 return nil, errors.Trace(err) 166 } 167 return extension, nil 168 case "SCT": 169 return &utls.SCTExtension{}, nil 170 case "SessionTicket": 171 return &utls.SessionTicketExtension{}, nil 172 case "Generic": 173 var extension *utls.GenericExtension 174 err := json.Unmarshal(e.Data, &extension) 175 if err != nil { 176 return nil, errors.Trace(err) 177 } 178 return extension, nil 179 case "ExtendedMasterSecret": 180 return &utls.UtlsExtendedMasterSecretExtension{}, nil 181 case "GREASE": 182 return &utls.UtlsGREASEExtension{}, nil 183 case "BoringPadding": 184 return &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, nil 185 case "KeyShare": 186 var extension *utls.KeyShareExtension 187 err := json.Unmarshal(e.Data, &extension) 188 if err != nil { 189 return nil, errors.Trace(err) 190 } 191 return extension, nil 192 case "PSKKeyExchangeModes": 193 var extension *utls.PSKKeyExchangeModesExtension 194 err := json.Unmarshal(e.Data, &extension) 195 if err != nil { 196 return nil, errors.Trace(err) 197 } 198 return extension, nil 199 case "SupportedVersions": 200 var extension *utls.SupportedVersionsExtension 201 err := json.Unmarshal(e.Data, &extension) 202 if err != nil { 203 return nil, errors.Trace(err) 204 } 205 return extension, nil 206 case "ChannelID": 207 return &utls.FakeChannelIDExtension{}, nil 208 case "CertCompressionAlgs": 209 var extension *utls.UtlsCompressCertExtension 210 err := json.Unmarshal(e.Data, &extension) 211 if err != nil { 212 return nil, errors.Trace(err) 213 } 214 return extension, nil 215 case "RecordSizeLimit": 216 var extension *utls.FakeRecordSizeLimitExtension 217 err := json.Unmarshal(e.Data, &extension) 218 if err != nil { 219 return nil, errors.Trace(err) 220 } 221 return extension, nil 222 case "ALPS": 223 var extension *utls.ApplicationSettingsExtension 224 err := json.Unmarshal(e.Data, &extension) 225 if err != nil { 226 return nil, errors.Trace(err) 227 } 228 return extension, nil 229 case "DelegatedCredentials": 230 var extension *utls.DelegatedCredentialsExtension 231 err := json.Unmarshal(e.Data, &extension) 232 if err != nil { 233 return nil, errors.Trace(err) 234 } 235 return extension, nil 236 } 237 238 return nil, errors.Tracef("unknown utls extension: %s", e.Name) 239 }