diff --git a/main.go b/main.go index e6e7666..fbc1582 100755 --- a/main.go +++ b/main.go @@ -60,6 +60,8 @@ type entry struct { Issuer string `json:"issuer"` Secret string `json:"secret"` Enc string `json:"enc"` + Period int `json:"period"` + DigitsNo int `json:"digits"` } 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] } -func decryptAESCBC(p string, enc string) (rv []byte, err error) { +func decryptAESCBC(p string, enc string) ([]byte, error) { var tmp []byte var c cipher.Block var m cipher.BlockMode + var err error 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}) { - err = errors.New("Magic not found") - return + return []byte{}, errors.New("Magic not found") } key, iv := deriveKeyFromPassword(p, tmp[8:16]) @@ -98,7 +100,7 @@ func decryptAESCBC(p string, enc string) (rv []byte, err error) { tmp = tmp[16:] if c, err = aes.NewCipher(key); err != nil { - return + return []byte{}, err } m = cipher.NewCBCDecrypter(c, iv) @@ -108,80 +110,7 @@ func decryptAESCBC(p string, enc string) (rv []byte, err error) { if lastval <= 16 { tmp = tmp[:len(tmp)-lastval] } - rv = tmp - 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 + return tmp, nil } func getFirefoxSyncv2Path() (string, error) { @@ -200,7 +129,103 @@ func getFirefoxSyncv2Path() (string, error) { 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 data []byte var fname string @@ -226,7 +251,7 @@ func main() { var dbf string var passwd string var err error - var entries []entry + var entries []*entry var firefox bool flag.StringVar(&dbf, "d", "", "Database file") @@ -236,19 +261,12 @@ func main() { flag.Parse() if firefox || dbf != "" { - if dbf == "" { - dbf, err = getFirefoxSyncv2Path() - if err != nil { - fmt.Printf("%s\n", err) - return - } - } - if entries, err = getEntriesFromFile(dbf, passwd); err != nil { + if entries, err = getEntriesFromFirefoxAuthenticator(dbf, passwd); err != nil { fmt.Printf("%s\n", err) return } } else { - if entries, err = getConfigurationFile(); err != nil { + if entries, err = getEntriesFromConfigFile(); err != nil { fmt.Printf("%s\n", err) return } @@ -258,19 +276,20 @@ func main() { if v.Encrypted { continue } + if sentry == "" { - p, err := computeOTP(v.Secret) + p, err := v.ComputeOTP() if err != nil { fmt.Printf("%s: %s\n", v.Issuer, err) } else { - fmt.Printf("%s: %06d\n", v.Issuer, p) + fmt.Printf("%s: %s\n", v.Issuer, p) } } else if v.Issuer == sentry { - p, err := computeOTP(v.Secret) + p, err := v.ComputeOTP() if err != nil { fmt.Printf("%s\n", err) } else { - fmt.Printf("%06d\n", p) + fmt.Printf("%s\n", p) } } }