From c28e6d13f920b1356007fba7fbe9be38c4618dfc Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 12 May 2019 02:17:57 -0400 Subject: [PATCH 1/5] implement ip cloaking --- irc/client.go | 12 +++++- irc/cloak_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++++ irc/config.go | 35 ++++++++++++++++ irc/gateways.go | 2 + irc/server.go | 28 +++++++++++++ irc/utils/crypto.go | 4 +- oragono.yaml | 39 ++++++++++++++++++ 7 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 irc/cloak_test.go diff --git a/irc/client.go b/irc/client.go index 2ed9bcc8..a9436504 100644 --- a/irc/client.go +++ b/irc/client.go @@ -70,6 +70,7 @@ type Client struct { preregNick string proxiedIP net.IP // actual remote IP if using the PROXY protocol rawHostname string + cloakedHostname string realname string realIP net.IP registered bool @@ -215,6 +216,7 @@ func RunNewClient(server *Server, conn clientConn) { session.realIP = utils.AddrToIP(remoteAddr) // set the hostname for this client (may be overridden later by PROXY or WEBIRC) session.rawHostname = utils.LookupHostname(session.realIP.String()) + client.cloakedHostname = config.Server.Cloaks.ComputeCloak(session.realIP) if utils.AddrIsLocal(remoteAddr) { // treat local connections as secure (may be overridden later by WEBIRC) client.SetMode(modes.TLS, true) @@ -812,7 +814,10 @@ func (client *Client) updateNick(nick, nickCasefolded, skeleton string) { func (client *Client) updateNickMaskNoMutex() { client.hostname = client.getVHostNoMutex() if client.hostname == "" { - client.hostname = client.rawHostname + client.hostname = client.cloakedHostname + if client.hostname == "" { + client.hostname = client.rawHostname + } } cfhostname, err := Casefold(client.hostname) @@ -831,6 +836,7 @@ func (client *Client) AllNickmasks() (masks []string) { nick := client.nickCasefolded username := client.username rawHostname := client.rawHostname + cloakedHostname := client.cloakedHostname vhost := client.getVHostNoMutex() client.stateMutex.RUnlock() username = strings.ToLower(username) @@ -849,6 +855,10 @@ func (client *Client) AllNickmasks() (masks []string) { masks = append(masks, rawhostmask) } + if cloakedHostname != "" { + masks = append(masks, fmt.Sprintf("%s!%s@%s", nick, username, cloakedHostname)) + } + ipmask := fmt.Sprintf("%s!%s@%s", nick, username, client.IPString()) if ipmask != rawhostmask { masks = append(masks, ipmask) diff --git a/irc/cloak_test.go b/irc/cloak_test.go new file mode 100644 index 00000000..49271fdd --- /dev/null +++ b/irc/cloak_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2019 Shivaram Lingamneni +// released under the MIT license + +package irc + +import ( + "net" + "testing" +) + +func easyParseIP(ipstr string) (result net.IP) { + result = net.ParseIP(ipstr) + if result == nil { + panic(ipstr) + } + return +} + +func cloakConfForTesting() CloakConfig { + config := CloakConfig{ + Enabled: true, + Netname: "oragono", + Secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg", + CidrLenIPv4: 32, + CidrLenIPv6: 64, + NumBits: 80, + } + config.postprocess() + return config +} + +func TestCloakDeterminism(t *testing.T) { + config := cloakConfForTesting() + + v4ip := easyParseIP("8.8.8.8").To4() + assertEqual(config.ComputeCloak(v4ip), "d2z5guriqhzwazyr.oragono", t) + // use of the 4-in-6 mapping should not affect the cloak + v6mappedIP := v4ip.To16() + assertEqual(config.ComputeCloak(v6mappedIP), "d2z5guriqhzwazyr.oragono", t) + + v6ip := easyParseIP("2001:0db8::1") + assertEqual(config.ComputeCloak(v6ip), "w7ren6nxii6f3i3d.oragono", t) + // same CIDR, so same cloak: + v6ipsamecidr := easyParseIP("2001:0db8::2") + assertEqual(config.ComputeCloak(v6ipsamecidr), "w7ren6nxii6f3i3d.oragono", t) + v6ipdifferentcidr := easyParseIP("2001:0db9::1") + // different CIDR, different cloak: + assertEqual(config.ComputeCloak(v6ipdifferentcidr), "ccmptyrjwsxv4f4d.oragono", t) + + // cloak values must be sensitive to changes in the secret key + config.Secret = "HJcXK4lLawxBE4-9SIdPji_21YiL3N5r5f5-SPNrGVY" + assertEqual(config.ComputeCloak(v4ip), "4khy3usk8mfu42pe.oragono", t) + assertEqual(config.ComputeCloak(v6mappedIP), "4khy3usk8mfu42pe.oragono", t) + assertEqual(config.ComputeCloak(v6ip), "mxpk3c83vdxkek9j.oragono", t) + assertEqual(config.ComputeCloak(v6ipsamecidr), "mxpk3c83vdxkek9j.oragono", t) +} + +func TestCloakShortv4Cidr(t *testing.T) { + config := CloakConfig{ + Enabled: true, + Netname: "oragono", + Secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg", + CidrLenIPv4: 24, + CidrLenIPv6: 64, + NumBits: 60, + } + config.postprocess() + + v4ip := easyParseIP("8.8.8.8") + assertEqual(config.ComputeCloak(v4ip), "3cay3zc72tnui.oragono", t) + v4ipsamecidr := easyParseIP("8.8.8.9") + assertEqual(config.ComputeCloak(v4ipsamecidr), "3cay3zc72tnui.oragono", t) +} + +func TestCloakZeroBits(t *testing.T) { + config := cloakConfForTesting() + config.NumBits = 0 + config.Netname = "example.com" + config.postprocess() + + v4ip := easyParseIP("8.8.8.8").To4() + assertEqual(config.ComputeCloak(v4ip), "example.com", t) +} + +func TestCloakDisabled(t *testing.T) { + config := cloakConfForTesting() + config.Enabled = false + v4ip := easyParseIP("8.8.8.8").To4() + assertEqual(config.ComputeCloak(v4ip), "", t) +} + +func BenchmarkCloaks(b *testing.B) { + config := cloakConfForTesting() + v6ip := easyParseIP("2001:0db8::1") + b.ResetTimer() + for i := 0; i < b.N; i++ { + config.ComputeCloak(v6ip) + } +} diff --git a/irc/config.go b/irc/config.go index 597b9309..639dc7a4 100644 --- a/irc/config.go +++ b/irc/config.go @@ -263,6 +263,38 @@ type TorListenersConfig struct { MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"` } +type CloakConfig struct { + Enabled bool + Netname string + Secret string + CidrLenIPv4 int `yaml:"cidr-len-ipv4"` + CidrLenIPv6 int `yaml:"cidr-len-ipv6"` + NumBits int `yaml:"num-bits"` + + numBytes int + ipv4Mask net.IPMask + ipv6Mask net.IPMask +} + +func (cloakConfig *CloakConfig) postprocess() { + // sanity checks: + numBits := cloakConfig.NumBits + if 0 == numBits { + numBits = 80 + } else if 256 < numBits { + numBits = 256 + } + + // derived values: + cloakConfig.numBytes = numBits / 8 + // round up to the nearest byte + if numBits%8 != 0 { + cloakConfig.numBytes += 1 + } + cloakConfig.ipv4Mask = net.CIDRMask(cloakConfig.CidrLenIPv4, 32) + cloakConfig.ipv6Mask = net.CIDRMask(cloakConfig.CidrLenIPv6, 128) +} + // Config defines the overall configuration. type Config struct { Network struct { @@ -297,6 +329,7 @@ type Config struct { isupport isupport.List ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` + Cloaks CloakConfig `yaml:"ip-cloaking"` } Languages struct { @@ -728,6 +761,8 @@ func LoadConfig(filename string) (config *Config, err error) { config.History.ClientLength = 0 } + config.Server.Cloaks.postprocess() + for _, listenAddress := range config.Server.TorListeners.Listeners { found := false for _, configuredListener := range config.Server.Listen { diff --git a/irc/gateways.go b/irc/gateways.go index 495dfee0..95f05aee 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -70,6 +70,7 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo ipstring := parsedProxiedIP.String() client.server.logger.Info("localconnect-ip", "Accepted proxy IP for client", ipstring) rawHostname := utils.LookupHostname(ipstring) + cloakedHostname := client.server.Config().Server.Cloaks.ComputeCloak(parsedProxiedIP) client.stateMutex.Lock() defer client.stateMutex.Unlock() @@ -77,6 +78,7 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo client.proxiedIP = parsedProxiedIP session.rawHostname = rawHostname client.rawHostname = rawHostname + client.cloakedHostname = cloakedHostname // nickmask will be updated when the client completes registration // set tls info client.certfp = "" diff --git a/irc/server.go b/irc/server.go index 87ced2b0..ced8cb7c 100644 --- a/irc/server.go +++ b/irc/server.go @@ -21,6 +21,8 @@ import ( "time" "unsafe" + "golang.org/x/crypto/sha3" + "github.com/goshuirc/irc-go/ircfmt" "github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/connection_limits" @@ -283,6 +285,32 @@ func (server *Server) checkTorLimits() (banned bool, message string) { } } +// simple cloaking algorithm: normalize the IP to its CIDR, +// then hash the resulting bytes with a secret key, +// then truncate to the desired length, b32encode, and append the fake TLD. +func (config *CloakConfig) ComputeCloak(ip net.IP) string { + if !config.Enabled { + return "" + } else if config.NumBits == 0 { + return config.Netname + } + var masked net.IP + v4ip := ip.To4() + if v4ip != nil { + masked = v4ip.Mask(config.ipv4Mask) + } else { + masked = ip.Mask(config.ipv6Mask) + } + // SHA3(K || M): + // https://crypto.stackexchange.com/questions/17735/is-hmac-needed-for-a-sha-3-based-mac + input := make([]byte, len(config.Secret)+len(masked)) + copy(input, config.Secret[:]) + copy(input[len(config.Secret):], masked) + digest := sha3.Sum512(input) + b32digest := utils.B32Encoder.EncodeToString(digest[:config.numBytes]) + return fmt.Sprintf("%s.%s", b32digest, config.Netname) +} + // // IRC protocol listeners // diff --git a/irc/utils/crypto.go b/irc/utils/crypto.go index 116f596a..8eb15440 100644 --- a/irc/utils/crypto.go +++ b/irc/utils/crypto.go @@ -11,7 +11,7 @@ import ( var ( // slingamn's own private b32 alphabet, removing 1, l, o, and 0 - b32encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding) + B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding) ) const ( @@ -24,7 +24,7 @@ func GenerateSecretToken() string { var buf [16]byte rand.Read(buf[:]) // 26 ASCII characters, should be fine for most purposes - return b32encoder.EncodeToString(buf[:]) + return B32Encoder.EncodeToString(buf[:]) } // securely check if a supplied token matches a stored token diff --git a/oragono.yaml b/oragono.yaml index 0e5e68e5..9a244cb6 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -188,6 +188,45 @@ server: # - "192.168.1.1" # - "2001:0db8::/32" + # IP cloaking hides users' IP addresses from other users and from channel admins + # (but not from server admins), while still allowing channel admins to ban + # offending IP addresses or networks. In place of hostnames derived from reverse + # DNS, users see fake domain names like pwbs2ui4377257x8.oragono. These names are + # generated deterministically from the underlying IP address, but if the underlying + # IP is not already known, it is infeasible to recover it from the cloaked name. + ip-cloaking: + # whether to enable IP cloaking + enabled: false + + # fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.oragono + netname: "oragono" + + # secret key to prevent dictionary attacks against cloaked IPs + # any high-entropy secret is valid for this purpose: + # you MUST generate a new one for your installation. + # suggestion: use the output of this command: + # python3 -c "import secrets; print(secrets.token_urlsafe())" + # note that rotating this key will invalidate all existing ban masks. + secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" + + # the cloaked hostname is derived only from the CIDR (most significant bits + # of the IP address), up to a configurable number of bits. this is the + # granularity at which bans will take effect for ipv4 (a /32 is a fully + # specified IP address). note that changing this value will invalidate + # any stored bans. + cidr-len-ipv4: 32 + + # analogous value for ipv6 (an ipv6 /64 is the typical prefix assigned + # by an ISP to an individual customer for their LAN) + cidr-len-ipv6: 64 + + # number of bits of hash output to include in the cloaked hostname. + # more bits means less likelihood of distinct IPs colliding, + # at the cost of a longer cloaked hostname. if this value is set to 0, + # all users will receive simply `netname` as their cloaked hostname. + num-bits: 80 + + # account options accounts: # account registration From 2451737f8768ce93a05b8c6a1e62560c27204130 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 12 May 2019 04:01:47 -0400 Subject: [PATCH 2/5] give cloaks their own package --- Makefile | 1 + irc/{ => cloaks}/cloak_test.go | 15 ++++++-- irc/cloaks/cloaks.go | 70 ++++++++++++++++++++++++++++++++++ irc/config.go | 37 ++---------------- irc/server.go | 28 -------------- 5 files changed, 85 insertions(+), 66 deletions(-) rename irc/{ => cloaks}/cloak_test.go (91%) create mode 100644 irc/cloaks/cloaks.go diff --git a/Makefile b/Makefile index 8b35ab9d..a355b42c 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ test: python3 ./gencapdefs.py | diff - ${capdef_file} cd irc && go test . && go vet . cd irc/caps && go test . && go vet . + cd irc/cloaks && go test . && go vet . cd irc/connection_limits && go test . && go vet . cd irc/history && go test . && go vet . cd irc/isupport && go test . && go vet . diff --git a/irc/cloak_test.go b/irc/cloaks/cloak_test.go similarity index 91% rename from irc/cloak_test.go rename to irc/cloaks/cloak_test.go index 49271fdd..f6cf1965 100644 --- a/irc/cloak_test.go +++ b/irc/cloaks/cloak_test.go @@ -1,13 +1,20 @@ // Copyright (c) 2019 Shivaram Lingamneni // released under the MIT license -package irc +package cloaks import ( "net" + "reflect" "testing" ) +func assertEqual(supplied, expected interface{}, t *testing.T) { + if !reflect.DeepEqual(supplied, expected) { + t.Errorf("expected %v but got %v", expected, supplied) + } +} + func easyParseIP(ipstr string) (result net.IP) { result = net.ParseIP(ipstr) if result == nil { @@ -25,7 +32,7 @@ func cloakConfForTesting() CloakConfig { CidrLenIPv6: 64, NumBits: 80, } - config.postprocess() + config.Initialize() return config } @@ -64,7 +71,7 @@ func TestCloakShortv4Cidr(t *testing.T) { CidrLenIPv6: 64, NumBits: 60, } - config.postprocess() + config.Initialize() v4ip := easyParseIP("8.8.8.8") assertEqual(config.ComputeCloak(v4ip), "3cay3zc72tnui.oragono", t) @@ -76,7 +83,7 @@ func TestCloakZeroBits(t *testing.T) { config := cloakConfForTesting() config.NumBits = 0 config.Netname = "example.com" - config.postprocess() + config.Initialize() v4ip := easyParseIP("8.8.8.8").To4() assertEqual(config.ComputeCloak(v4ip), "example.com", t) diff --git a/irc/cloaks/cloaks.go b/irc/cloaks/cloaks.go new file mode 100644 index 00000000..4557a85a --- /dev/null +++ b/irc/cloaks/cloaks.go @@ -0,0 +1,70 @@ +// Copyright (c) 2019 Shivaram Lingamneni + +package cloaks + +import ( + "fmt" + "net" + + "golang.org/x/crypto/sha3" + + "github.com/oragono/oragono/irc/utils" +) + +type CloakConfig struct { + Enabled bool + Netname string + Secret string + CidrLenIPv4 int `yaml:"cidr-len-ipv4"` + CidrLenIPv6 int `yaml:"cidr-len-ipv6"` + NumBits int `yaml:"num-bits"` + + numBytes int + ipv4Mask net.IPMask + ipv6Mask net.IPMask +} + +func (cloakConfig *CloakConfig) Initialize() { + // sanity checks: + numBits := cloakConfig.NumBits + if 0 == numBits { + numBits = 80 + } else if 256 < numBits { + numBits = 256 + } + + // derived values: + cloakConfig.numBytes = numBits / 8 + // round up to the nearest byte + if numBits%8 != 0 { + cloakConfig.numBytes += 1 + } + cloakConfig.ipv4Mask = net.CIDRMask(cloakConfig.CidrLenIPv4, 32) + cloakConfig.ipv6Mask = net.CIDRMask(cloakConfig.CidrLenIPv6, 128) +} + +// simple cloaking algorithm: normalize the IP to its CIDR, +// then hash the resulting bytes with a secret key, +// then truncate to the desired length, b32encode, and append the fake TLD. +func (config *CloakConfig) ComputeCloak(ip net.IP) string { + if !config.Enabled { + return "" + } else if config.NumBits == 0 { + return config.Netname + } + var masked net.IP + v4ip := ip.To4() + if v4ip != nil { + masked = v4ip.Mask(config.ipv4Mask) + } else { + masked = ip.Mask(config.ipv6Mask) + } + // SHA3(K || M): + // https://crypto.stackexchange.com/questions/17735/is-hmac-needed-for-a-sha-3-based-mac + input := make([]byte, len(config.Secret)+len(masked)) + copy(input, config.Secret[:]) + copy(input[len(config.Secret):], masked) + digest := sha3.Sum512(input) + b32digest := utils.B32Encoder.EncodeToString(digest[:config.numBytes]) + return fmt.Sprintf("%s.%s", b32digest, config.Netname) +} diff --git a/irc/config.go b/irc/config.go index 639dc7a4..11121495 100644 --- a/irc/config.go +++ b/irc/config.go @@ -18,6 +18,7 @@ import ( "time" "code.cloudfoundry.org/bytefmt" + "github.com/oragono/oragono/irc/cloaks" "github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/isupport" @@ -263,38 +264,6 @@ type TorListenersConfig struct { MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"` } -type CloakConfig struct { - Enabled bool - Netname string - Secret string - CidrLenIPv4 int `yaml:"cidr-len-ipv4"` - CidrLenIPv6 int `yaml:"cidr-len-ipv6"` - NumBits int `yaml:"num-bits"` - - numBytes int - ipv4Mask net.IPMask - ipv6Mask net.IPMask -} - -func (cloakConfig *CloakConfig) postprocess() { - // sanity checks: - numBits := cloakConfig.NumBits - if 0 == numBits { - numBits = 80 - } else if 256 < numBits { - numBits = 256 - } - - // derived values: - cloakConfig.numBytes = numBits / 8 - // round up to the nearest byte - if numBits%8 != 0 { - cloakConfig.numBytes += 1 - } - cloakConfig.ipv4Mask = net.CIDRMask(cloakConfig.CidrLenIPv4, 32) - cloakConfig.ipv6Mask = net.CIDRMask(cloakConfig.CidrLenIPv6, 128) -} - // Config defines the overall configuration. type Config struct { Network struct { @@ -329,7 +298,7 @@ type Config struct { isupport isupport.List ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` - Cloaks CloakConfig `yaml:"ip-cloaking"` + Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` } Languages struct { @@ -761,7 +730,7 @@ func LoadConfig(filename string) (config *Config, err error) { config.History.ClientLength = 0 } - config.Server.Cloaks.postprocess() + config.Server.Cloaks.Initialize() for _, listenAddress := range config.Server.TorListeners.Listeners { found := false diff --git a/irc/server.go b/irc/server.go index ced8cb7c..87ced2b0 100644 --- a/irc/server.go +++ b/irc/server.go @@ -21,8 +21,6 @@ import ( "time" "unsafe" - "golang.org/x/crypto/sha3" - "github.com/goshuirc/irc-go/ircfmt" "github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/connection_limits" @@ -285,32 +283,6 @@ func (server *Server) checkTorLimits() (banned bool, message string) { } } -// simple cloaking algorithm: normalize the IP to its CIDR, -// then hash the resulting bytes with a secret key, -// then truncate to the desired length, b32encode, and append the fake TLD. -func (config *CloakConfig) ComputeCloak(ip net.IP) string { - if !config.Enabled { - return "" - } else if config.NumBits == 0 { - return config.Netname - } - var masked net.IP - v4ip := ip.To4() - if v4ip != nil { - masked = v4ip.Mask(config.ipv4Mask) - } else { - masked = ip.Mask(config.ipv6Mask) - } - // SHA3(K || M): - // https://crypto.stackexchange.com/questions/17735/is-hmac-needed-for-a-sha-3-based-mac - input := make([]byte, len(config.Secret)+len(masked)) - copy(input, config.Secret[:]) - copy(input[len(config.Secret):], masked) - digest := sha3.Sum512(input) - b32digest := utils.B32Encoder.EncodeToString(digest[:config.numBytes]) - return fmt.Sprintf("%s.%s", b32digest, config.Netname) -} - // // IRC protocol listeners // From 11076be0ee7c0c223d3dd3623417f5175a42ab06 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 12 May 2019 16:26:23 -0400 Subject: [PATCH 3/5] review fixes --- irc/config.go | 5 +++++ irc/utils/crypto.go | 8 ++++++++ oragono.go | 4 ++++ oragono.yaml | 3 +-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/irc/config.go b/irc/config.go index 11121495..fb0aff27 100644 --- a/irc/config.go +++ b/irc/config.go @@ -731,6 +731,11 @@ func LoadConfig(filename string) (config *Config, err error) { } config.Server.Cloaks.Initialize() + if config.Server.Cloaks.Enabled { + if config.Server.Cloaks.Secret == "" || config.Server.Cloaks.Secret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" { + return nil, fmt.Errorf("You must generate a new value of ip-cloaking.secret to enable cloaking") + } + } for _, listenAddress := range config.Server.TorListeners.Listeners { found := false diff --git a/irc/utils/crypto.go b/irc/utils/crypto.go index 8eb15440..010b3401 100644 --- a/irc/utils/crypto.go +++ b/irc/utils/crypto.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "crypto/subtle" "encoding/base32" + "encoding/base64" ) var ( @@ -37,3 +38,10 @@ func SecretTokensMatch(storedToken string, suppliedToken string) bool { return subtle.ConstantTimeCompare([]byte(storedToken), []byte(suppliedToken)) == 1 } + +// generate a 256-bit secret key that can be written into a config file +func GenerateSecretKey() string { + var buf [32]byte + rand.Read(buf[:]) + return base64.RawURLEncoding.EncodeToString(buf[:]) +} diff --git a/oragono.go b/oragono.go index 0c1bf88e..77f8a1fb 100644 --- a/oragono.go +++ b/oragono.go @@ -17,6 +17,7 @@ import ( "github.com/oragono/oragono/irc" "github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/mkcerts" + "github.com/oragono/oragono/irc/utils" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/ssh/terminal" ) @@ -46,6 +47,7 @@ Usage: oragono upgradedb [--conf ] [--quiet] oragono genpasswd [--conf ] [--quiet] oragono mkcerts [--conf ] [--quiet] + oragono mksecret [--conf ] [--quiet] oragono run [--conf ] [--quiet] oragono -h | --help oragono --version @@ -128,6 +130,8 @@ Options: log.Fatal(" Could not create certificate:", err.Error()) } } + } else if arguments["mksecret"].(bool) { + fmt.Println(utils.GenerateSecretKey()) } else if arguments["run"].(bool) { if !arguments["--quiet"].(bool) { logman.Info("server", fmt.Sprintf("Oragono v%s starting", irc.SemVer)) diff --git a/oragono.yaml b/oragono.yaml index 9a244cb6..bd3c7534 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -204,8 +204,7 @@ server: # secret key to prevent dictionary attacks against cloaked IPs # any high-entropy secret is valid for this purpose: # you MUST generate a new one for your installation. - # suggestion: use the output of this command: - # python3 -c "import secrets; print(secrets.token_urlsafe())" + # suggestion: use the output of `oragono mksecret` # note that rotating this key will invalidate all existing ban masks. secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" From 18cf8163966575fb07a7240090d253aa2e7cbba9 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 12 May 2019 19:19:36 -0400 Subject: [PATCH 4/5] add a manual entry for cloaks --- docs/MANUAL.md | 11 +++++++++++ irc/config.go | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index d8c246e2..d44c12a8 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -26,6 +26,7 @@ _Copyright © 2018 Daniel Oaks _ - Nickname reservation - Channel Registration - Language + - IP cloaking - Frequently Asked Questions - Modes - User Modes @@ -243,6 +244,16 @@ The above will change the server language to Romanian, with a fallback to Chines Our language and translation functionality is very early, so feel free to let us know if there are any troubles with it! If you know another language and you'd like to contribute, we've got a CrowdIn project here: [https://crowdin.com/project/oragono](https://crowdin.com/project/oragono) +## IP cloaking + +Unlike many other chat and web platforms, IRC traditionally exposes the user's IP and hostname information to other users. This is in part because channel owners and operators (who have privileges over a single channel, but not over the server as a whole) need to be able to ban spammers and abusers from their channels, including via hostnames in cases where the abuser tries to evade the ban. + +IP cloaking is a way of balancing these concerns about abuse with concerns about user privacy. With cloaking, the user's IP address is deterministically "scrambled", typically via a cryptographic [MAC](https://en.wikipedia.org/wiki/Message_authentication_code), to form a "cloaked" hostname that replaces the usual reverse-DNS-based hostname. Users cannot reverse the scrambling to learn each other's IPs, but can ban a scrambled address the same way they would ban a regular hostname. + +Oragono supports cloaking, which can be enabled via the `server.ip-cloaking` section of the config. However, Oragono's cloaking behavior differs from other IRC software. Rather than scrambling each of the 4 bytes of the IPv4 address (or each 2-byte pair of the 8 such pairs of the IPv6 address) separately, the server administrator configures a CIDR length (essentially, a fixed number of most-significant-bits of the address). The CIDR (i.e., only the most significant portion of the address) is then scrambled atomically to produce the cloaked hostname. This errs on the side of user privacy, since knowing the cloaked hostname for one CIDR tells you nothing about the cloaked hostnames of other CIDRs --- the scheme reveals only whether two users are coming from the same CIDR. We suggest using 32-bit CIDRs for IPv4 (i.e., the whole address) and 64-bit CIDRs for IPv6, since these are the typical assignments made by ISPs to individual customers. + +Setting `server.ip-cloaking.num-bits` to 0 gives users cloaks that don't depend on their IP address information at all, which is an option for deployments where privacy is a more pressing concern than abuse. Holders of registered accounts can also use the vhost system (for details, `/msg HostServ HELP`.) + ------------------------------------------------------------------------------------------- diff --git a/irc/config.go b/irc/config.go index fb0aff27..8aea256b 100644 --- a/irc/config.go +++ b/irc/config.go @@ -733,7 +733,7 @@ func LoadConfig(filename string) (config *Config, err error) { config.Server.Cloaks.Initialize() if config.Server.Cloaks.Enabled { if config.Server.Cloaks.Secret == "" || config.Server.Cloaks.Secret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" { - return nil, fmt.Errorf("You must generate a new value of ip-cloaking.secret to enable cloaking") + return nil, fmt.Errorf("You must generate a new value of server.ip-cloaking.secret to enable cloaking") } } From fd8fd3e2528ca5e698b74ae5412286197ce7002c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 12 May 2019 19:23:45 -0400 Subject: [PATCH 5/5] don't require a config file for mksecret --- oragono.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/oragono.go b/oragono.go index 77f8a1fb..fe2f9069 100644 --- a/oragono.go +++ b/oragono.go @@ -59,7 +59,7 @@ Options: arguments, _ := docopt.ParseArgs(usage, nil, version) - // don't require a config file for genpasswd + // don't require a config file for genpasswd or mksecret if arguments["genpasswd"].(bool) { var password string fd := int(os.Stdin.Fd()) @@ -85,6 +85,9 @@ Options: fmt.Println() } return + } else if arguments["mksecret"].(bool) { + fmt.Println(utils.GenerateSecretKey()) + return } configfile := arguments["--conf"].(string) @@ -130,8 +133,6 @@ Options: log.Fatal(" Could not create certificate:", err.Error()) } } - } else if arguments["mksecret"].(bool) { - fmt.Println(utils.GenerateSecretKey()) } else if arguments["run"].(bool) { if !arguments["--quiet"].(bool) { logman.Info("server", fmt.Sprintf("Oragono v%s starting", irc.SemVer))