Personal book of passwords

main.go 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. "strconv"
  15. "strings"
  16. )
  17. type Site struct {
  18. Host string `json:host`
  19. MinimumLength int `json:minimumLength`
  20. MaximumLength int `json:maximumLength`
  21. SpecialCharacters string `json:specialCharacters`
  22. NumberOfSpecialCharacters int `json:numberOfSpecialCharacters`
  23. NumberOfUpperCase int `json:numberOfUpperCase`
  24. NumberOfDigits int `json:numberOfDigits`
  25. Revision int `json:revision`
  26. }
  27. func main() {
  28. http.HandleFunc("/api/generate", func(w http.ResponseWriter, r *http.Request) {
  29. profile := r.FormValue("profile")
  30. //passphrase := r.FormValue("p")
  31. host := r.FormValue("host")
  32. minimumLength, _ := strconv.Atoi(r.FormValue("minimumLength"))
  33. maximumLength, _ := strconv.Atoi(r.FormValue("maximumLength"))
  34. minimumDigits, _ := strconv.Atoi(r.FormValue("minimumDigits"))
  35. minimumUppercase, _ := strconv.Atoi(r.FormValue("minimumUppercase"))
  36. minimumSpecialCharacters, _ := strconv.Atoi(r.FormValue("minimumSpecialCharacters"))
  37. specialCharacters := r.FormValue("specialCharacters")
  38. site := Site{
  39. Host: host,
  40. MinimumLength: minimumLength,
  41. MaximumLength: maximumLength,
  42. SpecialCharacters: specialCharacters,
  43. NumberOfSpecialCharacters: minimumSpecialCharacters,
  44. NumberOfDigits: minimumDigits,
  45. NumberOfUpperCase: minimumUppercase,
  46. Revision: 0,
  47. }
  48. book := getBookname(profile)
  49. sites, err := Read(book)
  50. if err != nil {
  51. }
  52. sites = append(sites, site)
  53. err = Save(book, sites)
  54. if err != nil {
  55. }
  56. })
  57. http.HandleFunc("/api/update", func(w http.ResponseWriter, r *http.Request) {
  58. profile := r.FormValue("profile")
  59. //passphrase := r.FormValue("p")
  60. //newPassphrase := r.FormValue("newPassphrase")
  61. //confirmPassphrase := r.FormValue("confirmPassphrase")
  62. book := getBookname(profile)
  63. err := os.Remove(book)
  64. if err != nil {
  65. // Return an error
  66. }
  67. })
  68. http.HandleFunc("/api/refresh", func(w http.ResponseWriter, r *http.Request) {
  69. profile := r.FormValue("profile")
  70. //passphrase := r.FormValue("p")
  71. host := r.FormValue("host")
  72. // Update the revision number and generate a new password
  73. book := getBookname(profile)
  74. sites, err := Read(book)
  75. if err != nil {
  76. }
  77. for _, site := range sites {
  78. if site.Host == host {
  79. site.Revision++
  80. break
  81. }
  82. }
  83. err = Save(book, sites)
  84. if err != nil {
  85. }
  86. })
  87. http.HandleFunc("/api/remove", func(w http.ResponseWriter, r *http.Request) {
  88. profile := r.FormValue("profile")
  89. //passphrase := r.FormValue("p")
  90. host := r.FormValue("host")
  91. // Remove the site from our book and save it
  92. book := getBookname(profile)
  93. sites, err := Read(book)
  94. if err != nil {
  95. }
  96. for i, site := range sites {
  97. if site.Host == host {
  98. sites = append(sites[:i], sites[i+1:]...)
  99. break
  100. }
  101. }
  102. })
  103. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  104. path := strings.TrimPrefix(r.URL.Path, "/")
  105. if path == "" {
  106. // Index
  107. page, err := ioutil.ReadFile("index.html")
  108. if err != nil {
  109. http.NotFound(w, r)
  110. return
  111. }
  112. fmt.Fprintf(w, string(page))
  113. } else {
  114. // A passphrase has been entered
  115. }
  116. })
  117. log.Fatal(http.ListenAndServe("localhost:8080", nil))
  118. //execDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
  119. //if err != nil {
  120. // log.Fatal(err)
  121. //}
  122. //file := filepath.Join(execDir, "enigma.safe")
  123. //sites, err := Read(file)
  124. //if err != nil {
  125. // log.Fatal(err)
  126. //}
  127. //log.Println(sites)
  128. //err = Save(file, sites)
  129. //if err != nil {
  130. // log.Fatal(err)
  131. //}
  132. }
  133. func Save(file string, sites []Site) error {
  134. // If the file doesn't exist then create it
  135. if _, err := os.Stat(file); os.IsNotExist(err) {
  136. _, err = os.Create(file)
  137. if err != nil {
  138. return err
  139. }
  140. }
  141. // Marshal the JSON
  142. b, err := json.Marshal(sites)
  143. if err != nil {
  144. return err
  145. }
  146. // Compress the contents
  147. buffer := bytes.NewBuffer(b)
  148. gzipWriter := gzip.NewWriter(buffer)
  149. if err != nil {
  150. return err
  151. }
  152. defer gzipWriter.Close()
  153. // Write to the file
  154. fi, err := os.OpenFile(file, os.O_WRONLY, 0666)
  155. if err != nil {
  156. return err
  157. }
  158. defer fi.Close()
  159. _, err = fi.Write(buffer.Bytes())
  160. if err != nil {
  161. return err
  162. }
  163. return nil
  164. }
  165. // Read the password book
  166. func Read(file string) ([]Site, error) {
  167. // If the file doesn't exist yet no worries
  168. if _, err := os.Stat(file); os.IsNotExist(err) {
  169. return []Site{}, nil
  170. }
  171. // Bring in the compressed data
  172. log.Println("Reading compressed file")
  173. compressed, err := ioutil.ReadFile(file)
  174. if err != nil {
  175. return nil, err
  176. }
  177. // Decompress the file contents
  178. log.Println("Decompressing")
  179. buffer := bytes.NewBuffer(compressed)
  180. gzipReader, err := gzip.NewReader(buffer)
  181. if err != nil {
  182. return nil, err
  183. }
  184. defer gzipReader.Close()
  185. // Unmarshal the JSON information
  186. log.Println("Unmarshal")
  187. var sites []Site
  188. err = json.Unmarshal(buffer.Bytes(), &sites)
  189. if err != nil {
  190. return nil, err
  191. }
  192. return sites, nil
  193. }
  194. // Get the book name
  195. func getBookname(profile string) string {
  196. sha := sha1.New()
  197. sha.Write([]byte(profile))
  198. return string(sha.Sum(nil))
  199. }
  200. // Encrypt the password book
  201. func encrypt(clearText, profile, passphrase string) ([]byte, error) {
  202. return nil, nil
  203. }
  204. // Decrypt the password book
  205. func decrypt(encryptedText, profile, passphrase string) ([]byte, error) {
  206. return nil, nil
  207. }
  208. // Generate the passphrase
  209. func generatePassphrase(profile, passphrase string, settings Site) ([]byte, error) {
  210. clearText := fmt.Sprintf(
  211. "%s-%s-%s-%s",
  212. strings.ToLower(profile),
  213. strings.ToLower(passphrase),
  214. strings.ToLower(settings.Host),
  215. settings.Revision)
  216. sha := sha512.New()
  217. sha.Write([]byte(clearText))
  218. hash := sha.Sum(nil)
  219. hash = []byte(fmt.Sprintf("%x", hash))
  220. // Apply site criteria
  221. applySiteSettings(hash, settings)
  222. // If there is a maximum length truncate the hash
  223. if settings.MaximumLength > -1 {
  224. hash = hash[:settings.MaximumLength]
  225. }
  226. // Ensure the length is adequate
  227. if !validateLength(hash, settings.MinimumLength, settings.MaximumLength) {
  228. log.Println("Does not meed the length requirements")
  229. }
  230. return hash, nil
  231. }
  232. // Apply site settings to the hashed value
  233. func applySiteSettings(source []byte, settings Site) []byte {
  234. if !containsUppercase(source, settings.NumberOfUpperCase) {
  235. i := 0
  236. r := regexp.MustCompile(`[a-z]+`)
  237. var matches [][]int
  238. if matches = r.FindAllIndex(source, -1); matches != nil {
  239. for _, v := range matches {
  240. if i < settings.NumberOfUpperCase {
  241. c := strings.ToUpper(string(source[v[0]]))
  242. source[v[0]] = []byte(c)[0]
  243. i += 1
  244. }
  245. }
  246. }
  247. }
  248. if !containsDigits(source, settings.NumberOfDigits) {
  249. i := 0
  250. r := regexp.MustCompile(`[a-z]+`)
  251. var matches [][]int
  252. if matches = r.FindAllIndex(source, -1); matches != nil {
  253. for _, v := range matches {
  254. if i < settings.NumberOfDigits {
  255. source[v[0]] = byte(i)
  256. i += 1
  257. }
  258. }
  259. }
  260. }
  261. if !containsSpecialCharacters(source, settings.SpecialCharacters, settings.NumberOfSpecialCharacters) {
  262. i := 0
  263. r := regexp.MustCompile(`[a-z]+`)
  264. var matches [][]int
  265. if matches = r.FindAllIndex(source, -1); matches != nil {
  266. for _, v := range matches {
  267. if i < settings.NumberOfSpecialCharacters {
  268. i += 1
  269. source[v[0]] = []byte(settings.SpecialCharacters)[len(settings.SpecialCharacters)-i]
  270. }
  271. }
  272. }
  273. }
  274. return source
  275. }
  276. // Determine if the hash currently contains the appropriate amount of digits
  277. func containsDigits(source []byte, minOccurrences int) bool {
  278. r := regexp.MustCompile(`\d`)
  279. var matches [][]byte
  280. if matches = r.FindAll(source, -1); matches == nil {
  281. return false
  282. }
  283. return len(matches) >= minOccurrences
  284. }
  285. // Determine if the hash currently contains the appropriate amount of uppercase characters
  286. func containsUppercase(source []byte, minOccurrences int) bool {
  287. r := regexp.MustCompile(`[A-Z]+`)
  288. var matches [][]byte
  289. if matches = r.FindAll(source, -1); matches == nil {
  290. return false
  291. }
  292. return len(matches) >= minOccurrences
  293. }
  294. // Determine if the hash currently contains the appropriate amount of special characters from the allowed
  295. // character set
  296. func containsSpecialCharacters(source []byte, specialCharacters string, minOccurrences int) bool {
  297. s := specialCharacters
  298. s = strings.Replace(s, "\\", "\\\\", -1)
  299. s = strings.Replace(s, ".", "\\.", -1)
  300. s = strings.Replace(s, " ", "\\s", -1)
  301. s = strings.Replace(s, "-", "\\-", -1)
  302. s = strings.Replace(s, "[", "\\[", -1)
  303. s = strings.Replace(s, "]", "\\]", -1)
  304. r := regexp.MustCompile(`[` + s + `]+`)
  305. var matches [][]byte
  306. if matches = r.FindAll(source, -1); matches == nil {
  307. return false
  308. }
  309. return len(matches) >= minOccurrences
  310. }
  311. // Determine if the hash currently abides by the length restrictions
  312. func validateLength(source []byte, minimum, maximum int) bool {
  313. if minimum > -1 && len(source) < minimum {
  314. return false
  315. }
  316. if maximum > -1 && len(source) > maximum {
  317. return false
  318. }
  319. return true
  320. }