From e143aaa83fd8a5e357de77305f7f2fe251b08eaa Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 18 Dec 2019 15:44:06 -0500 Subject: [PATCH] fix #696 --- irc/commands.go | 2 +- irc/config.go | 3 +++ irc/handlers.go | 10 +++++++--- irc/help.go | 2 +- irc/utils/crypto.go | 13 +++++++++++++ irc/utils/crypto_test.go | 18 ++++++++++++++++++ oragono.yaml | 5 ++++- 7 files changed, 47 insertions(+), 6 deletions(-) diff --git a/irc/commands.go b/irc/commands.go index 24f8e5b8..31aa6c18 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -206,7 +206,7 @@ func init() { }, "OPER": { handler: operHandler, - minParams: 2, + minParams: 1, }, "PART": { handler: partHandler, diff --git a/irc/config.go b/irc/config.go index 464bcb00..09e01f8e 100644 --- a/irc/config.go +++ b/irc/config.go @@ -211,6 +211,7 @@ type OperConfig struct { Vhost string WhoisLine string `yaml:"whois-line"` Password string + Certfp string Modes string } @@ -459,6 +460,7 @@ type Oper struct { WhoisLine string Vhost string Pass []byte + Certfp string Modes []modes.ModeChange } @@ -479,6 +481,7 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error if err != nil { return nil, err } + oper.Certfp = opConf.Certfp oper.Vhost = opConf.Vhost class, exists := oc[opConf.Class] diff --git a/irc/handlers.go b/irc/handlers.go index dbcc5cb7..74381015 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -2170,7 +2170,7 @@ func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp return false } -// OPER +// OPER [password] func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { if client.HasMode(modes.Operator) { rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "OPER", client.t("You're already opered-up!")) @@ -2180,8 +2180,12 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp authorized := false oper := server.GetOperator(msg.Params[0]) if oper != nil { - password := []byte(msg.Params[1]) - authorized = (bcrypt.CompareHashAndPassword(oper.Pass, password) == nil) + if utils.CertfpsMatch(oper.Certfp, client.certfp) { + authorized = true + } else if 1 < len(msg.Params) { + password := []byte(msg.Params[1]) + authorized = (bcrypt.CompareHashAndPassword(oper.Pass, password) == nil) + } } if !authorized { rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect")) diff --git a/irc/help.go b/irc/help.go index 91d751be..c6a96963 100644 --- a/irc/help.go +++ b/irc/help.go @@ -349,7 +349,7 @@ The NPC command is used to send an action to the target as the source. Requires the roleplay mode (+E) to be set on the target.`, }, "oper": { - text: `OPER + text: `OPER [password] If the correct details are given, gives you IRCop privs.`, }, diff --git a/irc/utils/crypto.go b/irc/utils/crypto.go index a725b547..be4247ff 100644 --- a/irc/utils/crypto.go +++ b/irc/utils/crypto.go @@ -8,6 +8,7 @@ import ( "crypto/subtle" "encoding/base32" "encoding/base64" + "strings" ) var ( @@ -68,3 +69,15 @@ func GenerateSecretKey() string { rand.Read(buf[:]) return base64.RawURLEncoding.EncodeToString(buf[:]) } + +func normalizeCertfp(certfp string) string { + return strings.ToLower(strings.Replace(certfp, ":", "", -1)) +} + +// Convenience to compare certfps as returned by different tools, e.g., openssl vs. oragono +func CertfpsMatch(storedCertfp, suppliedCertfp string) bool { + if storedCertfp == "" { + return false + } + return normalizeCertfp(storedCertfp) == normalizeCertfp(suppliedCertfp) +} diff --git a/irc/utils/crypto_test.go b/irc/utils/crypto_test.go index c00ebad3..0b0b6967 100644 --- a/irc/utils/crypto_test.go +++ b/irc/utils/crypto_test.go @@ -81,3 +81,21 @@ func BenchmarkMungeSecretToken(b *testing.B) { t = MungeSecretToken(t) } } + +func TestCertfpComparisons(t *testing.T) { + opensslFP := "3D:6B:11:BF:B4:05:C3:F8:4B:38:CD:30:38:FB:EC:01:71:D5:03:54:79:04:07:88:4C:A5:5D:23:41:85:66:C9" + oragonoFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c9" + badFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c8" + if !CertfpsMatch(opensslFP, oragonoFP) { + t.Error("these certs should match") + } + if !CertfpsMatch(oragonoFP, opensslFP) { + t.Error("these certs should match") + } + if CertfpsMatch("", "") { + t.Error("empty stored certfp should not match empty provided certfp") + } + if CertfpsMatch(opensslFP, badFP) { + t.Error("these certs should not match") + } +} diff --git a/oragono.yaml b/oragono.yaml index 1f2d9ef4..f805bf06 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -121,7 +121,7 @@ server: # one webirc block -- should correspond to one set of gateways - # tls fingerprint the gateway must connect with to use this webirc block - fingerprint: 938dd33f4b76dcaf7ce5eb25c852369cb4b8fb47ba22fc235aa29c6623a5f182 + fingerprint: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" # password the gateway uses to connect, made with oragono genpasswd password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee" @@ -449,6 +449,9 @@ opers: # generated using "oragono genpasswd" password: "$2a$04$LiytCxaY0lI.guDj2pBN4eLRD5cdM2OLDwqmGAgB6M2OPirbF5Jcu" + # being logged in with this client cert will let you /OPER without a password + certfp: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + # logging, takes inspiration from Insp logging: -