main.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha1"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "log"
  10. "os"
  11. "syscall"
  12. "encoding/binary"
  13. "encoding/hex"
  14. "golang.org/x/crypto/ssh/terminal"
  15. )
  16. const fileVersionCriticalMask uint32 = 0xFFFF0000
  17. const argon2Salt = "S"
  18. const endOfHeader = 0
  19. const endOfVariantMap = 0
  20. const kdfParameters = 11
  21. func readSecret() (string, error) {
  22. fmt.Print("Secret: ")
  23. byteSecret, err := terminal.ReadPassword(int(syscall.Stdin))
  24. fmt.Println()
  25. secret := string(byteSecret)
  26. return secret, err
  27. }
  28. func readHeaderField(reader io.Reader) (bool, byte, []byte, error) {
  29. var fieldID byte
  30. err := binary.Read(reader, binary.LittleEndian, &fieldID)
  31. if err != nil {
  32. return true, 0, nil, err
  33. }
  34. if fieldID == endOfHeader {
  35. return false, 0, nil, nil
  36. }
  37. var fieldLength uint32
  38. err = binary.Read(reader, binary.LittleEndian, &fieldLength)
  39. if err != nil {
  40. return true, fieldID, nil, err
  41. }
  42. fieldData := make([]byte, fieldLength)
  43. err = binary.Read(reader, binary.LittleEndian, &fieldData)
  44. if err != nil {
  45. return true, fieldID, fieldData, err
  46. }
  47. return true, fieldID, fieldData, nil
  48. }
  49. func readVariantMap(reader io.Reader) ([]byte, error) {
  50. var version uint16
  51. err := binary.Read(reader, binary.LittleEndian, &version)
  52. if err != nil {
  53. return nil, err
  54. }
  55. var fieldType byte
  56. for err = binary.Read(reader, binary.LittleEndian, &fieldType); fieldType != endOfVariantMap && err == nil; err = binary.Read(reader, binary.LittleEndian, &fieldType) {
  57. var nameLen uint32
  58. err = binary.Read(reader, binary.LittleEndian, &nameLen)
  59. if err != nil {
  60. return nil, err
  61. }
  62. nameBytes := make([]byte, nameLen)
  63. err = binary.Read(reader, binary.LittleEndian, &nameBytes)
  64. if err != nil {
  65. return nil, err
  66. }
  67. name := string(nameBytes)
  68. var valueLen uint32
  69. err = binary.Read(reader, binary.LittleEndian, &valueLen)
  70. if err != nil {
  71. return nil, err
  72. }
  73. value := make([]byte, valueLen)
  74. err = binary.Read(reader, binary.LittleEndian, &value)
  75. if err != nil {
  76. return nil, err
  77. }
  78. if name == argon2Salt {
  79. return value, nil
  80. }
  81. }
  82. return nil, nil
  83. }
  84. func readKeepassHeader(keepassFilename string) ([]byte, error) {
  85. dbFile, err := os.Open(keepassFilename)
  86. defer dbFile.Close()
  87. if err != nil {
  88. return nil, err
  89. }
  90. var sig1, sig2, version uint32
  91. err = binary.Read(dbFile, binary.LittleEndian, &sig1)
  92. if err != nil {
  93. return nil, err
  94. }
  95. err = binary.Read(dbFile, binary.LittleEndian, &sig2)
  96. if err != nil {
  97. return nil, err
  98. }
  99. err = binary.Read(dbFile, binary.LittleEndian, &version)
  100. if err != nil {
  101. return nil, err
  102. }
  103. version &= fileVersionCriticalMask
  104. var fieldData []byte
  105. var fieldID byte
  106. var moreFields bool
  107. for moreFields, fieldID, fieldData, err = readHeaderField(dbFile); moreFields && err == nil && fieldID != kdfParameters; moreFields, fieldID, fieldData, err = readHeaderField(dbFile) {
  108. }
  109. if err != nil {
  110. return nil, err
  111. }
  112. fieldReader := bytes.NewReader(fieldData)
  113. seed, err := readVariantMap(fieldReader)
  114. if err != nil {
  115. return nil, err
  116. }
  117. return seed, nil
  118. }
  119. func main() {
  120. log.SetFlags(0)
  121. args := os.Args
  122. if len(args) != 3 {
  123. log.Fatalf("usage: %s keepassxc-database keyfile", args[0])
  124. }
  125. dbFilename := args[1]
  126. keyFilename := args[2]
  127. if _, err := os.Stat(keyFilename); err == nil {
  128. log.Fatalf("keyfile already exists, exiting")
  129. }
  130. secretHex, err := readSecret()
  131. if err != nil {
  132. log.Fatalf("couldn't read secret from stdin: %s", err)
  133. }
  134. secret, err := hex.DecodeString(secretHex)
  135. if err != nil {
  136. log.Fatalf("couldn't decode secret: %s", err)
  137. }
  138. challenge, err := readKeepassHeader(dbFilename)
  139. if err != nil {
  140. log.Fatalf("couldn't read challenge: %s", err)
  141. }
  142. if len(challenge) < 64 {
  143. padd := make([]byte, 64-len(challenge))
  144. for i, _ := range padd {
  145. padd[i] = byte(64-len(challenge))
  146. }
  147. challenge = append(challenge[:], padd[:]...)
  148. }
  149. mac := hmac.New(sha1.New, secret)
  150. mac.Write(challenge)
  151. hash := mac.Sum(nil)
  152. err = ioutil.WriteFile(keyFilename, hash, 0644)
  153. if err != nil {
  154. log.Fatalf("couldn't write keyfile: %s", err)
  155. }
  156. }