diff --git a/crypto.go b/crypto.go new file mode 100644 index 0000000..68a6ffe --- /dev/null +++ b/crypto.go @@ -0,0 +1,57 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "encoding/base32" + "encoding/binary" + "errors" + "fmt" + "hash" + "math" + "strings" + "time" +) + +func ComputeHmac256(message string, secret string) []byte { + h := hmac.New(sha256.New, []byte(secret)) + h.Write([]byte(message)) + return h.Sum(nil) +} + +func encodeSecret(secret []byte) string { + return strings.TrimRight(base32.StdEncoding.EncodeToString(secret), "=") +} + +func GenericTotpCode(secret string, t time.Time, algo string, digits int, period int) (string, error) { + var mac hash.Hash + var format string + + now := [8]byte{} + + binary.BigEndian.PutUint64(now[:], uint64(math.Floor(float64(t.Unix())/float64(period)))) + binsecret, _ := base32.StdEncoding.DecodeString(secret) + + switch algo { + case "sha1": + mac = hmac.New(sha1.New, binsecret) + case "sha256": + mac = hmac.New(sha256.New, binsecret) + default: + return "", errors.New("unsupported algorithm") + } + + switch digits { + case 6: + format = "%06d" + case 8: + format = "%08d" + default: + return "", errors.New("unsupported code size") + } + + mac.Write(now[:]) + sum := mac.Sum(nil) + return fmt.Sprintf(format, (binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0xf:])&0x7fffffff)%1000000), nil +} diff --git a/main.go b/main.go index ea2ffa2..a3c99e3 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,10 @@ func main() { server.slackTemplate2 = config.GetString("config.slackTemplate2", "") server.cacheDir = config.GetString("config.cacheDir", "") server.authCa = config.GetString("config.authCa", "") + server.otpMasterSecrets = parseConfigArray(config, "config.masterSecrets") + if len(server.otpMasterSecrets) == 0 { + server.otpMasterSecrets = append(server.otpMasterSecrets, "*******************") + } server.syslog = false if *logToSyslog { diff --git a/otp.go b/otp.go new file mode 100644 index 0000000..7b4e1ee --- /dev/null +++ b/otp.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "time" +) + +func (s *OpenVpnMgt) GenerateOTP(user string) ([]string, error) { + return s.GenerateOTPGeneric(user, 30, "sha1", 10, 6) +} + +// alternative OTP generator, not used at the moment +func (s *OpenVpnMgt) GenerateSlackOTP(user string) ([]string, error) { + return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8) +} + +func (s *OpenVpnMgt) GenerateOTPGeneric(user string, period int, algo string, secretLen int, digits int) ([]string, error) { + codes := []string{} + now := time.Now() + + secret := encodeSecret(ComputeHmac256(user, s.otpMasterSecrets[0])[:secretLen]) + code, err := GenericTotpCode(secret, now, algo, digits, period) + if err != nil { + return codes, err + } + // the first code is the generic one + codes = append(codes, code) + + for i := 1; i < 3; i++ { + code, _ = GenericTotpCode(secret, now.Add(-1*time.Second*time.Duration(period*i)), algo, digits, period) + codes = append(codes, code) + } + + for j := 1; j < len(s.otpMasterSecrets); j++ { + secret = encodeSecret(ComputeHmac256(user, s.otpMasterSecrets[j])[:secretLen]) + for i := 0; i < 3; i++ { + code, _ = GenericTotpCode(secret, now.Add(-1*time.Second*time.Duration(period*i)), algo, digits, period) + codes = append(codes, code) + } + } + return codes, nil +} diff --git a/openvpn.go b/vpnserver.go similarity index 87% rename from openvpn.go rename to vpnserver.go index 7deb5df..e7d10af 100644 --- a/openvpn.go +++ b/vpnserver.go @@ -13,23 +13,24 @@ import ( // Server represents the server type OpenVpnMgt struct { - Port string - buf *bufio.ReadWriter - connected bool - m sync.RWMutex - ret chan []string - ldap map[string]ldapConfig - authCa string - vpnlogUrl string - mailRelay string - MailFrom string - CcPwnPassword string - pwnTemplate string - newAsTemplate string - slackTemplate string - slackTemplate2 string - cacheDir string - syslog bool + Port string + buf *bufio.ReadWriter + connected bool + m sync.RWMutex + ret chan []string + ldap map[string]ldapConfig + authCa string + vpnlogUrl string + mailRelay string + MailFrom string + CcPwnPassword string + pwnTemplate string + newAsTemplate string + slackTemplate string + slackTemplate2 string + cacheDir string + syslog bool + otpMasterSecrets []string } // NewServer returns a pointer to a new server @@ -86,6 +87,23 @@ func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) { c.password = "" } + // if the otp is indicated, we check it against the valid codes as soon as + // possible + otpvalidated := false + if c.otpCode != "" { + codes, err := s.GenerateOTP(c.Login) + if err != nil { + return err, false + } + for _, possible := range codes { + if possible == c.otpCode { + otpvalidated = true + } + } + } + + log.Println(otpvalidated) + c.Profile = "" login := []string{c.Login} pass := c.password