Commit 6cd81ec4 authored by Yawning Angel's avatar Yawning Angel
Browse files

Change the bridge line format to be more compact.

Instead of "node-id" and "public-key" that are Base16 encoded, use
"cert" which contains the "node-id" and "public-key" in Base64 encoded
form.  This is more compact and cuts the length down by 49 characters.
parent 213495d3
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
Changes in version 0.0.3 - UNRELEASED
 - Change the obfs4 bridge line format to use a "cert" argument instead of the
   previous "node-id" and "public-key" arguments.  The "cert" consists of the
   Base64 encoded concatenation of the node ID and public key, with the
   trailing padding removed.  Old style separated bridge lines are still valid,
   but the newer representation is slightly more compact.

Changes in version 0.0.2 - 2014-09-26
 - Write an example client bridge line suitable for use with the running obfs4
   server instance to "obfs4_bridgeline.txt" for the convenience of bridge
+2 −2
Original line number Diff line number Diff line
@@ -82,8 +82,8 @@ ServerTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy
   appropriate.

 * The autogenerated obfs4 bridge parameters are placed in
   `DataDir/pt_state/obfs4_state.json`.  An obfs4 bridge line requires the
   `node-id`, `public-key` and `iat-mode` arguments.
   `DataDir/pt_state/obfs4_state.json`.  To ease deployment, the client side
   bridge line is written to `DataDir/pt_state/obfs4_bridgeline.txt`.

### Thanks

+33 −18
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ const (
	privateKeyArg = "private-key"
	seedArg       = "drbg-seed"
	iatArg        = "iat-mode"
	certArg       = "cert"

	biasCmdArg = "obfs4-distBias"

@@ -122,8 +123,7 @@ func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFa

	// Store the arguments that should appear in our descriptor for the clients.
	ptArgs := pt.Args{}
	ptArgs.Add(nodeIDArg, st.nodeID.Hex())
	ptArgs.Add(publicKeyArg, st.identityKey.Public().Hex())
	ptArgs.Add(certArg, st.cert.String())
	ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))

	// Initialize the replay filter.
@@ -154,15 +154,39 @@ func (cf *obfs4ClientFactory) Transport() base.Transport {
func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
	var err error

	// Handle the arguments.
	var nodeID *ntor.NodeID
	var publicKey *ntor.PublicKey

	// The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument
	// for the Node ID and Public Key.
	certStr, ok := args.Get(certArg)
	if ok {
		var cert *obfs4ServerCert
		if cert, err = serverCertFromString(certStr); err != nil {
			return nil, err
		}
		nodeID, publicKey = cert.unpack()
	} else {
		// The "old" style (version <= 0.0.2) bridge lines use separate Node ID
		// and Public Key arguments in Base16 encoding and are a UX disaster.
		nodeIDStr, ok := args.Get(nodeIDArg)
		if !ok {
			return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
		}
	var nodeID *ntor.NodeID
		if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
			return nil, err
		}

		publicKeyStr, ok := args.Get(publicKeyArg)
		if !ok {
			return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
		}
		if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
			return nil, err
		}
	}

	// IAT config is common across the two bridge line formats.
	iatStr, ok := args.Get(iatArg)
	if !ok {
		return nil, fmt.Errorf("missing argument '%s'", iatArg)
@@ -173,15 +197,6 @@ func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
		return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
	}

	publicKeyStr, ok := args.Get(publicKeyArg)
	if !ok {
		return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
	}
	var publicKey *ntor.PublicKey
	if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
		return nil, err
	}

	// Generate the session key pair before connectiong to hide the Elligator2
	// rejection sampling from network observers.
	sessionKey, err := ntor.NewKeypair(true)
+52 −4
Original line number Diff line number Diff line
@@ -28,12 +28,14 @@
package obfs4

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"

	"git.torproject.org/pluggable-transports/goptlib.git"
	"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
@@ -44,6 +46,9 @@ import (
const (
	stateFile  = "obfs4_state.json"
	bridgeFile = "obfs4_bridgeline.txt"

	certSuffix = "=="
	certLength = ntor.NodeIDLength + ntor.PublicKeyLength
)

type jsonServerState struct {
@@ -54,11 +59,55 @@ type jsonServerState struct {
	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) {
@@ -112,6 +161,7 @@ func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs
		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 {
@@ -190,10 +240,8 @@ func newBridgeFile(stateDir string, st *obfs4ServerState) (err error) {
		"#  <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> node-id=%s public-key=%s iat-mode=%d\n",
		st.nodeID.Hex(),
		st.identityKey.Public().Hex(),
		st.iatMode)
	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 {