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  }