github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/api/common/sign.go (about) 1 package common 2 3 import ( 4 "bytes" 5 "crypto/sha512" 6 "encoding/hex" 7 "fmt" 8 "net/http" 9 "strings" 10 "time" 11 12 "code.google.com/p/go.crypto/pbkdf2" 13 "github.com/agl/ed25519" 14 15 edhelpers "github.com/hoffie/larasync/helpers/ed25519" 16 ) 17 18 const ( 19 // PrivateKeySize denotes how many bytes a private key needs (binary encoded) 20 PrivateKeySize = ed25519.PrivateKeySize 21 // PublicKeySize denotes how many bytes a pubkey needs (binary encoded) 22 PublicKeySize = ed25519.PublicKeySize 23 // SignatureSize denotes how many bytes a sig needs (binary encoded) 24 SignatureSize = ed25519.SignatureSize 25 ) 26 27 var staticSalt = []byte("larasync") 28 29 // SignWithPassphrase signs the given request using the given admin passphrase 30 func SignWithPassphrase(req *http.Request, passphrase []byte) error { 31 key, err := PassphraseToKey(passphrase) 32 if err != nil { 33 return err 34 } 35 SignWithKey(req, key) 36 return nil 37 } 38 39 // SignWithKey signs the request with the given private key by adding an 40 // appropriate authorization header. 41 // A Date header is also appended if not yet existing 42 func SignWithKey(req *http.Request, key [PrivateKeySize]byte) { 43 if req.Header.Get("Date") == "" { 44 req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) 45 } 46 sig := getSignature(req, key) 47 req.Header.Set("Authorization", fmt.Sprintf("lara %s", 48 hex.EncodeToString(sig))) 49 } 50 51 // ValidateRequest checks whether the request signature is valid and 52 // matches the given public key. It also checks whether the request 53 // is not outdated according to the provided maxAge. 54 func ValidateRequest(req *http.Request, pubkey [PublicKeySize]byte, maxAge time.Duration) bool { 55 if !validateRequestSig(req, pubkey) { 56 return false 57 } 58 if !youngerThan(req, maxAge) { 59 return false 60 } 61 return true 62 } 63 64 // validateRequestSig is a helper which ensures that the request's signature 65 // is valid. It extracts the signature on its own. 66 func validateRequestSig(req *http.Request, pubkey [PublicKeySize]byte) bool { 67 auth := req.Header.Get("Authorization") 68 if auth == "" { 69 return false 70 } 71 if !strings.HasPrefix(auth, "lara ") { 72 return false 73 } 74 sig := strings.TrimPrefix(auth, "lara ") 75 if sig == "" { 76 return false 77 } 78 sigBytes, err := hex.DecodeString(sig) 79 if err != nil { 80 return false 81 } 82 if len(sigBytes) < SignatureSize { 83 return false 84 } 85 sigArr := new([SignatureSize]byte) 86 copy(sigArr[:], sigBytes[:SignatureSize]) 87 return verifySig(req, pubkey, *sigArr) 88 } 89 90 // youngerThan checks whether the request's Date header is at maximum 91 // maxAge old. 92 func youngerThan(req *http.Request, maxAge time.Duration) bool { 93 dateHeader := req.Header.Get("Date") 94 date, err := time.Parse(time.RFC1123, dateHeader) 95 if err != nil { 96 return false 97 } 98 if time.Now().UTC().Sub(date) > maxAge { 99 return false 100 } 101 return true 102 } 103 104 // getSignature uses public key cryptography to sign the request 105 // and return the resulting signature. 106 func getSignature(req *http.Request, key [PrivateKeySize]byte) []byte { 107 hash := getRequestHash(req) 108 sig := ed25519.Sign(&key, hash) 109 slSig := make([]byte, len(sig)) 110 copy(slSig, sig[0:len(sig)]) 111 return slSig 112 } 113 114 // getRequestHash returns the whole request's SHA512 hash. 115 func getRequestHash(req *http.Request) []byte { 116 mac := sha512.New() 117 err := concatenateTo(req, mac) 118 if err != nil { 119 // we use panic here as concatenateTo can only fail due to problems 120 // writing to the output writer; as our writer is a Hash instance which 121 // is not supposed to fail either, this is (hopefully) just a hypothetical 122 // just-in-case error check. 123 // returning the error here would just clobber the getSignature and the 124 // whole resulting method chain. 125 panic("concatenateTo failed") 126 } 127 hash := mac.Sum(nil) 128 return hash 129 } 130 131 // verifySig checks if the signature matches the provided 132 // public key and is valid for the given request. 133 func verifySig(req *http.Request, pubkey [PublicKeySize]byte, sig [SignatureSize]byte) bool { 134 hash := getRequestHash(req) 135 return ed25519.Verify(&pubkey, hash, &sig) 136 } 137 138 // PassphraseToKey converts the user-supplied passphrase to a key, usable for 139 // further signing purposes. 140 func PassphraseToKey(passphrase []byte) ([PrivateKeySize]byte, error) { 141 //PERFORMANCE/SECURITY: 4096 as a work factor may have to be adapted (runs per request) 142 key := pbkdf2.Key(passphrase, staticSalt, 4096, sha512.Size, sha512.New) 143 reader := bytes.NewBuffer(key) 144 _, priv, err := ed25519.GenerateKey(reader) 145 if err != nil { 146 return [PrivateKeySize]byte{}, err 147 } 148 return *priv, nil 149 } 150 151 // GetAdminSecretPubkey transforms the given passphrase into a private key 152 // and returns the accompying public key (e.g. for storage on the server) 153 func GetAdminSecretPubkey(passphrase []byte) ([PublicKeySize]byte, error) { 154 key, err := PassphraseToKey(passphrase) 155 if err != nil { 156 return [PublicKeySize]byte{}, err 157 } 158 return edhelpers.GetPublicKeyFromPrivate(key), nil 159 }