blob: f0425b36fbb76b321f0dd16ae9ab15ee8d24ed89 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""ChromeOS encryption/decryption key management"""
from __future__ import print_function
import os
import re
class KeyPair(object):
"""Container for a key's files.
Attributes:
name: name of keypair
keydir: location of key files
basename: common prefix for all key files
version: version of key
public: public key file complete path
private: private key file complete path
"""
def __init__(self, name, keydir, version=1, basename=None, pub_ext='.vbpubk',
priv_ext='.vbprivk'):
"""Initialize KeyPair.
Args:
name: name of key
keydir: directory containing key files
version: version of the key
basename: basename of key files, default to use name
pub_ext: file extension used for public key
priv_ext: file extension used for private key
"""
self.name = name
self.keydir = keydir
self.version = version
self.basename = name if basename is None else basename
self.public = os.path.join(self.keydir, self.basename + pub_ext)
self.private = os.path.join(self.keydir, self.basename + priv_ext)
def Exists(self, require_public=False, require_private=False):
"""Returns True if either the public or private key exist on disk."""
has_public = os.path.exists(self.public)
has_private = os.path.exists(self.private)
if require_public and not has_public:
return False
if require_private and not has_private:
return False
return has_public or has_private
def CreateKeyblock(self, name=None, basename=None):
"""Returns a new Keyblock instance which references this key.
Args:
name: keyblock name, defaults to keypair's striping out _data_key
basename: keyblock basename, defaults to keypair's striping out _data_key
"""
if name is None:
# Use keypair's name, striping out '_data_key' if exists
name = ''.join(self.name.split('_data_key'))
if basename is None:
basename = ''.join(self.basename.split('_data_key'))
return Keyblock(name, basename=basename, data_key=self)
class Keyblock(object):
"""Container for a keyblock.
A keyblock is a pre-computed file which contains a signed public key which can
be used as a link in the chain-of-trust. See:
http://www.chromium.org/chromium-os/chromiumos-design-docs/verified-boot-data-structures
Attributes:
name: name of keyblock
keydir: location of keyblock
filename: complete filename of keyblock
"""
def __init__(self, name, keydir=None, basename=None, data_key=None):
"""Initialize Keyblock.
Args:
name: name of keyblock
keydir: directory that contains keyblock file. Default to data_key.keydir
basename: basename of keyblock, to which '.keyblock' will be appended.
defaults to 'name'
data_key: Key contained inside the keyblock.
"""
self.name = name
if keydir is None:
if data_key is not None and data_key.keydir is not None:
self.keydir = data_key.keydir
else:
raise ValueError("No directory defined")
else:
self.keydir = keydir
self.data_key = data_key
if basename is None:
basename = name
self.filename = os.path.join(self.keydir, basename + '.keyblock')
def Exists(self):
"""Returns True if keyblock exists on disk."""
return os.path.exists(self.filename)
class Keyset(object):
"""Store signer keys and keyblocks (think keychain).
Attributes:
keys: dict of keypairs, indexed on key's name
keyblocks: dict of Keyblocks, indexed on keyblock's name
"""
def __init__(self):
self.keys = {}
self.keyblocks = {}
def Prune(self):
"""Check that all keys and keyblocks exists, else remove them."""
for k, key in self.keys.items():
if not key.Exists():
self.keys.pop(k)
for k, keyblock in self.keyblocks.items():
if not keyblock.Exists():
self.keyblocks.pop(k)
def AddKey(self, new_key, key_name=None):
"""Add the given key to keyset, using new_key.name if key_name is None."""
if key_name is None:
self.keys[new_key.name] = new_key
else:
self.keys[key_name] = new_key
def GetKey(self, key_name):
"""Returns key from key_name. Returns None if not found."""
return self.keys.get(key_name)
def KeyExists(self, key_name, require_public=False, require_private=False):
"""Returns if key is in keyset and exists."""
key = self.GetKey(key_name)
return key is not None and key.Exists(require_public=require_public,
require_private=require_private)
def AddKeyblock(self, keyblock, kb_name=None):
"""Add keyblock to keyset indexed by kb_name, use keyblock.name if None."""
self.keyblocks[kb_name or keyblock.name] = keyblock
def GetKeyblock(self, keyblock_name):
"""Returns keyblock from keyblock_name. Returns None if not found."""
return self.keyblocks.get(keyblock_name)
def KeyblockExists(self, keyblock_name):
"""Returns if the keyblock exists."""
keyblock = self.GetKeyblock(keyblock_name)
return keyblock is not None and keyblock.Exists()
def KeysetFromDir(key_dir):
"""Returns a populated keyset generated from given directory
Note: every public key and keyblock must have an accompanying private key
"""
# Match any private key file name
# Ex: firmware_data_key.loem4.vbprivk, kernel_subkey.vbprivk
re_keypair = re.compile(r'(?P<name>\w+(\.loem(?P<loem>\d+))?)\.vbprivk')
ks = Keyset()
for f_name in os.listdir(key_dir):
key_match = re_keypair.match(f_name)
if key_match:
key_name = key_match.group('name')
#TODO (chingcodes): add version from file
key = KeyPair(key_name, key_dir, version=1)
ks.AddKey(key)
#TODO (chingcodes): loem.ini logic here
kb = key.CreateKeyblock()
if kb.Exists():
ks.AddKeyblock(kb)
return ks