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.
This commit is contained in:
Yawning Angel 2014-10-01 19:00:30 +00:00
parent 213495d3b9
commit 6cd81ec42f
4 changed files with 94 additions and 24 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 {