1*a2cd85f6SMaxim Levitsky#!/usr/bin/env python3 2*a2cd85f6SMaxim Levitsky# 3*a2cd85f6SMaxim Levitsky# Test case QMP's encrypted key management 4*a2cd85f6SMaxim Levitsky# 5*a2cd85f6SMaxim Levitsky# Copyright (C) 2019 Red Hat, Inc. 6*a2cd85f6SMaxim Levitsky# 7*a2cd85f6SMaxim Levitsky# This program is free software; you can redistribute it and/or modify 8*a2cd85f6SMaxim Levitsky# it under the terms of the GNU General Public License as published by 9*a2cd85f6SMaxim Levitsky# the Free Software Foundation; either version 2 of the License, or 10*a2cd85f6SMaxim Levitsky# (at your option) any later version. 11*a2cd85f6SMaxim Levitsky# 12*a2cd85f6SMaxim Levitsky# This program is distributed in the hope that it will be useful, 13*a2cd85f6SMaxim Levitsky# but WITHOUT ANY WARRANTY; without even the implied warranty of 14*a2cd85f6SMaxim Levitsky# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15*a2cd85f6SMaxim Levitsky# GNU General Public License for more details. 16*a2cd85f6SMaxim Levitsky# 17*a2cd85f6SMaxim Levitsky# You should have received a copy of the GNU General Public License 18*a2cd85f6SMaxim Levitsky# along with this program. If not, see <http://www.gnu.org/licenses/>. 19*a2cd85f6SMaxim Levitsky# 20*a2cd85f6SMaxim Levitsky 21*a2cd85f6SMaxim Levitskyimport iotests 22*a2cd85f6SMaxim Levitskyimport os 23*a2cd85f6SMaxim Levitskyimport time 24*a2cd85f6SMaxim Levitskyimport json 25*a2cd85f6SMaxim Levitsky 26*a2cd85f6SMaxim Levitskytest_img = os.path.join(iotests.test_dir, 'test.img') 27*a2cd85f6SMaxim Levitsky 28*a2cd85f6SMaxim Levitskyclass Secret: 29*a2cd85f6SMaxim Levitsky def __init__(self, index): 30*a2cd85f6SMaxim Levitsky self._id = "keysec" + str(index) 31*a2cd85f6SMaxim Levitsky # you are not supposed to see the password... 32*a2cd85f6SMaxim Levitsky self._secret = "hunter" + str(index) 33*a2cd85f6SMaxim Levitsky 34*a2cd85f6SMaxim Levitsky def id(self): 35*a2cd85f6SMaxim Levitsky return self._id 36*a2cd85f6SMaxim Levitsky 37*a2cd85f6SMaxim Levitsky def secret(self): 38*a2cd85f6SMaxim Levitsky return self._secret 39*a2cd85f6SMaxim Levitsky 40*a2cd85f6SMaxim Levitsky def to_cmdline_object(self): 41*a2cd85f6SMaxim Levitsky return [ "secret,id=" + self._id + ",data=" + self._secret] 42*a2cd85f6SMaxim Levitsky 43*a2cd85f6SMaxim Levitsky def to_qmp_object(self): 44*a2cd85f6SMaxim Levitsky return { "qom_type" : "secret", "id": self.id(), 45*a2cd85f6SMaxim Levitsky "props": { "data": self.secret() } } 46*a2cd85f6SMaxim Levitsky 47*a2cd85f6SMaxim Levitsky################################################################################ 48*a2cd85f6SMaxim Levitskyclass EncryptionSetupTestCase(iotests.QMPTestCase): 49*a2cd85f6SMaxim Levitsky 50*a2cd85f6SMaxim Levitsky # test case startup 51*a2cd85f6SMaxim Levitsky def setUp(self): 52*a2cd85f6SMaxim Levitsky # start the VM 53*a2cd85f6SMaxim Levitsky self.vm = iotests.VM() 54*a2cd85f6SMaxim Levitsky self.vm.launch() 55*a2cd85f6SMaxim Levitsky 56*a2cd85f6SMaxim Levitsky # create the secrets and load 'em into the VM 57*a2cd85f6SMaxim Levitsky self.secrets = [ Secret(i) for i in range(0, 6) ] 58*a2cd85f6SMaxim Levitsky for secret in self.secrets: 59*a2cd85f6SMaxim Levitsky result = self.vm.qmp("object-add", **secret.to_qmp_object()) 60*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 61*a2cd85f6SMaxim Levitsky 62*a2cd85f6SMaxim Levitsky if iotests.imgfmt == "qcow2": 63*a2cd85f6SMaxim Levitsky self.pfx = "encrypt." 64*a2cd85f6SMaxim Levitsky self.img_opts = [ '-o', "encrypt.format=luks" ] 65*a2cd85f6SMaxim Levitsky else: 66*a2cd85f6SMaxim Levitsky self.pfx = "" 67*a2cd85f6SMaxim Levitsky self.img_opts = [] 68*a2cd85f6SMaxim Levitsky 69*a2cd85f6SMaxim Levitsky # test case shutdown 70*a2cd85f6SMaxim Levitsky def tearDown(self): 71*a2cd85f6SMaxim Levitsky # stop the VM 72*a2cd85f6SMaxim Levitsky self.vm.shutdown() 73*a2cd85f6SMaxim Levitsky 74*a2cd85f6SMaxim Levitsky ########################################################################### 75*a2cd85f6SMaxim Levitsky # create the encrypted block device 76*a2cd85f6SMaxim Levitsky def createImg(self, file, secret): 77*a2cd85f6SMaxim Levitsky 78*a2cd85f6SMaxim Levitsky iotests.qemu_img( 79*a2cd85f6SMaxim Levitsky 'create', 80*a2cd85f6SMaxim Levitsky '--object', *secret.to_cmdline_object(), 81*a2cd85f6SMaxim Levitsky '-f', iotests.imgfmt, 82*a2cd85f6SMaxim Levitsky '-o', self.pfx + 'key-secret=' + secret.id(), 83*a2cd85f6SMaxim Levitsky '-o', self.pfx + 'iter-time=10', 84*a2cd85f6SMaxim Levitsky *self.img_opts, 85*a2cd85f6SMaxim Levitsky file, 86*a2cd85f6SMaxim Levitsky '1M') 87*a2cd85f6SMaxim Levitsky 88*a2cd85f6SMaxim Levitsky ########################################################################### 89*a2cd85f6SMaxim Levitsky # open an encrypted block device 90*a2cd85f6SMaxim Levitsky def openImageQmp(self, id, file, secret, read_only = False): 91*a2cd85f6SMaxim Levitsky 92*a2cd85f6SMaxim Levitsky encrypt_options = { 93*a2cd85f6SMaxim Levitsky 'key-secret' : secret.id() 94*a2cd85f6SMaxim Levitsky } 95*a2cd85f6SMaxim Levitsky 96*a2cd85f6SMaxim Levitsky if iotests.imgfmt == "qcow2": 97*a2cd85f6SMaxim Levitsky encrypt_options = { 98*a2cd85f6SMaxim Levitsky 'encrypt': { 99*a2cd85f6SMaxim Levitsky 'format':'luks', 100*a2cd85f6SMaxim Levitsky **encrypt_options 101*a2cd85f6SMaxim Levitsky } 102*a2cd85f6SMaxim Levitsky } 103*a2cd85f6SMaxim Levitsky 104*a2cd85f6SMaxim Levitsky result = self.vm.qmp('blockdev-add', ** 105*a2cd85f6SMaxim Levitsky { 106*a2cd85f6SMaxim Levitsky 'driver': iotests.imgfmt, 107*a2cd85f6SMaxim Levitsky 'node-name': id, 108*a2cd85f6SMaxim Levitsky 'read-only': read_only, 109*a2cd85f6SMaxim Levitsky 110*a2cd85f6SMaxim Levitsky **encrypt_options, 111*a2cd85f6SMaxim Levitsky 112*a2cd85f6SMaxim Levitsky 'file': { 113*a2cd85f6SMaxim Levitsky 'driver': 'file', 114*a2cd85f6SMaxim Levitsky 'filename': test_img, 115*a2cd85f6SMaxim Levitsky } 116*a2cd85f6SMaxim Levitsky } 117*a2cd85f6SMaxim Levitsky ) 118*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 119*a2cd85f6SMaxim Levitsky 120*a2cd85f6SMaxim Levitsky # close the encrypted block device 121*a2cd85f6SMaxim Levitsky def closeImageQmp(self, id): 122*a2cd85f6SMaxim Levitsky result = self.vm.qmp('blockdev-del', **{ 'node-name': id }) 123*a2cd85f6SMaxim Levitsky self.assert_qmp(result, 'return', {}) 124*a2cd85f6SMaxim Levitsky 125*a2cd85f6SMaxim Levitsky ########################################################################### 126*a2cd85f6SMaxim Levitsky # add a key to an encrypted block device 127*a2cd85f6SMaxim Levitsky def addKeyQmp(self, id, new_secret, secret = None, 128*a2cd85f6SMaxim Levitsky slot = None, force = False): 129*a2cd85f6SMaxim Levitsky 130*a2cd85f6SMaxim Levitsky crypt_options = { 131*a2cd85f6SMaxim Levitsky 'state' : 'active', 132*a2cd85f6SMaxim Levitsky 'new-secret' : new_secret.id(), 133*a2cd85f6SMaxim Levitsky 'iter-time' : 10 134*a2cd85f6SMaxim Levitsky } 135*a2cd85f6SMaxim Levitsky 136*a2cd85f6SMaxim Levitsky if slot != None: 137*a2cd85f6SMaxim Levitsky crypt_options['keyslot'] = slot 138*a2cd85f6SMaxim Levitsky 139*a2cd85f6SMaxim Levitsky 140*a2cd85f6SMaxim Levitsky if secret != None: 141*a2cd85f6SMaxim Levitsky crypt_options['secret'] = secret.id() 142*a2cd85f6SMaxim Levitsky 143*a2cd85f6SMaxim Levitsky if iotests.imgfmt == "qcow2": 144*a2cd85f6SMaxim Levitsky crypt_options['format'] = 'luks' 145*a2cd85f6SMaxim Levitsky crypt_options = { 146*a2cd85f6SMaxim Levitsky 'encrypt': crypt_options 147*a2cd85f6SMaxim Levitsky } 148*a2cd85f6SMaxim Levitsky 149*a2cd85f6SMaxim Levitsky args = { 150*a2cd85f6SMaxim Levitsky 'node-name': id, 151*a2cd85f6SMaxim Levitsky 'job-id' : 'job_add_key', 152*a2cd85f6SMaxim Levitsky 'options' : { 153*a2cd85f6SMaxim Levitsky 'driver' : iotests.imgfmt, 154*a2cd85f6SMaxim Levitsky **crypt_options 155*a2cd85f6SMaxim Levitsky }, 156*a2cd85f6SMaxim Levitsky } 157*a2cd85f6SMaxim Levitsky 158*a2cd85f6SMaxim Levitsky if force == True: 159*a2cd85f6SMaxim Levitsky args['force'] = True 160*a2cd85f6SMaxim Levitsky 161*a2cd85f6SMaxim Levitsky #TODO: check what jobs return 162*a2cd85f6SMaxim Levitsky result = self.vm.qmp('x-blockdev-amend', **args) 163*a2cd85f6SMaxim Levitsky assert result['return'] == {} 164*a2cd85f6SMaxim Levitsky self.vm.run_job('job_add_key') 165*a2cd85f6SMaxim Levitsky 166*a2cd85f6SMaxim Levitsky # erase a key from an encrypted block device 167*a2cd85f6SMaxim Levitsky def eraseKeyQmp(self, id, old_secret = None, slot = None, force = False): 168*a2cd85f6SMaxim Levitsky 169*a2cd85f6SMaxim Levitsky crypt_options = { 170*a2cd85f6SMaxim Levitsky 'state' : 'inactive', 171*a2cd85f6SMaxim Levitsky } 172*a2cd85f6SMaxim Levitsky 173*a2cd85f6SMaxim Levitsky if slot != None: 174*a2cd85f6SMaxim Levitsky crypt_options['keyslot'] = slot 175*a2cd85f6SMaxim Levitsky if old_secret != None: 176*a2cd85f6SMaxim Levitsky crypt_options['old-secret'] = old_secret.id() 177*a2cd85f6SMaxim Levitsky 178*a2cd85f6SMaxim Levitsky if iotests.imgfmt == "qcow2": 179*a2cd85f6SMaxim Levitsky crypt_options['format'] = 'luks' 180*a2cd85f6SMaxim Levitsky crypt_options = { 181*a2cd85f6SMaxim Levitsky 'encrypt': crypt_options 182*a2cd85f6SMaxim Levitsky } 183*a2cd85f6SMaxim Levitsky 184*a2cd85f6SMaxim Levitsky args = { 185*a2cd85f6SMaxim Levitsky 'node-name': id, 186*a2cd85f6SMaxim Levitsky 'job-id' : 'job_erase_key', 187*a2cd85f6SMaxim Levitsky 'options' : { 188*a2cd85f6SMaxim Levitsky 'driver' : iotests.imgfmt, 189*a2cd85f6SMaxim Levitsky **crypt_options 190*a2cd85f6SMaxim Levitsky }, 191*a2cd85f6SMaxim Levitsky } 192*a2cd85f6SMaxim Levitsky 193*a2cd85f6SMaxim Levitsky if force == True: 194*a2cd85f6SMaxim Levitsky args['force'] = True 195*a2cd85f6SMaxim Levitsky 196*a2cd85f6SMaxim Levitsky result = self.vm.qmp('x-blockdev-amend', **args) 197*a2cd85f6SMaxim Levitsky assert result['return'] == {} 198*a2cd85f6SMaxim Levitsky self.vm.run_job('job_erase_key') 199*a2cd85f6SMaxim Levitsky 200*a2cd85f6SMaxim Levitsky ########################################################################### 201*a2cd85f6SMaxim Levitsky # create image, and change its key 202*a2cd85f6SMaxim Levitsky def testChangeKey(self): 203*a2cd85f6SMaxim Levitsky 204*a2cd85f6SMaxim Levitsky # create the image with secret0 and open it 205*a2cd85f6SMaxim Levitsky self.createImg(test_img, self.secrets[0]); 206*a2cd85f6SMaxim Levitsky self.openImageQmp("testdev", test_img, self.secrets[0]) 207*a2cd85f6SMaxim Levitsky 208*a2cd85f6SMaxim Levitsky # add key to slot 1 209*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[1]) 210*a2cd85f6SMaxim Levitsky 211*a2cd85f6SMaxim Levitsky # add key to slot 5 212*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[2], slot=5) 213*a2cd85f6SMaxim Levitsky 214*a2cd85f6SMaxim Levitsky # erase key from slot 0 215*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 216*a2cd85f6SMaxim Levitsky 217*a2cd85f6SMaxim Levitsky #reopen the image with secret1 218*a2cd85f6SMaxim Levitsky self.closeImageQmp("testdev") 219*a2cd85f6SMaxim Levitsky self.openImageQmp("testdev", test_img, self.secrets[1]) 220*a2cd85f6SMaxim Levitsky 221*a2cd85f6SMaxim Levitsky # close and erase the image for good 222*a2cd85f6SMaxim Levitsky self.closeImageQmp("testdev") 223*a2cd85f6SMaxim Levitsky os.remove(test_img) 224*a2cd85f6SMaxim Levitsky 225*a2cd85f6SMaxim Levitsky # test that if we erase the old password, 226*a2cd85f6SMaxim Levitsky # we can still change the encryption keys using 'old-secret' 227*a2cd85f6SMaxim Levitsky def testOldPassword(self): 228*a2cd85f6SMaxim Levitsky 229*a2cd85f6SMaxim Levitsky # create the image with secret0 and open it 230*a2cd85f6SMaxim Levitsky self.createImg(test_img, self.secrets[0]); 231*a2cd85f6SMaxim Levitsky self.openImageQmp("testdev", test_img, self.secrets[0]) 232*a2cd85f6SMaxim Levitsky 233*a2cd85f6SMaxim Levitsky # add key to slot 1 234*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[1]) 235*a2cd85f6SMaxim Levitsky 236*a2cd85f6SMaxim Levitsky # erase key from slot 0 237*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 238*a2cd85f6SMaxim Levitsky 239*a2cd85f6SMaxim Levitsky # this will fail as the old password is no longer valid 240*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[2]) 241*a2cd85f6SMaxim Levitsky 242*a2cd85f6SMaxim Levitsky # this will work 243*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[2], secret = self.secrets[1]) 244*a2cd85f6SMaxim Levitsky 245*a2cd85f6SMaxim Levitsky # close and erase the image for good 246*a2cd85f6SMaxim Levitsky self.closeImageQmp("testdev") 247*a2cd85f6SMaxim Levitsky os.remove(test_img) 248*a2cd85f6SMaxim Levitsky 249*a2cd85f6SMaxim Levitsky def testUseForceLuke(self): 250*a2cd85f6SMaxim Levitsky 251*a2cd85f6SMaxim Levitsky self.createImg(test_img, self.secrets[0]); 252*a2cd85f6SMaxim Levitsky self.openImageQmp("testdev", test_img, self.secrets[0]) 253*a2cd85f6SMaxim Levitsky 254*a2cd85f6SMaxim Levitsky # Add bunch of secrets 255*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[1], slot=4) 256*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[4], slot=2) 257*a2cd85f6SMaxim Levitsky 258*a2cd85f6SMaxim Levitsky # overwrite an active secret 259*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[5], slot=2) 260*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[5], slot=2, force=True) 261*a2cd85f6SMaxim Levitsky 262*a2cd85f6SMaxim Levitsky self.addKeyQmp("testdev", new_secret = self.secrets[0]) 263*a2cd85f6SMaxim Levitsky 264*a2cd85f6SMaxim Levitsky # Now erase all the secrets 265*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", old_secret = self.secrets[5]) 266*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", slot=4) 267*a2cd85f6SMaxim Levitsky 268*a2cd85f6SMaxim Levitsky # erase last keyslot 269*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", old_secret = self.secrets[0]) 270*a2cd85f6SMaxim Levitsky self.eraseKeyQmp("testdev", old_secret = self.secrets[0], force=True) 271*a2cd85f6SMaxim Levitsky 272*a2cd85f6SMaxim Levitsky self.closeImageQmp("testdev") 273*a2cd85f6SMaxim Levitsky os.remove(test_img) 274*a2cd85f6SMaxim Levitsky 275*a2cd85f6SMaxim Levitsky 276*a2cd85f6SMaxim Levitskyif __name__ == '__main__': 277*a2cd85f6SMaxim Levitsky iotests.verify_working_luks() 278*a2cd85f6SMaxim Levitsky # Encrypted formats support 279*a2cd85f6SMaxim Levitsky iotests.activate_logging() 280*a2cd85f6SMaxim Levitsky iotests.main(supported_fmts = ['qcow2', 'luks']) 281