commit 6cd81ec42f203585c59e610dc16728cb0a5d1455 Author: Yawning Angel yawning@torproject.org Date: Wed Oct 1 19:00:30 2014 +0000
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. --- ChangeLog | 7 ++++++ README.md | 4 +-- transports/obfs4/obfs4.go | 51 ++++++++++++++++++++++++------------- transports/obfs4/statefile.go | 56 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 94 insertions(+), 24 deletions(-)
diff --git a/ChangeLog b/ChangeLog index 5fa836d..8fc2275 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +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 diff --git a/README.md b/README.md index fdcb34b..40d092d 100644 --- a/README.md +++ b/README.md @@ -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
diff --git a/transports/obfs4/obfs4.go b/transports/obfs4/obfs4.go index b862e5a..256f549 100644 --- a/transports/obfs4/obfs4.go +++ b/transports/obfs4/obfs4.go @@ -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. - 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 + 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) + } + 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) diff --git a/transports/obfs4/statefile.go b/transports/obfs4/statefile.go index 1e00f32..0838180 100644 --- a/transports/obfs4/statefile.go +++ b/transports/obfs4/statefile.go @@ -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 {