Respect the period and number of digits
This commit is contained in:
parent
6739cfc984
commit
c324ecb8cf
207
main.go
207
main.go
|
@ -60,6 +60,8 @@ type entry struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Enc string `json:"enc"`
|
Enc string `json:"enc"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
DigitsNo int `json:"digits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func deriveKeyFromPassword(p string, s []byte) ([]byte, []byte) {
|
func deriveKeyFromPassword(p string, s []byte) ([]byte, []byte) {
|
||||||
|
@ -79,18 +81,18 @@ func deriveKeyFromPassword(p string, s []byte) ([]byte, []byte) {
|
||||||
return m[:32], m[32:48]
|
return m[:32], m[32:48]
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptAESCBC(p string, enc string) (rv []byte, err error) {
|
func decryptAESCBC(p string, enc string) ([]byte, error) {
|
||||||
var tmp []byte
|
var tmp []byte
|
||||||
var c cipher.Block
|
var c cipher.Block
|
||||||
var m cipher.BlockMode
|
var m cipher.BlockMode
|
||||||
|
var err error
|
||||||
|
|
||||||
if tmp, err = base64.StdEncoding.DecodeString(enc); err != nil {
|
if tmp, err = base64.StdEncoding.DecodeString(enc); err != nil {
|
||||||
return
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(tmp[:8], []byte{0x53, 0x61, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x5f}) {
|
if !bytes.Equal(tmp[:8], []byte{0x53, 0x61, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x5f}) {
|
||||||
err = errors.New("Magic not found")
|
return []byte{}, errors.New("Magic not found")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key, iv := deriveKeyFromPassword(p, tmp[8:16])
|
key, iv := deriveKeyFromPassword(p, tmp[8:16])
|
||||||
|
@ -98,7 +100,7 @@ func decryptAESCBC(p string, enc string) (rv []byte, err error) {
|
||||||
tmp = tmp[16:]
|
tmp = tmp[16:]
|
||||||
|
|
||||||
if c, err = aes.NewCipher(key); err != nil {
|
if c, err = aes.NewCipher(key); err != nil {
|
||||||
return
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m = cipher.NewCBCDecrypter(c, iv)
|
m = cipher.NewCBCDecrypter(c, iv)
|
||||||
|
@ -108,80 +110,7 @@ func decryptAESCBC(p string, enc string) (rv []byte, err error) {
|
||||||
if lastval <= 16 {
|
if lastval <= 16 {
|
||||||
tmp = tmp[:len(tmp)-lastval]
|
tmp = tmp[:len(tmp)-lastval]
|
||||||
}
|
}
|
||||||
rv = tmp
|
return tmp, nil
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEntriesFromFile(n string, p string) ([]entry, error) {
|
|
||||||
var data string
|
|
||||||
var db *sql.DB
|
|
||||||
var err error
|
|
||||||
var entries map[string]entry
|
|
||||||
var pwddb string
|
|
||||||
var s []byte
|
|
||||||
var i int
|
|
||||||
|
|
||||||
if db, err = sql.Open("sqlite3", n); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = db.QueryRow("select data from storage_sync_data where ext_id=\"authenticator@mymindstorm\";").Scan(&data)
|
|
||||||
db.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if data == "" {
|
|
||||||
return nil, errors.New("no data found in database")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(data), &entries); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pwd, ok := entries["key"]; ok {
|
|
||||||
keyPwd, err := decryptAESCBC(p, pwd.Enc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pwddb = fmt.Sprintf("%x", keyPwd)
|
|
||||||
delete(entries, "key")
|
|
||||||
}
|
|
||||||
|
|
||||||
rv := make([]entry, len(entries))
|
|
||||||
for _, v := range entries {
|
|
||||||
if v.Encrypted && pwddb != "" {
|
|
||||||
if s, err = decryptAESCBC(pwddb, v.Secret); err == nil {
|
|
||||||
v.Secret = string(s)
|
|
||||||
v.Encrypted = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv[i] = v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return rv, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func computeOTP(secret string) (uint32, error) {
|
|
||||||
var secretBytes []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
secret = strings.ToUpper(strings.TrimSpace(secret))
|
|
||||||
if n := len(secret) % 8; n != 0 {
|
|
||||||
secret = secret + strings.Repeat("=", 8-n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if secretBytes, err = base32.StdEncoding.DecodeString(secret); err != nil {
|
|
||||||
return 0, errors.New("decoding of secret as base32 failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
mac := hmac.New(sha1.New, secretBytes)
|
|
||||||
|
|
||||||
binary.Write(mac, binary.BigEndian, uint64(math.Floor(float64(time.Now().Unix())/float64(30))))
|
|
||||||
sum := mac.Sum(nil)
|
|
||||||
|
|
||||||
offset := sum[len(sum)-1] & 0xf
|
|
||||||
value := binary.BigEndian.Uint32(sum[offset:offset+4]) & 0x7fffffff
|
|
||||||
return value % 1000000, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFirefoxSyncv2Path() (string, error) {
|
func getFirefoxSyncv2Path() (string, error) {
|
||||||
|
@ -200,7 +129,103 @@ func getFirefoxSyncv2Path() (string, error) {
|
||||||
return u.HomeDir + "/.mozilla/firefox/" + p + "/storage-sync-v2.sqlite", nil
|
return u.HomeDir + "/.mozilla/firefox/" + p + "/storage-sync-v2.sqlite", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigurationFile() (entries []entry, err error) {
|
func getEntriesFromFirefoxAuthenticator(fname string, password string) ([]*entry, error) {
|
||||||
|
var data string
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
var entries map[string]*entry
|
||||||
|
var pwddb string
|
||||||
|
var s []byte
|
||||||
|
var i int
|
||||||
|
|
||||||
|
if fname == "" {
|
||||||
|
fname, err = getFirefoxSyncv2Path()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if db, err = sql.Open("sqlite3", fname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = db.QueryRow("select data from storage_sync_data where ext_id=\"authenticator@mymindstorm\";").Scan(&data)
|
||||||
|
db.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data == "" {
|
||||||
|
return nil, errors.New("no data found in database")
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(data), &entries); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pwd, ok := entries["key"]; ok {
|
||||||
|
if password != "" {
|
||||||
|
keyPwd, err := decryptAESCBC(password, pwd.Enc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pwddb = fmt.Sprintf("%x", keyPwd)
|
||||||
|
}
|
||||||
|
delete(entries, "key")
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := make([]*entry, len(entries))
|
||||||
|
for _, v := range entries {
|
||||||
|
if v.Encrypted && pwddb != "" {
|
||||||
|
if s, err = decryptAESCBC(pwddb, v.Secret); err == nil {
|
||||||
|
v.Secret = string(s)
|
||||||
|
v.Encrypted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rv[i] = v
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return rv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *entry) ComputeOTP() (string, error) {
|
||||||
|
var secretBytes []byte
|
||||||
|
var err error
|
||||||
|
var secret string
|
||||||
|
var period int
|
||||||
|
var digits int
|
||||||
|
const (
|
||||||
|
DefaultPeriod = 30
|
||||||
|
DefaultDigitsNo = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
secret = strings.ToUpper(strings.TrimSpace(e.Secret))
|
||||||
|
if n := len(secret) % 8; n != 0 {
|
||||||
|
secret = secret + strings.Repeat("=", 8-n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretBytes, err = base32.StdEncoding.DecodeString(secret); err != nil {
|
||||||
|
return "", errors.New("decoding of secret as base32 failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
period = e.Period
|
||||||
|
if period == 0 {
|
||||||
|
period = DefaultPeriod
|
||||||
|
}
|
||||||
|
digits = e.DigitsNo
|
||||||
|
if digits == 0 {
|
||||||
|
digits = DefaultDigitsNo
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha1.New, secretBytes)
|
||||||
|
|
||||||
|
binary.Write(mac, binary.BigEndian,
|
||||||
|
uint64(math.Floor(float64(time.Now().Unix())/float64(period))))
|
||||||
|
sum := mac.Sum(nil)
|
||||||
|
|
||||||
|
offset := sum[len(sum)-1] & 0xf
|
||||||
|
value := binary.BigEndian.Uint32(sum[offset:offset+4]) & 0x7fffffff
|
||||||
|
return fmt.Sprintf("%0*d", digits, uint64(value)%uint64(math.Pow10(digits))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEntriesFromConfigFile() (entries []*entry, err error) {
|
||||||
var u *user.User
|
var u *user.User
|
||||||
var data []byte
|
var data []byte
|
||||||
var fname string
|
var fname string
|
||||||
|
@ -226,7 +251,7 @@ func main() {
|
||||||
var dbf string
|
var dbf string
|
||||||
var passwd string
|
var passwd string
|
||||||
var err error
|
var err error
|
||||||
var entries []entry
|
var entries []*entry
|
||||||
var firefox bool
|
var firefox bool
|
||||||
|
|
||||||
flag.StringVar(&dbf, "d", "", "Database file")
|
flag.StringVar(&dbf, "d", "", "Database file")
|
||||||
|
@ -236,19 +261,12 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if firefox || dbf != "" {
|
if firefox || dbf != "" {
|
||||||
if dbf == "" {
|
if entries, err = getEntriesFromFirefoxAuthenticator(dbf, passwd); err != nil {
|
||||||
dbf, err = getFirefoxSyncv2Path()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if entries, err = getEntriesFromFile(dbf, passwd); err != nil {
|
|
||||||
fmt.Printf("%s\n", err)
|
fmt.Printf("%s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if entries, err = getConfigurationFile(); err != nil {
|
if entries, err = getEntriesFromConfigFile(); err != nil {
|
||||||
fmt.Printf("%s\n", err)
|
fmt.Printf("%s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -258,19 +276,20 @@ func main() {
|
||||||
if v.Encrypted {
|
if v.Encrypted {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if sentry == "" {
|
if sentry == "" {
|
||||||
p, err := computeOTP(v.Secret)
|
p, err := v.ComputeOTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s: %s\n", v.Issuer, err)
|
fmt.Printf("%s: %s\n", v.Issuer, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s: %06d\n", v.Issuer, p)
|
fmt.Printf("%s: %s\n", v.Issuer, p)
|
||||||
}
|
}
|
||||||
} else if v.Issuer == sentry {
|
} else if v.Issuer == sentry {
|
||||||
p, err := computeOTP(v.Secret)
|
p, err := v.ComputeOTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", err)
|
fmt.Printf("%s\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%06d\n", p)
|
fmt.Printf("%s\n", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user