First working version
This commit is contained in:
commit
69b1d09fc5
243
main.go
Executable file
243
main.go
Executable file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Arnaud Ysmal. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
Encrypted bool `json:"encrypted"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Enc string `json:"enc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveKeyFromPassword(p string, s []byte) ([]byte, []byte) {
|
||||||
|
var m []byte
|
||||||
|
prev := []byte{}
|
||||||
|
|
||||||
|
for len(m) < 48 {
|
||||||
|
a := make([]byte, len(prev)+len(p)+len(s))
|
||||||
|
copy(a, prev)
|
||||||
|
copy(a[len(prev):], p)
|
||||||
|
copy(a[len(prev)+len(p):], s)
|
||||||
|
|
||||||
|
nprev := md5.Sum(a)
|
||||||
|
prev = nprev[:]
|
||||||
|
m = append(m, prev...)
|
||||||
|
}
|
||||||
|
return m[:32], m[32:48]
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptAESCBC(p string, enc string) (rv []byte, err error) {
|
||||||
|
var tmp []byte
|
||||||
|
var c cipher.Block
|
||||||
|
var m cipher.BlockMode
|
||||||
|
|
||||||
|
if tmp, err = base64.StdEncoding.DecodeString(enc); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(tmp[:8], []byte{0x53, 0x61, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x5f}) {
|
||||||
|
err = errors.New("Magic not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, iv := deriveKeyFromPassword(p, tmp[8:16])
|
||||||
|
|
||||||
|
tmp = tmp[16:]
|
||||||
|
|
||||||
|
if c, err = aes.NewCipher(key); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = cipher.NewCBCDecrypter(c, iv)
|
||||||
|
m.CryptBlocks(tmp, tmp)
|
||||||
|
|
||||||
|
lastval := int(tmp[len(tmp)-1])
|
||||||
|
if lastval <= 16 {
|
||||||
|
tmp = tmp[:len(tmp)-lastval]
|
||||||
|
}
|
||||||
|
rv = tmp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEntriesFromFile(n string, p string) (*map[string]entry, error) {
|
||||||
|
var data string
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
var entries map[string]entry
|
||||||
|
var pwddb string
|
||||||
|
var s []byte
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range entries {
|
||||||
|
if v.Encrypted {
|
||||||
|
if s, err = decryptAESCBC(pwddb, v.Secret); err == nil {
|
||||||
|
v.Secret = string(s)
|
||||||
|
v.Encrypted = false
|
||||||
|
entries[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &entries, 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) {
|
||||||
|
var u *user.User
|
||||||
|
var err error
|
||||||
|
var f *ini.File
|
||||||
|
var p string
|
||||||
|
|
||||||
|
if u, err = user.Current(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if f, err = ini.Load(u.HomeDir + "/.mozilla/firefox/profiles.ini"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p = f.Section("Profile0").Key("Path").String()
|
||||||
|
return u.HomeDir + "/.mozilla/firefox/" + p + "/storage-sync-v2.sqlite", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var sentry string
|
||||||
|
var dbf string
|
||||||
|
var passwd string
|
||||||
|
var err error
|
||||||
|
var entries *map[string]entry
|
||||||
|
|
||||||
|
flag.StringVar(&dbf, "d", "", "Database file")
|
||||||
|
flag.StringVar(&sentry, "e", "", "Select entry")
|
||||||
|
flag.StringVar(&passwd, "p", "", "Database file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if dbf == "" {
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range *entries {
|
||||||
|
if v.Encrypted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sentry == "" {
|
||||||
|
p, err := computeOTP(v.Secret)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s: %s\n", v.Issuer, err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: %06d\n", v.Issuer, p)
|
||||||
|
}
|
||||||
|
} else if v.Issuer == sentry {
|
||||||
|
p, err := computeOTP(v.Secret)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%06d\n", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user