Skip to content

Commit

Permalink
Merge pull request #1 from b1naryth1ef/feature/totp-encryption
Browse files Browse the repository at this point in the history
TOTP Encryption Within Configuration
  • Loading branch information
b1naryth1ef authored Jun 6, 2017
2 parents 4e83525 + 0bb9a5b commit 14ae38d
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 5 deletions.
37 changes: 35 additions & 2 deletions cmd/bowser-create-account/bowser-create-account.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,47 @@ package main

import (
"bufio"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"encoding/base32"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"os"

"github.com/b1naryth1ef/bowser/lib"
"github.com/mdp/qrterminal"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/ssh/terminal"
)

var configPath = flag.String("config", "config.json", "path to config file")

func encryptTOTP(password []byte, salt []byte, totp []byte) ([]byte, error) {
dk := pbkdf2.Key(password, salt, 10000, 32, sha1.New)

block, err := aes.NewCipher(dk)
if err != nil {
return nil, err
}

ciphertext := make([]byte, aes.BlockSize+len(totp))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}

stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], totp)

return []byte(base64.URLEncoding.EncodeToString(ciphertext)), nil
}

func readPassword(attempts int) string {
// Create a raw terminal so we can read the password without echo
oldState, err := terminal.MakeRaw(0)
Expand Down Expand Up @@ -72,7 +98,7 @@ func main() {
totpRaw := make([]byte, 32)
_, err := rand.Read(totpRaw)
if err != nil {
fmt.Println("Failed to generate TOTP token")
fmt.Printf("Failed to generate TOTP token: %s\n", err)
return
}

Expand All @@ -88,12 +114,19 @@ func main() {
fmt.Printf("Please scan the above QR code with your TOTP app (or enter manually: `%s`)", totpEncoded)
reader.ReadString('\n')

// Now encrypt the TOTP token with the password
totpEncrypted, err := encryptTOTP([]byte(password), []byte(username[:len(username)-1]), []byte(totpEncoded))
if err != nil {
fmt.Printf("Failed to encrypt TOTP token: %v\n", err)
return
}

// Create a new account struct
account := bowser.Account{
Username: username[:len(username)-1],
Password: string(bcryptHash),
SSHKeysRaw: []string{sshKey[:len(sshKey)-1]},
MFA: bowser.AccountMFA{TOTP: string(totpEncoded)},
MFA: bowser.AccountMFA{TOTP: string(totpEncrypted)},
}

// If the configuration path was passed, we can attempt to append this to the
Expand Down
28 changes: 28 additions & 0 deletions lib/config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package bowser

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"io/ioutil"
"regexp"

"golang.org/x/crypto/pbkdf2"
)

type AccountMFA struct {
Expand Down Expand Up @@ -72,3 +78,25 @@ func (c *Config) SaveAccounts(acts []Account) (err error) {
err = ioutil.WriteFile(c.AccountsPath, data, 644)
return
}

func (am *AccountMFA) decryptTOTP(password []byte, salt []byte) (string, error) {
dk := pbkdf2.Key(password, salt, 10000, 32, sha1.New)

ciphertext, _ := base64.URLEncoding.DecodeString(am.TOTP)

block, err := aes.NewCipher(dk)
if err != nil {
return "", err
}

if len(ciphertext) < aes.BlockSize {
return "", err
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]

stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)

return string(ciphertext), nil
}
2 changes: 0 additions & 2 deletions lib/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import (
"net"
"sync"

_ "github.com/pquerna/otp/totp"
"github.com/satori/go.uuid"
"go.uber.org/zap"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
_ "golang.org/x/crypto/ssh/terminal"
)

// An account key represents a mapping of ssh public key to account
Expand Down
11 changes: 10 additions & 1 deletion lib/sshd.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,23 @@ func (s *SSHDState) Run() {
if account.MFA.TOTP != "" {
var verified bool

decryptedTOTP, err := account.MFA.decryptTOTP([]byte(passwordAnswer[0]), []byte(account.Username))
if err != nil {
s.log.Warn(
"Failed to decrypt TOTP token",
zap.String("username", conn.User()),
zap.Error(err))
return nil, badPasswordError
}

for i := 0; i < 3; i++ {
mfaAnswer, err := client(conn.User(), "", []string{"MFA Code: "}, []bool{true})

if err != nil {
continue
}

if totp.Validate(mfaAnswer[0], account.MFA.TOTP) {
if totp.Validate(mfaAnswer[0], decryptedTOTP) {
verified = true
break
}
Expand Down

0 comments on commit 14ae38d

Please sign in to comment.