Personal book of passwords

main.go 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package main
  2. import (
  3. "bytes"
  4. "compress/gzip"
  5. "crypto/sha1"
  6. "crypto/sha512"
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "os"
  13. "regexp"
  14. "strings"
  15. )
  16. type Site struct {
  17. Host string `json:host`
  18. MinimumLength int `json:minimumLength`
  19. MaximumLength int `json:maximumLength`
  20. SpecialCharacters string `json:specialCharacters`
  21. NumberOfSpecialCharacters int `json:numberOfSpecialCharacters`
  22. NumberOfUpperCase int `json:numberOfUpperCase`
  23. NumberOfDigits int `json:numberOfDigits`
  24. Revision int `json:revision`
  25. }
  26. func main() {
  27. http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
  28. })
  29. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  30. path := strings.TrimPrefix(r.URL.Path, "/")
  31. if path == "" {
  32. // Index
  33. page, err := ioutil.ReadFile("index.html")
  34. if err != nil {
  35. http.NotFound(w, r)
  36. return
  37. }
  38. fmt.Fprintf(w, string(page))
  39. } else {
  40. // A passphrase has been entered
  41. }
  42. })
  43. log.Fatal(http.ListenAndServe("localhost:8080", nil))
  44. //execDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
  45. //if err != nil {
  46. // log.Fatal(err)
  47. //}
  48. //file := filepath.Join(execDir, "enigma.safe")
  49. //sites, err := Read(file)
  50. //if err != nil {
  51. // log.Fatal(err)
  52. //}
  53. //log.Println(sites)
  54. //err = Save(file, sites)
  55. //if err != nil {
  56. // log.Fatal(err)
  57. //}
  58. }
  59. func Save(file string, sites []Site) error {
  60. // If the file doesn't exist then create it
  61. if _, err := os.Stat(file); os.IsNotExist(err) {
  62. _, err = os.Create(file)
  63. if err != nil {
  64. return err
  65. }
  66. }
  67. // Marshal the JSON
  68. b, err := json.Marshal(sites)
  69. if err != nil {
  70. return err
  71. }
  72. // Compress the contents
  73. buffer := bytes.NewBuffer(b)
  74. gzipWriter := gzip.NewWriter(buffer)
  75. if err != nil {
  76. return err
  77. }
  78. defer gzipWriter.Close()
  79. // Write to the file
  80. fi, err := os.OpenFile(file, os.O_WRONLY, 0666)
  81. if err != nil {
  82. return err
  83. }
  84. defer fi.Close()
  85. _, err = fi.Write(buffer.Bytes())
  86. if err != nil {
  87. return err
  88. }
  89. return nil
  90. }
  91. // Read the password book
  92. func Read(file string) ([]Site, error) {
  93. // If the file doesn't exist yet no worries
  94. if _, err := os.Stat(file); os.IsNotExist(err) {
  95. return []Site{}, nil
  96. }
  97. // Bring in the compressed data
  98. log.Println("Reading compressed file")
  99. compressed, err := ioutil.ReadFile(file)
  100. if err != nil {
  101. return nil, err
  102. }
  103. // Decompress the file contents
  104. log.Println("Decompressing")
  105. buffer := bytes.NewBuffer(compressed)
  106. gzipReader, err := gzip.NewReader(buffer)
  107. if err != nil {
  108. return nil, err
  109. }
  110. defer gzipReader.Close()
  111. // Unmarshal the JSON information
  112. log.Println("Unmarshal")
  113. var sites []Site
  114. err = json.Unmarshal(buffer.Bytes(), &sites)
  115. if err != nil {
  116. return nil, err
  117. }
  118. return sites, nil
  119. }
  120. // Get the book name
  121. func getBookname(profile string) []byte {
  122. sha := sha1.New()
  123. sha.Write([]byte(profile))
  124. return sha.Sum(nil)
  125. }
  126. // Encrypt the password book
  127. func encrypt(clearText, profile, passphrase string) ([]byte, error) {
  128. return nil, nil
  129. }
  130. // Decrypt the password book
  131. func decrypt(encryptedText, profile, passphrase string) ([]byte, error) {
  132. return nil, nil
  133. }
  134. // Generate the passphrase
  135. func generatePassphrase(profile, passphrase string, settings Site) ([]byte, error) {
  136. clearText := fmt.Sprintf(
  137. "%s-%s-%s-%s",
  138. strings.ToLower(profile),
  139. strings.ToLower(passphrase),
  140. strings.ToLower(settings.Host),
  141. settings.Revision)
  142. sha := sha512.New()
  143. sha.Write([]byte(clearText))
  144. hash := sha.Sum(nil)
  145. hash = []byte(fmt.Sprintf("%x", hash))
  146. // Apply site criteria
  147. applySiteSettings(hash, settings)
  148. // If there is a maximum length truncate the hash
  149. if settings.MaximumLength > -1 {
  150. hash = hash[:settings.MaximumLength]
  151. }
  152. // Ensure the length is adequate
  153. if !validateLength(hash, settings.MinimumLength, settings.MaximumLength) {
  154. log.Println("Does not meed the length requirements")
  155. }
  156. return hash, nil
  157. }
  158. // Apply site settings to the hashed value
  159. func applySiteSettings(source []byte, settings Site) []byte {
  160. if !containsUppercase(source, settings.NumberOfUpperCase) {
  161. i := 0
  162. r := regexp.MustCompile(`[a-z]+`)
  163. var matches [][]int
  164. if matches = r.FindAllIndex(source, -1); matches != nil {
  165. for _, v := range matches {
  166. if i < settings.NumberOfUpperCase {
  167. c := strings.ToUpper(string(source[v[0]]))
  168. source[v[0]] = []byte(c)[0]
  169. i += 1
  170. }
  171. }
  172. }
  173. }
  174. if !containsDigits(source, settings.NumberOfDigits) {
  175. i := 0
  176. r := regexp.MustCompile(`[a-z]+`)
  177. var matches [][]int
  178. if matches = r.FindAllIndex(source, -1); matches != nil {
  179. for _, v := range matches {
  180. if i < settings.NumberOfDigits {
  181. source[v[0]] = byte(i)
  182. i += 1
  183. }
  184. }
  185. }
  186. }
  187. if !containsSpecialCharacters(source, settings.SpecialCharacters, settings.NumberOfSpecialCharacters) {
  188. i := 0
  189. r := regexp.MustCompile(`[a-z]+`)
  190. var matches [][]int
  191. if matches = r.FindAllIndex(source, -1); matches != nil {
  192. for _, v := range matches {
  193. if i < settings.NumberOfSpecialCharacters {
  194. i += 1
  195. source[v[0]] = []byte(settings.SpecialCharacters)[len(settings.SpecialCharacters)-i]
  196. }
  197. }
  198. }
  199. }
  200. return source
  201. }
  202. // Determine if the hash currently contains the appropriate amount of digits
  203. func containsDigits(source []byte, minOccurrences int) bool {
  204. r := regexp.MustCompile(`\d`)
  205. var matches [][]byte
  206. if matches = r.FindAll(source, -1); matches == nil {
  207. return false
  208. }
  209. return len(matches) >= minOccurrences
  210. }
  211. // Determine if the hash currently contains the appropriate amount of uppercase characters
  212. func containsUppercase(source []byte, minOccurrences int) bool {
  213. r := regexp.MustCompile(`[A-Z]+`)
  214. var matches [][]byte
  215. if matches = r.FindAll(source, -1); matches == nil {
  216. return false
  217. }
  218. return len(matches) >= minOccurrences
  219. }
  220. // Determine if the hash currently contains the appropriate amount of special characters from the allowed
  221. // character set
  222. func containsSpecialCharacters(source []byte, specialCharacters string, minOccurrences int) bool {
  223. s := specialCharacters
  224. s = strings.Replace(s, "\\", "\\\\", -1)
  225. s = strings.Replace(s, ".", "\\.", -1)
  226. s = strings.Replace(s, " ", "\\s", -1)
  227. s = strings.Replace(s, "-", "\\-", -1)
  228. s = strings.Replace(s, "[", "\\[", -1)
  229. s = strings.Replace(s, "]", "\\]", -1)
  230. r := regexp.MustCompile(`[` + s + `]+`)
  231. var matches [][]byte
  232. if matches = r.FindAll(source, -1); matches == nil {
  233. return false
  234. }
  235. return len(matches) >= minOccurrences
  236. }
  237. // Determine if the hash currently abides by the length restrictions
  238. func validateLength(source []byte, minimum, maximum int) bool {
  239. if minimum > -1 && len(source) < minimum {
  240. return false
  241. }
  242. if maximum > -1 && len(source) > maximum {
  243. return false
  244. }
  245. return true
  246. }