From 724b17ec5b25b57b477fd936ad8e52a4f18761e5 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 11 Mar 2022 12:39:28 -0500 Subject: obfsvpn: support gateway certificates Support creating dialers and listeners from the newer gateway certificates, as well as setting the cert argument when connecting. Signed-off-by: Sam Whited --- dialer.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- listener.go | 13 +++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/dialer.go b/dialer.go index a7bd075..444e0c2 100644 --- a/dialer.go +++ b/dialer.go @@ -2,6 +2,7 @@ package obfsvpn import ( "context" + "encoding/base64" "fmt" "net" "strconv" @@ -16,6 +17,11 @@ const ( ptArgNode = "node-id" ptArgKey = "public-key" ptArgMode = "iat-mode" + ptArgCert = "cert" +) + +const ( + certLength = ntor.NodeIDLength + ntor.PublicKeyLength ) // IATMode determines the amount of time sent between packets. @@ -42,8 +48,49 @@ type Dialer struct { cf base.ClientFactory } -// NewDialer creates a dialer from existing pluggable transport arguments. -func NewDialer(args pt.Args) (*Dialer, error) { +func packCert(node *ntor.NodeID, public *ntor.PublicKey) string { + cert := make([]byte, 0, certLength) + cert = append(cert, node[:]...) + cert = append(cert, public[:]...) + + return base64.RawStdEncoding.EncodeToString(cert) +} + +func unpackCert(cert string) (*ntor.NodeID, *ntor.PublicKey, error) { + if l := base64.RawStdEncoding.DecodedLen(len(cert)); l != certLength { + return nil, nil, fmt.Errorf("cert length %d is invalid", l) + } + decoded, err := base64.RawStdEncoding.DecodeString(cert) + if err != nil { + return nil, nil, err + } + + nodeID, err := ntor.NewNodeID(decoded[:ntor.NodeIDLength]) + if err != nil { + return nil, nil, err + } + pubKey, err := ntor.NewPublicKey(decoded[ntor.NodeIDLength:]) + if err != nil { + return nil, nil, err + } + return nodeID, pubKey, nil +} + +// NewDialerCert creates a dialer from a node certificate. +func NewDialerCert(cert string) (*Dialer, error) { + nodeID, publicKey, err := unpackCert(cert) + + if err != nil { + return nil, err + } + return &Dialer{ + NodeID: nodeID, + PublicKey: publicKey, + }, nil +} + +// NewDialerArgs creates a dialer from existing pluggable transport arguments. +func NewDialerArgs(args pt.Args) (*Dialer, error) { cf, err := (&obfs4.Transport{}).ClientFactory("") if err != nil { return nil, err @@ -93,12 +140,16 @@ func (d *Dialer) Dial(ctx context.Context, network, address string) (net.Conn, e } // Args returns the dialers options as pluggable transport arguments. +// The args include valid args for the "new" (version >= 0.0.3) bridge lines +// that use a unified "cert" argument as well as the legacy lines that use a +// separate Node ID and Public Key. func (d *Dialer) Args() pt.Args { if d.ptArgs == nil { d.ptArgs = make(pt.Args) d.ptArgs.Add(ptArgNode, d.NodeID.Hex()) d.ptArgs.Add(ptArgKey, d.PublicKey.Hex()) d.ptArgs.Add(ptArgMode, strconv.Itoa(int(d.IATMode))) + d.ptArgs.Add(ptArgCert, packCert(d.NodeID, d.PublicKey)) } return d.ptArgs } diff --git a/listener.go b/listener.go index 67a25e7..4163cb9 100644 --- a/listener.go +++ b/listener.go @@ -25,6 +25,19 @@ type ListenConfig struct { StateDir string } +// NewListenConfigCert creates a listener config by unpacking the node ID from +// its certificate. +// The private key must still be specified. +func NewListenConfigCert(cert string) (*ListenConfig, error) { + nodeID, _, err := unpackCert(cert) + if err != nil { + return nil, err + } + return &ListenConfig{ + NodeID: nodeID, + }, nil +} + // Listen announces on the local network address. // // See func net.Dial for a description of the network and address parameters. -- cgit v1.2.3