123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package main
- import (
- "bytes"
- "crypto/hmac"
- "crypto/sha1"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "syscall"
- "encoding/binary"
- "encoding/hex"
- "golang.org/x/crypto/ssh/terminal"
- )
- const fileVersionCriticalMask uint32 = 0xFFFF0000
- const argon2Salt = "S"
- const endOfHeader = 0
- const endOfVariantMap = 0
- const kdfParameters = 11
- func readSecret() (string, error) {
- fmt.Print("Secret: ")
- byteSecret, err := terminal.ReadPassword(int(syscall.Stdin))
- fmt.Println()
- secret := string(byteSecret)
- return secret, err
- }
- func readHeaderField(reader io.Reader) (bool, byte, []byte, error) {
- var fieldID byte
- err := binary.Read(reader, binary.LittleEndian, &fieldID)
- if err != nil {
- return true, 0, nil, err
- }
- if fieldID == endOfHeader {
- return false, 0, nil, nil
- }
- var fieldLength uint32
- err = binary.Read(reader, binary.LittleEndian, &fieldLength)
- if err != nil {
- return true, fieldID, nil, err
- }
- fieldData := make([]byte, fieldLength)
- err = binary.Read(reader, binary.LittleEndian, &fieldData)
- if err != nil {
- return true, fieldID, fieldData, err
- }
- return true, fieldID, fieldData, nil
- }
- func readVariantMap(reader io.Reader) ([]byte, error) {
- var version uint16
- err := binary.Read(reader, binary.LittleEndian, &version)
- if err != nil {
- return nil, err
- }
- var fieldType byte
- for err = binary.Read(reader, binary.LittleEndian, &fieldType); fieldType != endOfVariantMap && err == nil; err = binary.Read(reader, binary.LittleEndian, &fieldType) {
- var nameLen uint32
- err = binary.Read(reader, binary.LittleEndian, &nameLen)
- if err != nil {
- return nil, err
- }
- nameBytes := make([]byte, nameLen)
- err = binary.Read(reader, binary.LittleEndian, &nameBytes)
- if err != nil {
- return nil, err
- }
- name := string(nameBytes)
- var valueLen uint32
- err = binary.Read(reader, binary.LittleEndian, &valueLen)
- if err != nil {
- return nil, err
- }
- value := make([]byte, valueLen)
- err = binary.Read(reader, binary.LittleEndian, &value)
- if err != nil {
- return nil, err
- }
- if name == argon2Salt {
- return value, nil
- }
- }
- return nil, nil
- }
- func readKeepassHeader(keepassFilename string) ([]byte, error) {
- dbFile, err := os.Open(keepassFilename)
- defer dbFile.Close()
- if err != nil {
- return nil, err
- }
- var sig1, sig2, version uint32
- err = binary.Read(dbFile, binary.LittleEndian, &sig1)
- if err != nil {
- return nil, err
- }
- err = binary.Read(dbFile, binary.LittleEndian, &sig2)
- if err != nil {
- return nil, err
- }
- err = binary.Read(dbFile, binary.LittleEndian, &version)
- if err != nil {
- return nil, err
- }
- version &= fileVersionCriticalMask
- var fieldData []byte
- var fieldID byte
- var moreFields bool
- for moreFields, fieldID, fieldData, err = readHeaderField(dbFile); moreFields && err == nil && fieldID != kdfParameters; moreFields, fieldID, fieldData, err = readHeaderField(dbFile) {
- }
- if err != nil {
- return nil, err
- }
- fieldReader := bytes.NewReader(fieldData)
- seed, err := readVariantMap(fieldReader)
- if err != nil {
- return nil, err
- }
- return seed, nil
- }
- func main() {
- log.SetFlags(0)
- args := os.Args
- if len(args) != 3 {
- log.Fatalf("usage: %s keepassxc-database keyfile", args[0])
- }
- dbFilename := args[1]
- keyFilename := args[2]
- if _, err := os.Stat(keyFilename); err == nil {
- log.Fatalf("keyfile already exists, exiting")
- }
- secretHex, err := readSecret()
- if err != nil {
- log.Fatalf("couldn't read secret from stdin: %s", err)
- }
- secret, err := hex.DecodeString(secretHex)
- if err != nil {
- log.Fatalf("couldn't decode secret: %s", err)
- }
- challenge, err := readKeepassHeader(dbFilename)
- if err != nil {
- log.Fatalf("couldn't read challenge: %s", err)
- }
- if len(challenge) < 64 {
- padd := make([]byte, 64-len(challenge))
- for i, _ := range padd {
- padd[i] = byte(64-len(challenge))
- }
- challenge = append(challenge[:], padd[:]...)
- }
- mac := hmac.New(sha1.New, secret)
- mac.Write(challenge)
- hash := mac.Sum(nil)
- err = ioutil.WriteFile(keyFilename, hash, 0644)
- if err != nil {
- log.Fatalf("couldn't write keyfile: %s", err)
- }
- }
|