| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- /*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
- package obfs4
- import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "strconv"
- "strings"
- "git.torproject.org/pluggable-transports/goptlib.git"
- "gitlab.com/yawning/obfs4.git/common/csrand"
- "gitlab.com/yawning/obfs4.git/common/drbg"
- "gitlab.com/yawning/obfs4.git/common/ntor"
- )
- const (
- stateFile = "obfs4_state.json"
- bridgeFile = "obfs4_bridgeline.txt"
- certSuffix = "=="
- certLength = ntor.NodeIDLength + ntor.PublicKeyLength
- )
- type jsonServerState struct {
- NodeID string `json:"node-id"`
- PrivateKey string `json:"private-key"`
- PublicKey string `json:"public-key"`
- DrbgSeed string `json:"drbg-seed"`
- IATMode int `json:"iat-mode"`
- }
- type obfs4ServerCert struct {
- raw []byte
- }
- func (cert *obfs4ServerCert) String() string {
- return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix)
- }
- func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) {
- if len(cert.raw) != certLength {
- panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw)))
- }
- nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength])
- pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:])
- return nodeID, pubKey
- }
- func serverCertFromString(encoded string) (*obfs4ServerCert, error) {
- decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix)
- if err != nil {
- return nil, fmt.Errorf("failed to decode cert: %s", err)
- }
- if len(decoded) != certLength {
- return nil, fmt.Errorf("cert length %d is invalid", len(decoded))
- }
- return &obfs4ServerCert{raw: decoded}, nil
- }
- func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert {
- cert := new(obfs4ServerCert)
- cert.raw = append(st.nodeID.Bytes()[:], st.identityKey.Public().Bytes()[:]...)
- return cert
- }
- type obfs4ServerState struct {
- nodeID *ntor.NodeID
- identityKey *ntor.Keypair
- drbgSeed *drbg.Seed
- iatMode int
- cert *obfs4ServerCert
- }
- func (st *obfs4ServerState) clientString() string {
- return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode)
- }
- func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
- var js jsonServerState
- var nodeIDOk, privKeyOk, seedOk bool
- js.NodeID, nodeIDOk = args.Get(nodeIDArg)
- js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
- js.DrbgSeed, seedOk = args.Get(seedArg)
- iatStr, iatOk := args.Get(iatArg)
- // Either a private key, node id, and seed are ALL specified, or
- // they should be loaded from the state file.
- if !privKeyOk && !nodeIDOk && !seedOk {
- if err := jsonServerStateFromFile(stateDir, &js); err != nil {
- return nil, err
- }
- } else if !privKeyOk {
- return nil, fmt.Errorf("missing argument '%s'", privateKeyArg)
- } else if !nodeIDOk {
- return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
- } else if !seedOk {
- return nil, fmt.Errorf("missing argument '%s'", seedArg)
- }
- // The IAT mode should be independently configurable.
- if iatOk {
- // If the IAT mode is specified, attempt to parse and apply it
- // as an override.
- iatMode, err := strconv.Atoi(iatStr)
- if err != nil {
- return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr)
- }
- js.IATMode = iatMode
- }
- return serverStateFromJSONServerState(stateDir, &js)
- }
- func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) {
- var err error
- st := new(obfs4ServerState)
- if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil {
- return nil, err
- }
- if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil {
- return nil, err
- }
- if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil {
- return nil, err
- }
- if js.IATMode < iatNone || js.IATMode > iatParanoid {
- return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode)
- }
- st.iatMode = js.IATMode
- st.cert = serverCertFromState(st)
- // Generate a human readable summary of the configured endpoint.
- if err = newBridgeFile(stateDir, st); err != nil {
- return nil, err
- }
- // Write back the possibly updated server state.
- return st, writeJSONServerState(stateDir, js)
- }
- func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
- fPath := path.Join(stateDir, stateFile)
- f, err := ioutil.ReadFile(fPath)
- if err != nil {
- if os.IsNotExist(err) {
- if err = newJSONServerState(stateDir, js); err == nil {
- return nil
- }
- }
- return err
- }
- if err := json.Unmarshal(f, js); err != nil {
- return fmt.Errorf("failed to load statefile '%s': %s", fPath, err)
- }
- return nil
- }
- func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
- // Generate everything a server needs, using the cryptographic PRNG.
- var st obfs4ServerState
- rawID := make([]byte, ntor.NodeIDLength)
- if err = csrand.Bytes(rawID); err != nil {
- return
- }
- if st.nodeID, err = ntor.NewNodeID(rawID); err != nil {
- return
- }
- if st.identityKey, err = ntor.NewKeypair(false); err != nil {
- return
- }
- if st.drbgSeed, err = drbg.NewSeed(); err != nil {
- return
- }
- st.iatMode = iatNone
- // Encode it into JSON format and write the state file.
- js.NodeID = st.nodeID.Hex()
- js.PrivateKey = st.identityKey.Private().Hex()
- js.PublicKey = st.identityKey.Public().Hex()
- js.DrbgSeed = st.drbgSeed.Hex()
- js.IATMode = st.iatMode
- return writeJSONServerState(stateDir, js)
- }
- func writeJSONServerState(stateDir string, js *jsonServerState) error {
- var err error
- var encoded []byte
- if encoded, err = json.Marshal(js); err != nil {
- return err
- }
- if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
- return err
- }
- return nil
- }
- func newBridgeFile(stateDir string, st *obfs4ServerState) error {
- const prefix = "# obfs4 torrc client bridge line\n" +
- "#\n" +
- "# This file is an automatically generated bridge line based on\n" +
- "# the current obfs4proxy configuration. EDITING IT WILL HAVE\n" +
- "# NO EFFECT.\n" +
- "#\n" +
- "# Before distributing this Bridge, edit the placeholder fields\n" +
- "# to contain the actual values:\n" +
- "# <IP ADDRESS> - The public IP address of your obfs4 bridge.\n" +
- "# <PORT> - The TCP/IP port of your obfs4 bridge.\n" +
- "# <FINGERPRINT> - The bridge's fingerprint.\n\n"
- bridgeLine := fmt.Sprintf("Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> %s\n",
- st.clientString())
- tmp := []byte(prefix + bridgeLine)
- if err := ioutil.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0600); err != nil {
- return err
- }
- return nil
- }
|