commit 6cd81ec42f203585c59e610dc16728cb0a5d1455
Author: Yawning Angel <[email protected]>
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 {

_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to