diff --git a/handlers.go b/handlers.go index 0da8ad5..e675ece 100644 --- a/handlers.go +++ b/handlers.go @@ -22,6 +22,20 @@ const ( // formParseMaxMemory value is based on the default value that is used in // Request.ParseMultipartForm. formParseMaxMemory = 32 << 20 // 32 MB + + // highOnlineEntropy is the desired entropy of an "unguessable" paste URL + // (in bits). It should be chosen such that it should be hard for an + // attacker to find *any* key that should not be found. + // It is desired that the probability to guess a good key is small. + // + // [ amount of pastes ] + // Pr[ good key ] = --------------------------- + // [ amount of possible keys ] + // + // So with a conservative [ amount of pastes ] = 2^32 (= 4 billion), and + // an [ amount of possible keys ] = 2^80 then the probability of a correct + // guess is 2^-48. + highOnlineEntropy = 80 ) type viewPaste uint @@ -369,7 +383,11 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL) (*db.Paste, error) { // Add a paste (of any kind) to the database with arbitrary content. func shorten(tx *bolt.Tx, ty db.PasteType, content []byte) (*db.Paste, error) { // Generate the paste key - pasteKey, err := db.GeneratePasteKey(tx) + var keyEntropy int + if ty == db.PasteTypeFileUpload || ty == db.PasteTypePaste { + keyEntropy = highOnlineEntropy + } + pasteKey, err := db.GeneratePasteKey(tx, keyEntropy) if err != nil { return nil, errors.Wrap(err, "generating paste key") } diff --git a/internal/db/paste.go b/internal/db/paste.go index 6f12fba..4fbad07 100644 --- a/internal/db/paste.go +++ b/internal/db/paste.go @@ -164,10 +164,13 @@ func (p *Paste) RedirectURL() *url.URL { return urlParse } -// GeneratePasteKey generates a key until it is not in the database, the -// running time of this function is in O(log N), where N is the amount of +// GeneratePasteKey generates a new paste key. It will ensure that the newly +// generated paste key does not already exist in the database. +// The running time of this function is in O(log N), where N is the amount of // keys stored in the url-shorten database. -func GeneratePasteKey(tx *bolt.Tx) (string, error) { +// In tx, a Bolt transaction is given. Use minimumEntropy to set the mimimum +// guessing entropy of the generated key. +func GeneratePasteKey(tx *bolt.Tx, minimumEntropy int) (string, error) { pastesBucket := tx.Bucket([]byte(BucketPastes)) if pastesBucket == nil { return "", errors.Errorf("bucket %v does not exist", BucketPastes) @@ -177,7 +180,7 @@ func GeneratePasteKey(tx *bolt.Tx) (string, error) { var key string for { var err error - key, err = generatePasteKeyInner(epoch) + key, err = generatePasteKeyInner(epoch, minimumEntropy) if err != nil { return "", errors.Wrap(err, "url-key generation failed") } @@ -203,8 +206,23 @@ func GeneratePasteKey(tx *bolt.Tx) (string, error) { return key, nil } -func generatePasteKeyInner(epoch int) (string, error) { - urlKey := make([]byte, 4+epoch) +// generatePasteKeyInner generates a new paste key, but leaves the +// uniqueness and is-reserved checks to the caller. That is, it only +// generates a random key in the correct (syntactical) format. +// Both epoch and entropy can be used to set the key length. Epoch is used +// to prevent collisions in retrying to generate new keys. Entropy (in bits) +// is used to ensure that a new key has at least some amount of guessing +// entropy. +func generatePasteKeyInner(epoch, entropy int) (string, error) { + minKeyLen := 4 + entropyEpoch := entropy + entropyEpoch -= minKeyLen * 6 // First 4 characters provide 24 bits. + entropyEpoch++ // One bit less because of '0' bit. + entropyEpoch = (entropyEpoch-1)/5 + 1 // 5 bits for every added epoch. + if epoch < entropyEpoch { + epoch = entropyEpoch + } + urlKey := make([]byte, minKeyLen+epoch) _, err := rand.Read(urlKey) if err != nil { return "", err