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  }