Browse Source

refactoring and basic usability reached

bmallred 10 years ago
parent
commit
5e7c62b47c
7 changed files with 528 additions and 441 deletions
  1. BIN
      084e0343a0486ff05530df6c705c8bb4
  2. 88 0
      file.go
  3. 260 0
      handlers.go
  4. 6 429
      main.go
  5. 7 12
      resources.go
  6. 11 0
      security.go
  7. 156 0
      site.go

BIN
084e0343a0486ff05530df6c705c8bb4


+ 88 - 0
file.go

@ -0,0 +1,88 @@
1
package main
2
3
import (
4
	"bytes"
5
	"compress/gzip"
6
	"crypto/md5"
7
	"encoding/json"
8
	"fmt"
9
	"io/ioutil"
10
	"os"
11
)
12
13
func save(file, passphrase string, sites []Site) error {
14
	// If the file doesn't exist then create it
15
	if _, err := os.Stat(file); os.IsNotExist(err) {
16
		_, err = os.Create(file)
17
		if err != nil {
18
			return err
19
		}
20
	}
21
22
	// Marshal the JSON
23
	b, err := json.Marshal(sites)
24
	if err != nil {
25
		return err
26
	}
27
28
	// Compress the contents
29
	var buffer bytes.Buffer
30
	gzip := gzip.NewWriter(&buffer)
31
	if err != nil {
32
		return err
33
	}
34
	gzip.Write(b)
35
	gzip.Close()
36
37
	// Write to the file
38
	fi, err := os.OpenFile(file, os.O_WRONLY, 0666)
39
	if err != nil {
40
		return err
41
	}
42
	_, err = fi.Write(buffer.Bytes())
43
	if err != nil {
44
		return err
45
	}
46
	fi.Close()
47
48
	return nil
49
}
50
51
// Read the password book
52
func read(file, passphrase string) ([]Site, error) {
53
	// If the file doesn't exist yet no worries
54
	if _, err := os.Stat(file); os.IsNotExist(err) {
55
		return []Site{}, nil
56
	}
57
58
	// Bring in the compressed data
59
	fi, err := os.Open(file)
60
	if err != nil {
61
		return nil, err
62
	}
63
64
	// Decompress the file contents
65
	gzip, err := gzip.NewReader(fi)
66
	if err != nil {
67
		return nil, err
68
	}
69
	decompressed, err := ioutil.ReadAll(gzip)
70
	gzip.Close()
71
72
	// Unmarshal the JSON information
73
	var sites []Site
74
	err = json.Unmarshal(decompressed, &sites)
75
	if err != nil {
76
		return nil, err
77
	}
78
	fi.Close()
79
80
	return sites, nil
81
}
82
83
// Get the book name
84
func getBookname(profile string) string {
85
	hash := md5.New()
86
	hash.Write([]byte(profile))
87
	return fmt.Sprintf("%x", string(hash.Sum(nil)))
88
}

+ 260 - 0
handlers.go

@ -0,0 +1,260 @@
1
package main
2
3
import (
4
	"fmt"
5
	"net/http"
6
	"net/url"
7
	"os"
8
	"strconv"
9
	"text/template"
10
)
11
12
type Page struct {
13
	Profile    string
14
	Passphrase string
15
	Sites      []Site
16
}
17
18
func GenerateHandler(w http.ResponseWriter, r *http.Request) {
19
	profile := r.FormValue("profile")
20
	passphrase := r.FormValue("p")
21
	host := r.FormValue("host")
22
	minimumLength, _ := strconv.Atoi(r.FormValue("minimumLength"))
23
	maximumLength, _ := strconv.Atoi(r.FormValue("maximumLength"))
24
	minimumDigits, _ := strconv.Atoi(r.FormValue("minimumDigits"))
25
	minimumUppercase, _ := strconv.Atoi(r.FormValue("minimumUppercase"))
26
	minimumSpecialCharacters, _ := strconv.Atoi(r.FormValue("minimumSpecialCharacters"))
27
	specialCharacters := r.FormValue("specialCharacters")
28
29
	if profile == "" || passphrase == "" || host == "" {
30
		http.Error(w, "Missing credentials", http.StatusUnauthorized)
31
		return
32
	}
33
34
	url, err := url.Parse(host)
35
	if err != nil {
36
		http.Error(w, err.Error(), http.StatusInternalServerError)
37
		return
38
	}
39
40
	site := Site{
41
		Host:                      url.Host,
42
		MinimumLength:             minimumLength,
43
		MaximumLength:             maximumLength,
44
		SpecialCharacters:         specialCharacters,
45
		NumberOfSpecialCharacters: minimumSpecialCharacters,
46
		NumberOfDigits:            minimumDigits,
47
		NumberOfUpperCase:         minimumUppercase,
48
		Revision:                  0,
49
	}
50
51
	book := getBookname(profile)
52
	sites, err := read(book, passphrase)
53
	if err != nil {
54
		http.Error(w, err.Error(), http.StatusInternalServerError)
55
		return
56
	}
57
	sites = append(sites, site)
58
	err = save(book, passphrase, sites)
59
	if err != nil {
60
		http.Error(w, err.Error(), http.StatusInternalServerError)
61
		return
62
	}
63
64
	http.Redirect(w, r, "/book", http.StatusSeeOther)
65
}
66
67
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
68
	profile := r.FormValue("profile")
69
	passphrase := r.FormValue("p")
70
	newPassphrase := r.FormValue("newPassphrase")
71
	confirmPassphrase := r.FormValue("confirmPassphrase")
72
	cmd := r.FormValue("cmd")
73
74
	if profile == "" || passphrase == "" || newPassphrase == "" || confirmPassphrase == "" || cmd == "" {
75
		http.Error(w, "Missing credentials", http.StatusUnauthorized)
76
		return
77
	}
78
79
	book := getBookname(profile)
80
81
	if cmd == "delete" {
82
		err := os.Remove(book)
83
		if err != nil {
84
			// Return an error
85
			http.Error(w, err.Error(), http.StatusInternalServerError)
86
			return
87
		}
88
	} else if cmd == "update" {
89
		if newPassphrase != confirmPassphrase {
90
		}
91
		sites, err := read(book, passphrase)
92
		if err != nil {
93
			http.Error(w, err.Error(), http.StatusInternalServerError)
94
			return
95
		}
96
		err = save(book, newPassphrase, sites)
97
		if err != nil {
98
			http.Error(w, err.Error(), http.StatusInternalServerError)
99
			return
100
		}
101
	}
102
103
	http.Redirect(w, r, "/book", http.StatusSeeOther)
104
}
105
106
func RefreshHandler(w http.ResponseWriter, r *http.Request) {
107
	profile := r.FormValue("profile")
108
	passphrase := r.FormValue("p")
109
	host := r.FormValue("host")
110
111
	if profile == "" || passphrase == "" || host == "" {
112
		http.Error(w, "Missing credentials", http.StatusUnauthorized)
113
		return
114
	}
115
116
	// Update the revision number and generate a new password
117
	book := getBookname(profile)
118
	sites, err := read(book, passphrase)
119
	if err != nil {
120
		http.Error(w, err.Error(), http.StatusInternalServerError)
121
		return
122
	}
123
	for i, s := range sites {
124
		if s.Host == host {
125
			sites[i].Revision++
126
			break
127
		}
128
	}
129
	err = save(book, passphrase, sites)
130
	if err != nil {
131
		http.Error(w, err.Error(), http.StatusInternalServerError)
132
		return
133
	}
134
135
	http.Redirect(w, r, "/book", http.StatusSeeOther)
136
}
137
138
func RemoveHandler(w http.ResponseWriter, r *http.Request) {
139
	profile := r.FormValue("profile")
140
	passphrase := r.FormValue("p")
141
	host := r.FormValue("host")
142
143
	if profile == "" || passphrase == "" || host == "host" {
144
		http.Error(w, "Missing credentials", http.StatusUnauthorized)
145
		return
146
	}
147
148
	// Remove the site from our book and save it
149
	book := getBookname(profile)
150
	sites, err := read(book, passphrase)
151
	if err != nil {
152
		http.Error(w, err.Error(), http.StatusInternalServerError)
153
		return
154
	}
155
	for i, site := range sites {
156
		if site.Host == host {
157
			sites = append(sites[:i], sites[i+1:]...)
158
			break
159
		}
160
	}
161
	err = save(book, passphrase, sites)
162
	if err != nil {
163
		http.Error(w, err.Error(), http.StatusInternalServerError)
164
		return
165
	}
166
167
	http.Redirect(w, r, "/book", http.StatusSeeOther)
168
}
169
170
func SignOffHandler(w http.ResponseWriter, r *http.Request) {
171
	cookieProfile := &http.Cookie{
172
		Name:   "profile",
173
		MaxAge: -1,
174
	}
175
	cookiePassphrase := &http.Cookie{
176
		Name:   "passphrase",
177
		MaxAge: -1,
178
	}
179
180
	http.SetCookie(w, cookieProfile)
181
	http.SetCookie(w, cookiePassphrase)
182
	http.Redirect(w, r, "/", http.StatusSeeOther)
183
}
184
185
func BookHandler(w http.ResponseWriter, r *http.Request) {
186
	profile := r.FormValue("profile")
187
	passphrase := r.FormValue("p")
188
189
	if profile == "" || passphrase == "" {
190
		c, err := r.Cookie("profile")
191
		if err == nil {
192
			profile = c.Value
193
		}
194
195
		c, err = r.Cookie("passphrase")
196
		if err == nil {
197
			passphrase = c.Value
198
		}
199
	}
200
201
	if profile == "" || passphrase == "" {
202
		http.Redirect(w, r, "/book", http.StatusSeeOther)
203
		return
204
	}
205
206
	// Set cookies
207
	//expire := time.Now().AddDate(0, 0, 1)
208
	cookieProfile := &http.Cookie{
209
		Name:  "profile",
210
		Value: profile,
211
		//Path:       "/",
212
		//Domain:     "localhost",
213
		//Expires:    expire,
214
		//RawExpires: expire.Format(time.UnixDate),
215
		//MaxAge:     0,
216
		//Secure:     false,
217
		//HttpOnly:   true,
218
	}
219
	http.SetCookie(w, cookieProfile)
220
	cookiePassphrase := &http.Cookie{
221
		Name:  "passphrase",
222
		Value: passphrase,
223
		//Path:       "/",
224
		//Domain:     "localhost",
225
		//Expires:    expire,
226
		//RawExpires: expire.Format(time.UnixDate),
227
		//MaxAge:     0,
228
		//Secure:     false,
229
		//HttpOnly:   true,
230
	}
231
	http.SetCookie(w, cookiePassphrase)
232
	book := getBookname(profile)
233
234
	sites, err := read(book, passphrase)
235
	if err != nil {
236
		http.Error(w, err.Error(), http.StatusInternalServerError)
237
		return
238
	}
239
	for i, s := range sites {
240
		p := s.generatePassphrase(profile, passphrase)
241
		sites[i].Password = fmt.Sprintf("%s", string(p))
242
	}
243
244
	page := Page{
245
		Profile:    profile,
246
		Passphrase: passphrase,
247
		Sites:      sites,
248
	}
249
250
	t := template.Must(template.New("book").Parse(templateBook))
251
	err = t.Execute(w, page)
252
	if err != nil {
253
		http.Error(w, err.Error(), http.StatusInternalServerError)
254
		return
255
	}
256
}
257
258
func DefaultHandler(w http.ResponseWriter, r *http.Request) {
259
	fmt.Fprintf(w, templateIndex)
260
}

+ 6 - 429
main.go

@ -1,440 +1,17 @@
1 1
package main
2 2
3 3
import (
4
	"bytes"
5
	"compress/gzip"
6
	"crypto/md5"
7
	"crypto/sha512"
8
	"encoding/json"
9
	"fmt"
10
	"io/ioutil"
11 4
	"log"
12 5
	"net/http"
13
	"os"
14
	"regexp"
15
	"strconv"
16
	"strings"
17
	"text/template"
18 6
)
19 7
20
type Site struct {
21
	Host                      string `json:host`
22
	MinimumLength             int    `json:minimumLength`
23
	MaximumLength             int    `json:maximumLength`
24
	SpecialCharacters         string `json:specialCharacters`
25
	NumberOfSpecialCharacters int    `json:numberOfSpecialCharacters`
26
	NumberOfUpperCase         int    `json:numberOfUpperCase`
27
	NumberOfDigits            int    `json:numberOfDigits`
28
	Revision                  int    `json:revision`
29
	Password                  string `json:",omitempty"`
30
}
31
32
type Page struct {
33
	Profile    string
34
	Passphrase string
35
	Sites      []Site
36
}
37
38 8
func main() {
39
	http.HandleFunc("/api/generate", func(w http.ResponseWriter, r *http.Request) {
40
		profile := r.FormValue("profile")
41
		passphrase := r.FormValue("p")
42
		host := r.FormValue("host")
43
		minimumLength, _ := strconv.Atoi(r.FormValue("minimumLength"))
44
		maximumLength, _ := strconv.Atoi(r.FormValue("maximumLength"))
45
		minimumDigits, _ := strconv.Atoi(r.FormValue("minimumDigits"))
46
		minimumUppercase, _ := strconv.Atoi(r.FormValue("minimumUppercase"))
47
		minimumSpecialCharacters, _ := strconv.Atoi(r.FormValue("minimumSpecialCharacters"))
48
		specialCharacters := r.FormValue("specialCharacters")
49
50
		if profile == "" || passphrase == "" || host == "" {
51
			http.Error(w, "Missing credentials", http.StatusUnauthorized)
52
			return
53
		}
54
55
		site := Site{
56
			Host:                      host,
57
			MinimumLength:             minimumLength,
58
			MaximumLength:             maximumLength,
59
			SpecialCharacters:         specialCharacters,
60
			NumberOfSpecialCharacters: minimumSpecialCharacters,
61
			NumberOfDigits:            minimumDigits,
62
			NumberOfUpperCase:         minimumUppercase,
63
			Revision:                  0,
64
		}
65
66
		book := getBookname(profile)
67
		sites, err := read(book, passphrase)
68
		if err != nil {
69
			http.Error(w, err.Error(), http.StatusInternalServerError)
70
			return
71
		}
72
		sites = append(sites, site)
73
		err = save(book, passphrase, sites)
74
		if err != nil {
75
			http.Error(w, err.Error(), http.StatusInternalServerError)
76
			return
77
		}
78
		http.Redirect(w, r, "/", 200)
79
	})
80
	http.HandleFunc("/api/update", func(w http.ResponseWriter, r *http.Request) {
81
		profile := r.FormValue("profile")
82
		passphrase := r.FormValue("p")
83
		newPassphrase := r.FormValue("newPassphrase")
84
		confirmPassphrase := r.FormValue("confirmPassphrase")
85
		cmd := r.FormValue("cmd")
86
87
		if profile == "" || passphrase == "" || newPassphrase == "" || confirmPassphrase == "" || cmd == "" {
88
			http.Error(w, "Missing credentials", http.StatusUnauthorized)
89
			return
90
		}
91
92
		book := getBookname(profile)
93
94
		if cmd == "delete" {
95
			err := os.Remove(book)
96
			if err != nil {
97
				// Return an error
98
				http.Error(w, err.Error(), http.StatusInternalServerError)
99
				return
100
			}
101
		} else if cmd == "update" {
102
			if newPassphrase != confirmPassphrase {
103
			}
104
			sites, err := read(book, passphrase)
105
			if err != nil {
106
				http.Error(w, err.Error(), http.StatusInternalServerError)
107
				return
108
			}
109
			err = save(book, newPassphrase, sites)
110
			if err != nil {
111
				http.Error(w, err.Error(), http.StatusInternalServerError)
112
				return
113
			}
114
		}
115
		http.Redirect(w, r, "/", 200)
116
	})
117
	http.HandleFunc("/api/refresh", func(w http.ResponseWriter, r *http.Request) {
118
		profile := r.FormValue("profile")
119
		passphrase := r.FormValue("p")
120
		host := r.FormValue("host")
121
122
		if profile == "" || passphrase == "" || host == "" {
123
			http.Error(w, "Missing credentials", http.StatusUnauthorized)
124
			return
125
		}
126
127
		// Update the revision number and generate a new password
128
		book := getBookname(profile)
129
		sites, err := read(book, passphrase)
130
		if err != nil {
131
			http.Error(w, err.Error(), http.StatusInternalServerError)
132
			return
133
		}
134
		for _, site := range sites {
135
			if site.Host == host {
136
				site.Revision++
137
				break
138
			}
139
		}
140
		err = save(book, passphrase, sites)
141
		if err != nil {
142
			http.Error(w, err.Error(), http.StatusInternalServerError)
143
			return
144
		}
145
		http.Redirect(w, r, "/", 200)
146
	})
147
	http.HandleFunc("/api/remove", func(w http.ResponseWriter, r *http.Request) {
148
		profile := r.FormValue("profile")
149
		passphrase := r.FormValue("p")
150
		host := r.FormValue("host")
151
152
		if profile == "" || passphrase == "" || host == "host" {
153
			http.Error(w, "Missing credentials", http.StatusUnauthorized)
154
			return
155
		}
156
157
		// Remove the site from our book and save it
158
		book := getBookname(profile)
159
		sites, err := read(book, passphrase)
160
		if err != nil {
161
			http.Error(w, err.Error(), http.StatusInternalServerError)
162
			return
163
		}
164
		for i, site := range sites {
165
			if site.Host == host {
166
				sites = append(sites[:i], sites[i+1:]...)
167
				break
168
			}
169
		}
170
		err = save(book, passphrase, sites)
171
		if err != nil {
172
			http.Error(w, err.Error(), http.StatusInternalServerError)
173
			return
174
		}
175
		http.Redirect(w, r, "/", 200)
176
	})
177
178
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
179
		profile := r.FormValue("profile")
180
		passphrase := r.FormValue("p")
181
182
		if profile == "" || passphrase == "" {
183
			fmt.Fprintf(w, templateIndex)
184
		} else {
185
			book := getBookname(profile)
186
			sites, err := read(book, passphrase)
187
			if err != nil {
188
				http.Error(w, err.Error(), http.StatusInternalServerError)
189
				return
190
			}
191
			for _, s := range sites {
192
				p, err := generatePassphrase(profile, passphrase, s)
193
				if err != nil {
194
				}
195
				s.Password = fmt.Sprintf("%s", string(p))
196
			}
197
198
			page := Page{
199
				Profile:    profile,
200
				Passphrase: passphrase,
201
				Sites:      sites,
202
			}
203
204
			t := template.Must(template.New("book").Parse(templateBook))
205
			err = t.Execute(w, page)
206
			if err != nil {
207
				http.Error(w, err.Error(), http.StatusInternalServerError)
208
				return
209
			}
210
		}
211
	})
9
	http.HandleFunc("/api/generate", GenerateHandler)
10
	http.HandleFunc("/api/update", ProfileHandler)
11
	http.HandleFunc("/api/refresh", RefreshHandler)
12
	http.HandleFunc("/api/remove", RemoveHandler)
13
	http.HandleFunc("/book", BookHandler)
14
	http.HandleFunc("/", DefaultHandler)
212 15
213 16
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
214 17
}
215
216
func save(file, passphrase string, sites []Site) error {
217
	// If the file doesn't exist then create it
218
	if _, err := os.Stat(file); os.IsNotExist(err) {
219
		_, err = os.Create(file)
220
		if err != nil {
221
			return err
222
		}
223
	}
224
225
	// Marshal the JSON
226
	b, err := json.Marshal(sites)
227
	if err != nil {
228
		return err
229
	}
230
231
	// Compress the contents
232
	var buffer bytes.Buffer
233
	gzip := gzip.NewWriter(&buffer)
234
	if err != nil {
235
		return err
236
	}
237
	gzip.Write(b)
238
	gzip.Close()
239
240
	// Write to the file
241
	fi, err := os.OpenFile(file, os.O_WRONLY, 0666)
242
	if err != nil {
243
		return err
244
	}
245
	_, err = fi.Write(buffer.Bytes())
246
	if err != nil {
247
		return err
248
	}
249
	fi.Close()
250
251
	return nil
252
}
253
254
// Read the password book
255
func read(file, passphrase string) ([]Site, error) {
256
	// If the file doesn't exist yet no worries
257
	if _, err := os.Stat(file); os.IsNotExist(err) {
258
		return []Site{}, nil
259
	}
260
261
	// Bring in the compressed data
262
	fi, err := os.Open(file)
263
	if err != nil {
264
		return nil, err
265
	}
266
267
	// Decompress the file contents
268
	gzip, err := gzip.NewReader(fi)
269
	if err != nil {
270
		return nil, err
271
	}
272
	decompressed, err := ioutil.ReadAll(gzip)
273
	gzip.Close()
274
275
	// Unmarshal the JSON information
276
	var sites []Site
277
	err = json.Unmarshal(decompressed, &sites)
278
	if err != nil {
279
		return nil, err
280
	}
281
	fi.Close()
282
283
	return sites, nil
284
}
285
286
// Get the book name
287
func getBookname(profile string) string {
288
	hash := md5.New()
289
	hash.Write([]byte(profile))
290
	return fmt.Sprintf("%x", string(hash.Sum(nil)))
291
}
292
293
// Encrypt the password book
294
func encrypt(clearText, profile, passphrase string) ([]byte, error) {
295
	return nil, nil
296
}
297
298
// Decrypt the password book
299
func decrypt(encryptedText, profile, passphrase string) ([]byte, error) {
300
	return nil, nil
301
}
302
303
// Generate the passphrase
304
func generatePassphrase(profile, passphrase string, settings Site) ([]byte, error) {
305
	clearText := fmt.Sprintf(
306
		"%s-%s-%s-%s",
307
		strings.ToLower(profile),
308
		strings.ToLower(passphrase),
309
		strings.ToLower(settings.Host),
310
		settings.Revision)
311
312
	sha := sha512.New()
313
	sha.Write([]byte(clearText))
314
	hash := sha.Sum(nil)
315
	hash = []byte(fmt.Sprintf("%x", hash))
316
317
	// Apply site criteria
318
	applySiteSettings(hash, settings)
319
320
	// If there is a maximum length truncate the hash
321
	if settings.MaximumLength > -1 {
322
		hash = hash[:settings.MaximumLength]
323
	}
324
325
	// Ensure the length is adequate
326
	if !validateLength(hash, settings.MinimumLength, settings.MaximumLength) {
327
		log.Println("Does not meed the length requirements")
328
	}
329
330
	return hash, nil
331
}
332
333
// Apply site settings to the hashed value
334
func applySiteSettings(source []byte, settings Site) []byte {
335
	if !containsUppercase(source, settings.NumberOfUpperCase) {
336
		i := 0
337
		r := regexp.MustCompile(`[a-z]+`)
338
339
		var matches [][]int
340
		if matches = r.FindAllIndex(source, -1); matches != nil {
341
			for _, v := range matches {
342
				if i < settings.NumberOfUpperCase {
343
					c := strings.ToUpper(string(source[v[0]]))
344
					source[v[0]] = []byte(c)[0]
345
					i += 1
346
				}
347
			}
348
		}
349
	}
350
351
	if !containsDigits(source, settings.NumberOfDigits) {
352
		i := 0
353
		r := regexp.MustCompile(`[a-z]+`)
354
355
		var matches [][]int
356
		if matches = r.FindAllIndex(source, -1); matches != nil {
357
			for _, v := range matches {
358
				if i < settings.NumberOfDigits {
359
					source[v[0]] = byte(i)
360
					i += 1
361
				}
362
			}
363
		}
364
	}
365
366
	if !containsSpecialCharacters(source, settings.SpecialCharacters, settings.NumberOfSpecialCharacters) {
367
		i := 0
368
		r := regexp.MustCompile(`[a-z]+`)
369
370
		var matches [][]int
371
		if matches = r.FindAllIndex(source, -1); matches != nil {
372
			for _, v := range matches {
373
				if i < settings.NumberOfSpecialCharacters {
374
					i += 1
375
					source[v[0]] = []byte(settings.SpecialCharacters)[len(settings.SpecialCharacters)-i]
376
				}
377
			}
378
		}
379
	}
380
381
	return source
382
}
383
384
// Determine if the hash currently contains the appropriate amount of digits
385
func containsDigits(source []byte, minOccurrences int) bool {
386
	r := regexp.MustCompile(`\d`)
387
388
	var matches [][]byte
389
	if matches = r.FindAll(source, -1); matches == nil {
390
		return false
391
	}
392
393
	return len(matches) >= minOccurrences
394
}
395
396
// Determine if the hash currently contains the appropriate amount of uppercase characters
397
func containsUppercase(source []byte, minOccurrences int) bool {
398
	r := regexp.MustCompile(`[A-Z]+`)
399
400
	var matches [][]byte
401
	if matches = r.FindAll(source, -1); matches == nil {
402
		return false
403
	}
404
405
	return len(matches) >= minOccurrences
406
}
407
408
// Determine if the hash currently contains the appropriate amount of special characters from the allowed
409
// character set
410
func containsSpecialCharacters(source []byte, specialCharacters string, minOccurrences int) bool {
411
	s := specialCharacters
412
	s = strings.Replace(s, "\\", "\\\\", -1)
413
	s = strings.Replace(s, ".", "\\.", -1)
414
	s = strings.Replace(s, " ", "\\s", -1)
415
	s = strings.Replace(s, "-", "\\-", -1)
416
	s = strings.Replace(s, "[", "\\[", -1)
417
	s = strings.Replace(s, "]", "\\]", -1)
418
419
	r := regexp.MustCompile(`[` + s + `]+`)
420
421
	var matches [][]byte
422
	if matches = r.FindAll(source, -1); matches == nil {
423
		return false
424
	}
425
426
	return len(matches) >= minOccurrences
427
}
428
429
// Determine if the hash currently abides by the length restrictions
430
func validateLength(source []byte, minimum, maximum int) bool {
431
	if minimum > -1 && len(source) < minimum {
432
		return false
433
	}
434
435
	if maximum > -1 && len(source) > maximum {
436
		return false
437
	}
438
439
	return true
440
}

+ 7 - 12
resources.go

@ -64,17 +64,12 @@ const (
64 64
</head>
65 65
<body>
66 66
    <div class="container">
67
        <form action="/" method="post" class="form-signin">
67
        <form action="/book" method="post" class="form-signin">
68 68
            <h2 class="form-signin-heading">Please sign in</h2>
69 69
            <label for="profile" class="sr-only">Email address</label>
70 70
            <input type="text" id="profile" name="profile" class="form-control" placeholder="Username" required autofocus>
71 71
            <label for="p" class="sr-only">Password</label>
72 72
            <input type="password" id="p" name="p" class="form-control" placeholder="Password" required>
73
            <div class="checkbox">
74
                <label>
75
                    <input type="checkbox" value="remember-me"> Remember me
76
                </label>
77
            </div>
78 73
            <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
79 74
        </form>
80 75
    </div>
@ -191,7 +186,7 @@ const (
191 186
                                    <option>3</option>
192 187
                                    <option>4</option>
193 188
                                    <option>5</option>
194
                                    <option>6</option>
189
                                    <option selected="selected">6</option>
195 190
                                    <option>7</option>
196 191
                                    <option>8</option>
197 192
                                    <option>9</option>
@ -212,7 +207,7 @@ const (
212 207
                                    <option>9</option>
213 208
                                    <option>10</option>
214 209
                                    <option>11</option>
215
                                    <option>12</option>
210
                                    <option selected="selected">12</option>
216 211
                                    <option>13</option>
217 212
                                    <option>14</option>
218 213
                                    <option>15</option>
@ -231,7 +226,7 @@ const (
231 226
                                    <option>0</option>
232 227
                                    <option>1</option>
233 228
                                    <option>2</option>
234
                                    <option>3</option>
229
                                    <option selected="selected">3</option>
235 230
                                    <option>4</option>
236 231
                                    <option>5</option>
237 232
                                    <option>6</option>
@ -248,7 +243,7 @@ const (
248 243
                                <select id="minimumUppercase" name="minimumUppercase" class="form-control">
249 244
                                    <option>0</option>
250 245
                                    <option>1</option>
251
                                    <option>2</option>
246
                                    <option selected="selected">2</option>
252 247
                                    <option>3</option>
253 248
                                    <option>4</option>
254 249
                                    <option>5</option>
@ -266,7 +261,7 @@ const (
266 261
                                <select id="minimumSpecialCharacters" name="minimumSpecialCharacters" class="form-control">
267 262
                                    <option>0</option>
268 263
                                    <option>1</option>
269
                                    <option>2</option>
264
                                    <option selected="selected">2</option>
270 265
                                    <option>3</option>
271 266
                                    <option>4</option>
272 267
                                    <option>5</option>
@ -281,7 +276,7 @@ const (
281 276
                        <div class="form-group">
282 277
                            <label for="specialCharacters" class="col-xs-3 control-label">Special Characters</label>
283 278
                            <div class="col-xs-9">
284
                                <input id="specialCharacters" name="specialCharacters" type="text" class="form-control" value=" !@#$%^&*()_+-=<>,." />
279
                                <input id="specialCharacters" name="specialCharacters" type="text" class="form-control" value=" !@#$%^&*_+-=,." />
285 280
                            </div>
286 281
                        </div>
287 282
                        <div class="form-group">

+ 11 - 0
security.go

@ -0,0 +1,11 @@
1
package main
2
3
// Encrypt the password book
4
func encrypt(clearText, profile, passphrase string) ([]byte, error) {
5
	return nil, nil
6
}
7
8
// Decrypt the password book
9
func decrypt(encryptedText, profile, passphrase string) ([]byte, error) {
10
	return nil, nil
11
}

+ 156 - 0
site.go

@ -0,0 +1,156 @@
1
package main
2
3
import (
4
	"crypto/sha512"
5
	"fmt"
6
	"log"
7
	"regexp"
8
	"strings"
9
)
10
11
type Site struct {
12
	Host                      string `json:host`
13
	MinimumLength             int    `json:minimumLength`
14
	MaximumLength             int    `json:maximumLength`
15
	SpecialCharacters         string `json:specialCharacters`
16
	NumberOfSpecialCharacters int    `json:numberOfSpecialCharacters`
17
	NumberOfUpperCase         int    `json:numberOfUpperCase`
18
	NumberOfDigits            int    `json:numberOfDigits`
19
	Revision                  int    `json:revision`
20
	Password                  string `json:",omitempty"`
21
}
22
23
// Generate the passphrase
24
func (s *Site) generatePassphrase(profile, passphrase string) []byte {
25
	clearText := fmt.Sprintf(
26
		"%s-%s-%s-%s",
27
		strings.ToLower(profile),
28
		strings.ToLower(passphrase),
29
		strings.ToLower(s.Host),
30
		s.Revision)
31
32
	sha := sha512.New()
33
	sha.Write([]byte(clearText))
34
35
	return s.applyCriteria(sha.Sum(nil))
36
}
37
38
func (s *Site) applyCriteria(sha []byte) []byte {
39
	hash := []byte(fmt.Sprintf("%x", sha))
40
41
	if !containsUppercase(hash, s.NumberOfUpperCase) {
42
		i := 0
43
		r := regexp.MustCompile(`[a-z]+`)
44
45
		var matches [][]int
46
		if matches = r.FindAllIndex(hash, -1); matches != nil {
47
			for _, v := range matches {
48
				if i < s.NumberOfUpperCase {
49
					c := strings.ToUpper(string(hash[v[0]]))
50
					hash[v[0]] = []byte(c)[0]
51
					i += 1
52
				}
53
			}
54
		}
55
	}
56
57
	if !containsDigits(hash, s.NumberOfDigits) {
58
		i := 0
59
		r := regexp.MustCompile(`[a-z]+`)
60
61
		var matches [][]int
62
		if matches = r.FindAllIndex(hash, -1); matches != nil {
63
			for _, v := range matches {
64
				if i < s.NumberOfDigits {
65
					hash[v[0]] = byte(i)
66
					i += 1
67
				}
68
			}
69
		}
70
	}
71
72
	if !containsSpecialCharacters(hash, s.SpecialCharacters, s.NumberOfSpecialCharacters) {
73
		i := 0
74
		r := regexp.MustCompile(`[a-z]+`)
75
76
		var matches [][]int
77
		if matches = r.FindAllIndex(hash, -1); matches != nil {
78
			for _, v := range matches {
79
				if i < s.NumberOfSpecialCharacters {
80
					i += 1
81
					hash[v[0]] = []byte(s.SpecialCharacters)[len(s.SpecialCharacters)-i]
82
				}
83
			}
84
		}
85
	}
86
87
	// If there is a maximum length truncate the hash
88
	if s.MaximumLength > -1 {
89
		hash = hash[:s.MaximumLength]
90
	}
91
92
	// Ensure the length is adequate
93
	if !validateLength(hash, s.MinimumLength, s.MaximumLength) {
94
		log.Println("Does not meed the length requirements")
95
	}
96
97
	return hash
98
}
99
100
// Determine if the hash currently contains the appropriate amount of digits
101
func containsDigits(source []byte, minOccurrences int) bool {
102
	r := regexp.MustCompile(`\d`)
103
104
	var matches [][]byte
105
	if matches = r.FindAll(source, -1); matches == nil {
106
		return false
107
	}
108
109
	return len(matches) >= minOccurrences
110
}
111
112
// Determine if the hash currently contains the appropriate amount of uppercase characters
113
func containsUppercase(source []byte, minOccurrences int) bool {
114
	r := regexp.MustCompile(`[A-Z]+`)
115
116
	var matches [][]byte
117
	if matches = r.FindAll(source, -1); matches == nil {
118
		return false
119
	}
120
121
	return len(matches) >= minOccurrences
122
}
123
124
// Determine if the hash currently contains the appropriate amount of special characters from the allowed
125
// character set
126
func containsSpecialCharacters(source []byte, specialCharacters string, minOccurrences int) bool {
127
	s := specialCharacters
128
	s = strings.Replace(s, "\\", "\\\\", -1)
129
	s = strings.Replace(s, ".", "\\.", -1)
130
	s = strings.Replace(s, " ", "\\s", -1)
131
	s = strings.Replace(s, "-", "\\-", -1)
132
	s = strings.Replace(s, "[", "\\[", -1)
133
	s = strings.Replace(s, "]", "\\]", -1)
134
135
	r := regexp.MustCompile(`[` + s + `]+`)
136
137
	var matches [][]byte
138
	if matches = r.FindAll(source, -1); matches == nil {
139
		return false
140
	}
141
142
	return len(matches) >= minOccurrences
143
}
144
145
// Determine if the hash currently abides by the length restrictions
146
func validateLength(source []byte, minimum, maximum int) bool {
147
	if minimum > -1 && len(source) < minimum {
148
		return false
149
	}
150
151
	if maximum > -1 && len(source) > maximum {
152
		return false
153
	}
154
155
	return true
156
}