PageRenderTime 27ms CodeModel.GetById 19ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 1ms

/trunk_go/nonce/nonce.go

https://code.google.com/p/nonce-lib/
Go | 149 lines | 97 code | 17 blank | 35 comment | 23 complexity | 8a23775be266579af11056a0c5048874 MD5 | raw file
  1// Author: Tyler Egeto, tyleregeto.com
  2// Free to use, modify, and redistribute.
  3
  4// A simple nonce implmentation. Nonce, (number used once), is a
  5// simple way of adding extra security to a web application, particularly
  6// preventing replay attacks, semantic URL attacks, and also verify the
  7// source of the request. For more information see these resources:
  8// a) http://tyleregeto.com/a-guide-to-nonce
  9// b) http://en.wikipedia.org/wiki/Cryptographic_nonce
 10
 11package nonce
 12
 13import (
 14	"fmt"
 15	"time"
 16	"crypto/sha1"
 17	"os"
 18	"io/ioutil"
 19	"strconv"
 20	"log"
 21)
 22
 23var (
 24	// Used in every nonce hash generation. It is recommeneded that you
 25	// set this to a value unique to your application.
 26	Salt string = "a-fistfull-of-dollars"
 27	// The max age of a nonce before it expires. In nanoseconds.
 28	// Default value is 24 hours
 29	MaxAge int64 = 1e9 * 60 * 60 * 24;
 30	// The location where expired nonce data is stored
 31	SavePath = "/tmp/"
 32	// Prefix used to identify files created by this package.
 33	filePrefix string = "nonceegeto_"
 34	// How often the GC should run
 35	GcInterval int64 = 1e9 * 60 * 60//1.0 hrs
 36)
 37
 38type Nonce struct {
 39	Nonce string
 40	Timestamp int64
 41}
 42
 43// Generates a new nonce token based on the arguments & the package
 44// level salt. Arguments are handled using fmt.Sprintf.
 45func NewNonce(args ...interface{}) (n *Nonce) {
 46	return makeNonce(time.Nanoseconds(), args)
 47}
 48
 49// Recreates a nonce given a timestamp. If the recreated nonce does not match
 50// the one passed in by the user, it is invalid.
 51func RecreateNonce(timestamp int64, args ...interface{}) (n *Nonce) {
 52	return makeNonce(timestamp, args)
 53}
 54
 55func makeNonce(timestamp int64, args ...interface{}) (n *Nonce) {
 56	str := fmt.Sprint(args...)
 57	str = fmt.Sprintf("a%s7xf6g%s%s9", Salt, str, timestamp)
 58	hash := sha1.New()
 59	hash.Write([]byte(str))
 60	nonce := fmt.Sprintf("%x", hash.Sum())
 61	return &Nonce{Nonce:nonce, Timestamp:timestamp}
 62}
 63
 64// Checks if a nonce is valid & can be used.
 65func IsValid(nonce *Nonce) bool {
 66	//If the time stamp is too old it is expired
 67	diff := time.Nanoseconds() - nonce.Timestamp
 68	if diff > MaxAge {
 69		return false
 70	}
 71	
 72	//if a file exists it has already been used
 73	//we expect an error to occur indicating the file doen't exist
 74	_, err := os.Stat(SavePath + filePrefix + nonce.Nonce);
 75	if(err == nil) {
 76		return false;
 77	}
 78	
 79	//otherwise it is ok
 80	return true
 81}
 82
 83// Expires a nonce value so it cannot be used. Once expired calls to IsValid
 84// will return false.
 85func Expire(nonce *Nonce) (e *os.Error) {
 86	file, err := os.Create(SavePath + filePrefix + nonce.Nonce)
 87	if err != nil {
 88		fmt.Println(err.String())
 89		return &err
 90	}
 91	
 92	// The comtents of ths file will be its time stamp. GC will use this value.
 93	timestamp := fmt.Sprintf("%d", nonce.Timestamp)
 94	_, err = file.WriteString(timestamp);
 95	if err != nil {
 96		fmt.Println(err.String())
 97		return &err
 98	}
 99	err = file.Close()
100	
101	return nil
102}
103
104// When a nonce is expired files are created to mark them so. `gc` runs
105// to clean up these expired files after some time passes.
106func gc() {
107	for {
108		//grab all the files in the save dir
109		files, err := ioutil.ReadDir(SavePath)
110		prefixLen := len(filePrefix)
111		if err == nil {
112			for _, fileInfo := range(files) {
113				//look for files that start with our prefix
114				name := fileInfo.Name;
115				if len(name) > prefixLen && name[:prefixLen] == filePrefix {
116					//the file content is expected to be a timestamp 
117					content, err := ioutil.ReadFile(SavePath + name)
118					if err != nil {
119						log.Println(err.String())
120						continue
121					}
122					timestamp, err := strconv.Atoi64(string(content))
123					if(err == nil) {
124						diff := time.Nanoseconds() - timestamp
125						//if the file is old enough delete it
126						if diff > MaxAge {
127							err = os.Remove(SavePath + name)
128							if err != nil {
129								log.Println(err.String())
130							}
131						}
132					} else {
133						log.Println(err.String())
134					}
135				}
136			}
137		} else {
138			log.Println(err.String())
139		}
140		//delay until the next interval
141		time.Sleep(int64(GcInterval))
142	}
143}
144
145// init starts a goroutine to clean up old files every once in a while.
146func init() {
147	go gc()
148}
149