diff options
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go')
-rw-r--r-- | vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go new file mode 100644 index 0000000..b8b8fa0 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go @@ -0,0 +1,260 @@ +/* + * 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" + + "github.com/OperatorFoundation/obfs4/common/csrand" + "github.com/OperatorFoundation/obfs4/common/drbg" + "github.com/OperatorFoundation/obfs4/common/ntor" + "github.com/OperatorFoundation/shapeshifter-ipc" +) + +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 +} |