Fix bug in putC/delC for mysql storage backend

When using encryption, the expected value (once encrypted) would never be the same as the encrypted value in the storage layer. As a result, it would never be possible to putC or delC a KV item.

Now the mysql package first gets the key from the storage layer, and then checks it’s decrypted value, before attempting to overwirte the value in the storage layer.
This commit is contained in:
Tobie Morgan Hitchcock 2018-04-04 19:11:59 +01:00
parent 085ba565b2
commit 82ffd9ef31
4 changed files with 101 additions and 36 deletions

22
kvs/mysql/err.go Normal file
View file

@ -0,0 +1,22 @@
// Copyright © 2016 Abcum Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mysql
import (
"errors"
)
// ErrKvNotExpectedValue occurs when conditionally putting or deleting a key-value item.
var ErrTxNotExpectedValue = errors.New("KV val is not expected value")

View file

@ -73,13 +73,6 @@ const sqlDel = `
LIMIT 1
`
const sqlDelC = `
DELETE FROM kv
WHERE t<=? AND k=? AND v=?
ORDER BY t DESC
LIMIT 1
`
const sqlDelP = `
DELETE q1 FROM kv
JOIN (
@ -120,11 +113,3 @@ const sqlPutN = `
VALUES
(?, ?, ?)
`
const sqlPutC = `
UPDATE kv
SET v=?
WHERE t<=? AND k=? AND v=?
ORDER BY t DESC
LIMIT 1
`

View file

@ -36,12 +36,10 @@ type TX struct {
getP *sql.Stmt
getR *sql.Stmt
del *sql.Stmt
delC *sql.Stmt
delP *sql.Stmt
delR *sql.Stmt
put *sql.Stmt
putN *sql.Stmt
putC *sql.Stmt
}
}
@ -286,21 +284,41 @@ func (tx *TX) Del(ver int64, key []byte) (kvs.KV, error) {
func (tx *TX) DelC(ver int64, key []byte, exp []byte) (kvs.KV, error) {
var err error
var now kvs.KV
var res *sql.Rows
exp, err = enc(exp)
if err != nil {
return nil, err
}
tx.lock.Lock()
defer tx.lock.Unlock()
if tx.stmt.delC == nil {
tx.stmt.delC, _ = tx.pntr.Prepare(sqlDelC)
// Get the item at the key
if tx.stmt.get == nil {
tx.stmt.get, _ = tx.pntr.Prepare(sqlGet)
}
res, err = tx.stmt.delC.Query(ver, key, exp)
res, err = tx.stmt.get.Query(ver, key)
if err != nil {
return nil, err
}
now, err = one(res, err)
if err != nil {
return nil, err
}
// Check if the values match
if !alter(now.Val(), exp) {
return nil, ErrTxNotExpectedValue
}
// If they match then delete
if tx.stmt.del == nil {
tx.stmt.del, _ = tx.pntr.Prepare(sqlDel)
}
res, err = tx.stmt.del.Query(ver, key)
return one(res, err)
@ -376,6 +394,7 @@ func (tx *TX) Put(ver int64, key []byte, val []byte) (kvs.KV, error) {
func (tx *TX) PutC(ver int64, key []byte, val []byte, exp []byte) (kvs.KV, error) {
var err error
var now kvs.KV
var res *sql.Rows
val, err = enc(val)
@ -383,14 +402,6 @@ func (tx *TX) PutC(ver int64, key []byte, val []byte, exp []byte) (kvs.KV, error
return nil, err
}
exp, err = enc(exp)
if err != nil {
return nil, err
}
tx.lock.Lock()
defer tx.lock.Unlock()
switch exp {
case nil:
@ -405,11 +416,35 @@ func (tx *TX) PutC(ver int64, key []byte, val []byte, exp []byte) (kvs.KV, error
default:
if tx.stmt.putC == nil {
tx.stmt.putC, _ = tx.pntr.Prepare(sqlPutC)
// Get the item at the key
if tx.stmt.get == nil {
tx.stmt.get, _ = tx.pntr.Prepare(sqlGet)
}
res, err = tx.stmt.putC.Query(val, ver, key, exp)
res, err = tx.stmt.get.Query(ver, key)
if err != nil {
return nil, err
}
now, err = one(res, err)
if err != nil {
return nil, err
}
// Check if the values match
if !check(now.Val(), exp) {
return nil, ErrTxNotExpectedValue
}
// If they match then delete
if tx.stmt.del == nil {
tx.stmt.put, _ = tx.pntr.Prepare(sqlPut)
}
res, err = tx.stmt.put.Query(ver, key, val, val)
return one(res, err)

View file

@ -15,6 +15,7 @@
package mysql
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
@ -23,6 +24,28 @@ import (
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// Used to see if we can conditionally put a value. We can only put
// a value if the value is the same, or if both items are nil.
func check(a, b []byte) bool {
if a != nil && b != nil {
return bytes.Equal(a, b)
} else if a == nil && b == nil {
return true
}
return false
}
// Used to see if we can conditionally del a value. We can only del
// a value if the value is the same, and neither item is nil.
func alter(a, b []byte) bool {
if a != nil && b != nil {
return bytes.Equal(a, b)
} else if a == nil && b == nil {
return false
}
return false
}
func encrypt(key []byte, src []byte) (dst []byte, err error) {
if key == nil || len(key) == 0 || len(src) == 0 {