xref: /openbmc/qemu/tests/qemu-iotests/295 (revision a2cd85f6acc3c3a6cfc1abd8ac732aa7cfd5e9ef)
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