//Package for communication with the snowflake broker // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" package messages import ( "bytes" "encoding/base64" "encoding/json" "fmt" "github.com/fxamacker/cbor" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" ) const ClientVersion = "1.0" /* Client--Broker protocol v1.x specification: All messages contain the version number followed by a new line and then the message body := \n := . := | There are two different types of body messages, each encoded in JSON format == ClientPollRequest == := { offer: [nat: (unknown|restricted|unrestricted)] [fingerprint: ] } The NAT field is optional, and if it is missing a value of "unknown" will be assumed. The fingerprint is also optional and, if absent, will be assigned the fingerprint of the default bridge. == ClientPollResponse == := { [answer: ] [error: ] } If the broker succeeded in matching the client with a proxy, the answer field MUST contain a valid SDP answer, and the error field MUST be empty. If the answer field is empty, the error field MUST contain a string explaining with a reason for the error. */ // The bridge fingerprint to assume, for client poll requests that do not // specify a fingerprint. Before #28651, there was only one bridge with one // fingerprint, which all clients expected to be connected to implicitly. // If a client is old enough that it does not specify a fingerprint, this is // the fingerprint it expects. Clients that do set a fingerprint in the // SOCKS params will also be assumed to want to connect to the default bridge. const defaultBridgeFingerprint = "2B280B23E1107BB62ABFC40DDCC8824814F80A72" type ClientPollRequest struct { Offer string `json:"offer"` NAT string `json:"nat"` Fingerprint string `json:"fingerprint"` } // Encodes a poll message from a snowflake client func (req *ClientPollRequest) EncodeClientPollRequest() ([]byte, error) { if req.Fingerprint == "" { req.Fingerprint = defaultBridgeFingerprint } body, err := json.Marshal(req) if err != nil { return nil, err } return append([]byte(ClientVersion+"\n"), body...), nil } // Decodes a poll message from a snowflake client func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) { parts := bytes.SplitN(data, []byte("\n"), 2) if len(parts) < 2 { // no version number found return nil, fmt.Errorf("unsupported message version") } var message ClientPollRequest if string(parts[0]) != ClientVersion { return nil, fmt.Errorf("unsupported message version") } err := json.Unmarshal(parts[1], &message) if err != nil { return nil, err } if message.Offer == "" { return nil, fmt.Errorf("no supplied offer") } if message.Fingerprint == "" { message.Fingerprint = defaultBridgeFingerprint } if _, err := bridgefingerprint.FingerprintFromHexString(message.Fingerprint); err != nil { return nil, fmt.Errorf("cannot decode fingerprint") } switch message.NAT { case "": message.NAT = nat.NATUnknown case nat.NATUnknown: case nat.NATRestricted: case nat.NATUnrestricted: default: return nil, fmt.Errorf("invalid NAT type") } return &message, nil } type ClientPollResponse struct { Answer string `json:"answer,omitempty"` Error string `json:"error,omitempty"` } // Encodes a poll response for a snowflake client func (resp *ClientPollResponse) EncodePollResponse() ([]byte, error) { return json.Marshal(resp) } // Decodes a poll response for a snowflake client // If the Error field is empty, the Answer should be non-empty func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) { var message ClientPollResponse err := json.Unmarshal(data, &message) if err != nil { return nil, err } if message.Error == "" && message.Answer == "" { return nil, fmt.Errorf("received empty broker response") } return &message, nil } // ClientConnectionMetadata is a struct that contains metadata about a snowflake connection between client and server // It will be sent from the client to the proxy in WebRTC data channel protocol string // The proxy will then send the metadata to the server in the protocol get parameter of the WebSocket connection type ClientConnectionMetadata struct { ClientID []byte `json:"client_id"` } func (meta *ClientConnectionMetadata) EncodeConnectionMetadata() (string, error) { jsonData, err := cbor.Marshal(meta, cbor.CanonicalEncOptions()) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(jsonData), nil } func DecodeConnectionMetadata(data string) (*ClientConnectionMetadata, error) { decodedData, err := base64.RawURLEncoding.DecodeString(data) if err != nil { return nil, err } var meta ClientConnectionMetadata err = cbor.Unmarshal(decodedData, &meta) if err != nil { return nil, err } return &meta, nil }