Use high-entropy URLs for file uploads

Fixes issue #59
This commit is contained in:
Daan Sprenkels 2020-07-27 14:53:19 +02:00
parent 1c09bb0a71
commit 6d3e8028cb
2 changed files with 43 additions and 7 deletions

View File

@ -22,6 +22,20 @@ const (
// formParseMaxMemory value is based on the default value that is used in // formParseMaxMemory value is based on the default value that is used in
// Request.ParseMultipartForm. // Request.ParseMultipartForm.
formParseMaxMemory = 32 << 20 // 32 MB 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 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. // 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) { func shorten(tx *bolt.Tx, ty db.PasteType, content []byte) (*db.Paste, error) {
// Generate the paste key // 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 { if err != nil {
return nil, errors.Wrap(err, "generating paste key") return nil, errors.Wrap(err, "generating paste key")
} }

View File

@ -164,10 +164,13 @@ func (p *Paste) RedirectURL() *url.URL {
return urlParse return urlParse
} }
// GeneratePasteKey generates a key until it is not in the database, the // GeneratePasteKey generates a new paste key. It will ensure that the newly
// running time of this function is in O(log N), where N is the amount of // 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. // 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)) pastesBucket := tx.Bucket([]byte(BucketPastes))
if pastesBucket == nil { if pastesBucket == nil {
return "", errors.Errorf("bucket %v does not exist", BucketPastes) return "", errors.Errorf("bucket %v does not exist", BucketPastes)
@ -177,7 +180,7 @@ func GeneratePasteKey(tx *bolt.Tx) (string, error) {
var key string var key string
for { for {
var err error var err error
key, err = generatePasteKeyInner(epoch) key, err = generatePasteKeyInner(epoch, minimumEntropy)
if err != nil { if err != nil {
return "", errors.Wrap(err, "url-key generation failed") return "", errors.Wrap(err, "url-key generation failed")
} }
@ -203,8 +206,23 @@ func GeneratePasteKey(tx *bolt.Tx) (string, error) {
return key, nil return key, nil
} }
func generatePasteKeyInner(epoch int) (string, error) { // generatePasteKeyInner generates a new paste key, but leaves the
urlKey := make([]byte, 4+epoch) // 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) _, err := rand.Read(urlKey)
if err != nil { if err != nil {
return "", err return "", err