Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use proquints for recovery codes #41379

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ require (
github.com/schollz/progressbar/v3 v3.14.2
github.com/scim2/filter-parser/v2 v2.2.0
github.com/segmentio/parquet-go v0.0.0-20230712180008-5d42db8f0d47
github.com/sethvargo/go-diceware v0.3.0
github.com/sigstore/cosign/v2 v2.2.4
github.com/sigstore/sigstore v1.8.3
github.com/sijms/go-ora/v2 v2.8.10
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2153,8 +2153,6 @@ github.com/segmentio/parquet-go v0.0.0-20230712180008-5d42db8f0d47 h1:5am1AKPVBj
github.com/segmentio/parquet-go v0.0.0-20230712180008-5d42db8f0d47/go.mod h1:+J0xQnJjm8DuQUHBO7t57EnmPbstT6+b45+p3DC9k1Q=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sethvargo/go-diceware v0.3.0 h1:UVVEfmN/uF50JfWAN7nbY6CiAlp5xeSx+5U0lWKkMCQ=
github.com/sethvargo/go-diceware v0.3.0/go.mod h1:lH5Q/oSPMivseNdhMERAC7Ti5oOPqsaVddU1BcN1CY0=
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500/go.mod h1:+njLrG5wSeoG4Ds61rFgEzKvenR2UHbjMoDHsczxly0=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
Expand Down
44 changes: 35 additions & 9 deletions lib/auth/accountrecovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ package auth

import (
"context"
"crypto/rand"
"encoding/binary"
"net/mail"
"strings"

"github.com/gravitational/trace"
"github.com/sethvargo/go-diceware/diceware"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"

Expand Down Expand Up @@ -479,20 +480,45 @@ func (a *Server) isAccountRecoveryAllowed(ctx context.Context) error {
// generateRecoveryCodes returns an array of tokens where each token
// have 8 random words prefixed with tele and concanatenated with dashes.
func generateRecoveryCodes() ([]string, error) {
gen, err := diceware.NewGenerator(nil /* use default word list */)
if err != nil {
return nil, trace.Wrap(err)
}
tokenList := make([]string, 0, numOfRecoveryCodes)

tokenList := make([]string, numOfRecoveryCodes)
for i := 0; i < numOfRecoveryCodes; i++ {
list, err := gen.Generate(numWordsInRecoveryCode)
if err != nil {
wordIDs := make([]uint16, numWordsInRecoveryCode)
if err := binary.Read(rand.Reader, binary.NativeEndian, wordIDs); err != nil {
return nil, trace.Wrap(err)
}

tokenList[i] = "tele-" + strings.Join(list, "-")
words := make([]string, 0, 1+len(wordIDs))
words = append(words, "tele")
for _, id := range wordIDs {
words = append(words, encodeProquint(id))
}

tokenList = append(tokenList, strings.Join(words, "-"))
}

return tokenList, nil
}

// encodeProquint returns a five-letter word based on a uint16.
// This proquint implementation is adapted from upspin.io:
// https://github.com/upspin/upspin/blob/master/key/proquint/proquint.go
// For the algorithm, see https://arxiv.org/html/0901.4016
func encodeProquint(x uint16) string {
const consonants = "bdfghjklmnprstvz"
const vowels = "aiou"

cons3 := x & 0b1111
vow2 := (x >> 4) & 0b11
cons2 := (x >> 6) & 0b1111
vow1 := (x >> 10) & 0b11
cons1 := x >> 12

return string([]byte{
consonants[cons1],
vowels[vow1],
consonants[cons2],
vowels[vow2],
consonants[cons3],
})
}
33 changes: 33 additions & 0 deletions lib/auth/accountrecovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ package auth

import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"math/rand"
"net/netip"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -1353,3 +1355,34 @@ func createUserWithSecondFactors(testServer *TestTLSServer) (*userAuthCreds, err
webDev: webDev,
}, nil
}

func TestProquint(t *testing.T) {
t.Parallel()

// source: https://arxiv.org/html/0901.4016
proquintTestCases := []struct{ address, proquint string }{
{"127.0.0.1", "lusab-babad"},
{"63.84.220.193", "gutih-tugad"},
{"63.118.7.35", "gutuk-bisog"},
{"140.98.193.141", "mudof-sakat"},
{"64.255.6.200", "haguz-biram"},
{"128.30.52.45", "mabiv-gibot"},
{"147.67.119.2", "natag-lisaf"},
{"212.58.253.68", "tibup-zujah"},
{"216.35.68.215", "tobog-higil"},
{"216.68.232.21", "todah-vobij"},
{"198.81.129.136", "sinid-makam"},
{"12.110.110.204", "budov-kuras"},
}

for _, tc := range proquintTestCases {
addr, err := netip.ParseAddr(tc.address)
require.NoError(t, err)
require.True(t, addr.Is4())
addr4 := addr.As4()

hi, lo := binary.BigEndian.Uint16(addr4[:2]), binary.BigEndian.Uint16(addr4[2:])
proquint := encodeProquint(hi) + "-" + encodeProquint(lo)
require.Equal(t, tc.proquint, proquint, "wrong encoding for address %v", addr)
}
}
Loading