mirror of
https://github.com/borgbackup/borg.git
synced 2024-12-26 01:37:20 +00:00
Verify cindex integrity on archive open
This commit is contained in:
parent
480562570f
commit
f5f8065dd3
3 changed files with 38 additions and 24 deletions
|
@ -2,6 +2,7 @@
|
||||||
import logging
|
import logging
|
||||||
import msgpack
|
import msgpack
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -26,34 +27,40 @@ def __init__(self, store, crypto, name=None):
|
||||||
|
|
||||||
def load(self, id):
|
def load(self, id):
|
||||||
self.id = id
|
self.id = id
|
||||||
archive = msgpack.unpackb(self.crypto.decrypt(self.store.get(NS_ARCHIVES, self.id)))
|
data, hash = self.crypto.decrypt(self.store.get(NS_ARCHIVES, self.id))
|
||||||
|
archive = msgpack.unpackb(data)
|
||||||
if archive['version'] != 1:
|
if archive['version'] != 1:
|
||||||
raise Exception('Archive version %r not supported' % archive['version'])
|
raise Exception('Archive version %r not supported' % archive['version'])
|
||||||
self.items = archive['items']
|
self.items = archive['items']
|
||||||
self.name = archive['name']
|
self.name = archive['name']
|
||||||
cindex = msgpack.unpackb(self.crypto.decrypt(self.store.get(NS_CINDEX, self.id)))
|
data, hash = self.crypto.decrypt(self.store.get(NS_CINDEX, self.id))
|
||||||
|
cindex = msgpack.unpackb(data)
|
||||||
assert cindex['version'] == 1
|
assert cindex['version'] == 1
|
||||||
|
if archive['cindex'] != hash:
|
||||||
|
raise Exception('decryption failed')
|
||||||
self.chunks = cindex['chunks']
|
self.chunks = cindex['chunks']
|
||||||
for i, chunk in enumerate(self.chunks):
|
for i, chunk in enumerate(self.chunks):
|
||||||
self.chunk_idx[i] = chunk[0]
|
self.chunk_idx[i] = chunk[0]
|
||||||
|
|
||||||
def save(self, name):
|
def save(self, name):
|
||||||
self.id = self.crypto.id_hash(name)
|
self.id = self.crypto.id_hash(name)
|
||||||
archive = {
|
|
||||||
'version': 1,
|
|
||||||
'name': name,
|
|
||||||
'cmdline': sys.argv,
|
|
||||||
'ts': datetime.utcnow().isoformat(),
|
|
||||||
'items': self.items,
|
|
||||||
}
|
|
||||||
data = self.crypto.encrypt_read(msgpack.packb(archive))
|
|
||||||
self.store.put(NS_ARCHIVES, self.id, data)
|
|
||||||
cindex = {
|
cindex = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'chunks': self.chunks,
|
'chunks': self.chunks,
|
||||||
}
|
}
|
||||||
data = self.crypto.encrypt_create(msgpack.packb(cindex))
|
data, cindex_hash = self.crypto.encrypt_create(msgpack.packb(cindex))
|
||||||
self.store.put(NS_CINDEX, self.id, data)
|
self.store.put(NS_CINDEX, self.id, data)
|
||||||
|
archive = {
|
||||||
|
'version': 1,
|
||||||
|
'name': name,
|
||||||
|
'cindex': cindex_hash,
|
||||||
|
'cmdline': sys.argv,
|
||||||
|
'hostname': socket.gethostname(),
|
||||||
|
'ts': datetime.utcnow().isoformat(),
|
||||||
|
'items': self.items,
|
||||||
|
}
|
||||||
|
data, hash = self.crypto.encrypt_read(msgpack.packb(archive))
|
||||||
|
self.store.put(NS_ARCHIVES, self.id, data)
|
||||||
self.store.commit()
|
self.store.commit()
|
||||||
|
|
||||||
def add_chunk(self, id, size):
|
def add_chunk(self, id, size):
|
||||||
|
@ -117,7 +124,10 @@ def extract(self, dest=None):
|
||||||
for chunk in item['chunks']:
|
for chunk in item['chunks']:
|
||||||
id = self.chunk_idx[chunk]
|
id = self.chunk_idx[chunk]
|
||||||
try:
|
try:
|
||||||
fd.write(self.crypto.decrypt(self.store.get(NS_CHUNKS, id)))
|
data, hash = self.crypto.decrypt(self.store.get(NS_CHUNKS, id))
|
||||||
|
if self.crypto.id_hash(data) != id:
|
||||||
|
raise IntegrityError('chunk id did not match')
|
||||||
|
fd.write(data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Exception('Invalid chunk checksum')
|
raise Exception('Invalid chunk checksum')
|
||||||
self.restore_stat(path, item)
|
self.restore_stat(path, item)
|
||||||
|
@ -145,7 +155,9 @@ def verify(self):
|
||||||
for chunk in item['chunks']:
|
for chunk in item['chunks']:
|
||||||
id = self.chunk_idx[chunk]
|
id = self.chunk_idx[chunk]
|
||||||
try:
|
try:
|
||||||
self.crypto.decrypt(self.store.get(NS_CHUNKS, id))
|
data, hash = self.crypto.decrypt(self.store.get(NS_CHUNKS, id))
|
||||||
|
if self.crypto.id_hash(data) != id:
|
||||||
|
raise IntegrityError('chunk id did not match')
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
logging.error('%s ... ERROR', item['path'])
|
logging.error('%s ... ERROR', item['path'])
|
||||||
break
|
break
|
||||||
|
|
|
@ -42,7 +42,8 @@ def init(self, crypto):
|
||||||
if self.store.tid == 0:
|
if self.store.tid == 0:
|
||||||
return
|
return
|
||||||
for id in list(self.store.list(NS_CINDEX)):
|
for id in list(self.store.list(NS_CINDEX)):
|
||||||
cindex = msgpack.unpackb(crypto.decrypt(self.store.get(NS_CINDEX, id)))
|
data, hash = crypto.decrypt(self.store.get(NS_CINDEX, id))
|
||||||
|
cindex = msgpack.unpackb(data)
|
||||||
for id, size in cindex['chunks']:
|
for id, size in cindex['chunks']:
|
||||||
try:
|
try:
|
||||||
count, size = self.chunkmap[id]
|
count, size = self.chunkmap[id]
|
||||||
|
@ -68,7 +69,7 @@ def save(self):
|
||||||
def add_chunk(self, id, data, crypto):
|
def add_chunk(self, id, data, crypto):
|
||||||
if self.seen_chunk(id):
|
if self.seen_chunk(id):
|
||||||
return self.chunk_incref(id)
|
return self.chunk_incref(id)
|
||||||
data = crypto.encrypt_read(data)
|
data, hash = crypto.encrypt_read(data)
|
||||||
csize = len(data)
|
csize = len(data)
|
||||||
self.store.put(NS_CHUNKS, id, data)
|
self.store.put(NS_CHUNKS, id, data)
|
||||||
self.chunkmap[id] = (1, csize)
|
self.chunkmap[id] = (1, csize)
|
||||||
|
|
|
@ -68,17 +68,17 @@ def id_hash(self, data):
|
||||||
|
|
||||||
def encrypt_read(self, data):
|
def encrypt_read(self, data):
|
||||||
data = zlib.compress(data)
|
data = zlib.compress(data)
|
||||||
hash = SHA256.new(data).digest()
|
hash = self.id_hash(data)
|
||||||
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
||||||
data = AES.new(self.read_key, AES.MODE_CTR, '', counter=counter).encrypt(data)
|
data = AES.new(self.read_key, AES.MODE_CTR, '', counter=counter).encrypt(data)
|
||||||
return ''.join((self.READ, self.read_encrypted, hash, data))
|
return ''.join((self.READ, self.read_encrypted, hash, data)), hash
|
||||||
|
|
||||||
def encrypt_create(self, data):
|
def encrypt_create(self, data):
|
||||||
data = zlib.compress(data)
|
data = zlib.compress(data)
|
||||||
hash = SHA256.new(data).digest()
|
hash = self.id_hash(data)
|
||||||
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
||||||
data = AES.new(self.create_key, AES.MODE_CTR, '', counter=counter).encrypt(data)
|
data = AES.new(self.create_key, AES.MODE_CTR, '', counter=counter).encrypt(data)
|
||||||
return ''.join((self.CREATE, self.create_encrypted, hash, data))
|
return ''.join((self.CREATE, self.create_encrypted, hash, data)), hash
|
||||||
|
|
||||||
def decrypt_key(self, data, rsa_key):
|
def decrypt_key(self, data, rsa_key):
|
||||||
try:
|
try:
|
||||||
|
@ -94,16 +94,17 @@ def decrypt(self, data):
|
||||||
hash = data[257:289]
|
hash = data[257:289]
|
||||||
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
||||||
data = AES.new(key, AES.MODE_CTR, counter=counter).decrypt(data[289:])
|
data = AES.new(key, AES.MODE_CTR, counter=counter).decrypt(data[289:])
|
||||||
if SHA256.new(data).digest() != hash:
|
if self.id_hash(data) != hash:
|
||||||
raise IntegrityError('decryption failed')
|
raise IntegrityError('decryption failed')
|
||||||
return zlib.decompress(data)
|
return zlib.decompress(data), hash
|
||||||
elif type == self.CREATE:
|
elif type == self.CREATE:
|
||||||
key = self.decrypt_key(data[1:257], self.keychain.rsa_create)
|
key = self.decrypt_key(data[1:257], self.keychain.rsa_create)
|
||||||
hash = data[257:289]
|
hash = data[257:289]
|
||||||
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
counter = Counter.new(128, initial_value=bytes_to_long(hash[:16]), allow_wraparound=True)
|
||||||
data = AES.new(key, AES.MODE_CTR, '', counter=counter).decrypt(data[289:])
|
data = AES.new(key, AES.MODE_CTR, '', counter=counter).decrypt(data[289:])
|
||||||
if SHA256.new(data).digest() != hash:
|
if self.id_hash(data) != hash:
|
||||||
raise IntegrityError('decryption failed')
|
raise IntegrityError('decryption failed')
|
||||||
return zlib.decompress(data)
|
return zlib.decompress(data), hash
|
||||||
else:
|
else:
|
||||||
raise Exception('Unknown pack type %d found' % ord(type))
|
raise Exception('Unknown pack type %d found' % ord(type))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue