|
| 1 | +package tlssession |
| 2 | + |
| 3 | +// |
| 4 | +// The functions in this file deal with control messages. These control |
| 5 | +// messages are sent and received over the TLS session once we've gone one |
| 6 | +// established. |
| 7 | +// |
| 8 | +// The control **channel** below us will deal with serializing and deserializing them, |
| 9 | +// what we receive at this stage are the cleartext payloads obtained after decrypting |
| 10 | +// an application data TLS record. |
| 11 | +// |
| 12 | + |
| 13 | +import ( |
| 14 | + "bytes" |
| 15 | + "errors" |
| 16 | + "fmt" |
| 17 | + |
| 18 | + "github.com/ooni/minivpn/internal/bytesx" |
| 19 | + "github.com/ooni/minivpn/internal/model" |
| 20 | + "github.com/ooni/minivpn/internal/session" |
| 21 | +) |
| 22 | + |
| 23 | +// encodeClientControlMessage returns a byte array with the payload for a control channel packet. |
| 24 | +// This is the packet that the client sends to the server with the key |
| 25 | +// material, local options and credentials (if username+password authentication is used). |
| 26 | +func encodeClientControlMessageAsBytes(k *session.KeySource, o *model.Options) ([]byte, error) { |
| 27 | + opt, err := bytesx.EncodeOptionStringToBytes(o.ServerOptionsString()) |
| 28 | + if err != nil { |
| 29 | + return nil, err |
| 30 | + } |
| 31 | + user, err := bytesx.EncodeOptionStringToBytes(string(o.Username)) |
| 32 | + if err != nil { |
| 33 | + return nil, err |
| 34 | + } |
| 35 | + pass, err := bytesx.EncodeOptionStringToBytes(string(o.Password)) |
| 36 | + if err != nil { |
| 37 | + return nil, err |
| 38 | + } |
| 39 | + |
| 40 | + var out bytes.Buffer |
| 41 | + out.Write(controlMessageHeader) |
| 42 | + out.WriteByte(0x02) // key method (2) |
| 43 | + out.Write(k.Bytes()) |
| 44 | + out.Write(opt) |
| 45 | + out.Write(user) |
| 46 | + out.Write(pass) |
| 47 | + |
| 48 | + // we could send IV_PLAT too, but afaik declaring the platform does not |
| 49 | + // make any difference for our purposes. |
| 50 | + rawInfo := fmt.Sprintf("IV_VER=%s\nIV_PROTO=%s\n", ivVer, ivProto) |
| 51 | + peerInfo, _ := bytesx.EncodeOptionStringToBytes(rawInfo) |
| 52 | + out.Write(peerInfo) |
| 53 | + return out.Bytes(), nil |
| 54 | +} |
| 55 | + |
| 56 | +// controlMessageHeader is the header prefixed to control messages |
| 57 | +var controlMessageHeader = []byte{0x00, 0x00, 0x00, 0x00} |
| 58 | + |
| 59 | +const ivVer = "2.5.5" // OpenVPN version compat that we declare to the server |
| 60 | +const ivProto = "2" // IV_PROTO declared to the server. We need to be sure to enable the peer-id bit to use P_DATA_V2. |
| 61 | + |
| 62 | +// errMissingHeader indicates that we're missing the four-byte all-zero header. |
| 63 | +var errMissingHeader = errors.New("missing four-byte all-zero header") |
| 64 | + |
| 65 | +// errInvalidHeader indicates that the header is not a sequence of four zeroed bytes. |
| 66 | +var errInvalidHeader = errors.New("expected four-byte all-zero header") |
| 67 | + |
| 68 | +// errBadControlMessage indicates that a control message cannot be parsed. |
| 69 | +var errBadControlMessage = errors.New("cannot parse control message") |
| 70 | + |
| 71 | +// errBadKeyMethod indicates we don't support a key method |
| 72 | +var errBadKeyMethod = errors.New("unsupported key method") |
| 73 | + |
| 74 | +// parseControlMessage gets a server control message and returns the value for |
| 75 | +// the remote key, the server remote options, and an error indicating if the |
| 76 | +// operation could not be completed. |
| 77 | +func parseServerControlMessage(message []byte) (*session.KeySource, string, error) { |
| 78 | + if len(message) < 4 { |
| 79 | + return nil, "", errMissingHeader |
| 80 | + } |
| 81 | + if !bytes.Equal(message[:4], controlMessageHeader) { |
| 82 | + return nil, "", errInvalidHeader |
| 83 | + } |
| 84 | + // TODO(ainghazal): figure out why 71 here |
| 85 | + if len(message) < 71 { |
| 86 | + return nil, "", fmt.Errorf("%w: bad len from server:%d", errBadControlMessage, len(message)) |
| 87 | + } |
| 88 | + keyMethod := message[4] |
| 89 | + if keyMethod != 2 { |
| 90 | + return nil, "", fmt.Errorf("%w: %d", errBadKeyMethod, keyMethod) |
| 91 | + |
| 92 | + } |
| 93 | + var random1, random2 [32]byte |
| 94 | + // first chunk of random bytes |
| 95 | + copy(random1[:], message[5:37]) |
| 96 | + // second chunk of random bytes |
| 97 | + copy(random2[:], message[37:69]) |
| 98 | + |
| 99 | + options, err := bytesx.DecodeOptionStringFromBytes(message[69:]) |
| 100 | + if err != nil { |
| 101 | + return nil, "", fmt.Errorf("%w:%s", errBadControlMessage, "bad options string") |
| 102 | + } |
| 103 | + |
| 104 | + remoteKey := &session.KeySource{ |
| 105 | + R1: random1, |
| 106 | + R2: random2, |
| 107 | + PreMaster: [48]byte{}, |
| 108 | + } |
| 109 | + return remoteKey, options, nil |
| 110 | +} |
| 111 | + |
| 112 | +// serverBadAuth indicates that the authentication failed |
| 113 | +var serverBadAuth = []byte("AUTH_FAILED") |
| 114 | + |
| 115 | +// serverPushReply is the response for a successful push request |
| 116 | +var serverPushReply = []byte("PUSH_REPLY") |
| 117 | + |
| 118 | +// errBadAuth means we could not authenticate |
| 119 | +var errBadAuth = errors.New("server says: bad auth") |
| 120 | + |
| 121 | +// errBadServerReply indicates we didn't get one of the few responses we expected |
| 122 | +var errBadServerReply = errors.New("bad server reply") |
| 123 | + |
| 124 | +// parseServerPushReply parses the push reply |
| 125 | +func parseServerPushReply(logger model.Logger, resp []byte) (*model.TunnelInfo, error) { |
| 126 | + // make sure the server's response contains the expected result |
| 127 | + if bytes.HasPrefix(resp, serverBadAuth) { |
| 128 | + return nil, errBadAuth |
| 129 | + } |
| 130 | + if !bytes.HasPrefix(resp, serverPushReply) { |
| 131 | + return nil, fmt.Errorf("%w:%s", errBadServerReply, "expected push reply") |
| 132 | + } |
| 133 | + |
| 134 | + // TODO(bassosimone): consider moving the two functions below in this package |
| 135 | + optsMap := model.PushedOptionsAsMap(resp) |
| 136 | + logger.Infof("Server pushed options: %v", optsMap) |
| 137 | + ti := model.NewTunnelInfoFromPushedOptions(optsMap) |
| 138 | + return ti, nil |
| 139 | +} |
0 commit comments