diff options
Diffstat (limited to 'scripts/build-attributes')
-rwxr-xr-x | scripts/build-attributes | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/scripts/build-attributes b/scripts/build-attributes new file mode 100755 index 0000000..891bdb6 --- /dev/null +++ b/scripts/build-attributes @@ -0,0 +1,403 @@ +#!/usr/bin/env python + +""" +Generate a C header file based on a YAML description of PKCS #11 +attributes. See comments in attributes.yaml for details. +""" + +# Author: Rob Austein +# Copyright (c) 2015, SUNET +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This requires a third-party YAML parser. On Debian-family Linux, +# you can install this with: +# +# sudo apt-get install python-yaml + +import os +import sys +import yaml +import argparse + + +def define_flags(flag_names): + """ + Flag definitions. Called later, here at front of program just to + make them easier to find. + """ + + flag_names.create("DEFAULT_VALUE", "Value field contains default") + flag_names.footnote( 1, "REQUIRED_BY_CREATEOBJECT") + flag_names.footnote( 2, "FORBIDDEN_BY_CREATEOBJECT") + flag_names.footnote( 3, "REQUIRED_BY_GENERATE") + flag_names.footnote( 4, "FORBIDDEN_BY_GENERATE") + flag_names.footnote( 5, "REQUIRED_BY_UNWRAP") + flag_names.footnote( 6, "FORBIDDEN_BY_UNWRAP") + flag_names.footnote( 7, "SENSITIVE") + flag_names.footnote( 8, "PERHAPS_MODIFIABLE") + flag_names.footnote( 9, "DEFAULT_IS_TOKEN_SPECIFIC") + flag_names.footnote(10, "ONLY_SO_USER_CAN_SET") + flag_names.footnote(11, "LATCHES_WHEN_TRUE") + flag_names.footnote(12, "LATCHES_WHEN_FALSE") + + +class PKCS11ParseError(Exception): + "Failure parsing PCKS #11 object definitions from YAML data." + + +def write_lines(*lines, **d): + """ + Utility to simplify writing formatted text to the output stream. + """ + + for line in lines: + args.output_file.write((line % d) + "\n") + + +class Flags(object): + """ + Descriptor flag database. + + Many of these are derived from PKCS #11 Table 15 footnotes + """ + + prefix = "P11_DESCRIPTOR_" # Prefix string for all descriptor flags + + def __init__(self): + self.names = [] + self.notes = {} + self.width = 0 + + def create(self, name, comment = None): + """ + Create a descriptor flag. + """ + + assert len(self.names) < 32 + name = self.prefix + name + self.names.append((name, comment)) + if len(name) > self.width: + self.width = len(name) + + def footnote(self, number, name): + """ + Create a descriptor flag for a PKCS #11 table 15 footnote. + """ + + assert number not in self.notes + self.create(name, "Section 10.2 table 15 footnote #%2d" % number) + self.notes[number] = self.prefix + name + + def write(self): + """ + Generate the flags, assigning bit positions as we go. + """ + + assert len(self.names) < 32 + self.width = (((self.width + 4) >> 2) << 2) - 1 + bit = 1 + for name, comment in self.names: + format = "#define %(name)s 0x%(bit)08x" + if comment is not None: + format += " /* %(comment)s */" + write_lines(format, bit = bit, comment = comment, name = "%-*s" % (self.width, name)) + bit <<= 1 + + +class AttributeNumbers(dict): + """ + Attribute names and numbers scraped (yuck) from pkcs11t.h. + """ + + def __init__(self, filename): + with open(filename, "r") as f: + for line in f: + word = line.split() + if len(word) <= 2 or word[0] != "#define" or not word[1].startswith("CKA_"): + continue + if word[2] in self: + continue + if word[2].startswith("(CKF_ARRAY_ATTRIBUTE|"): + word[2] = word[2].translate(None, "()").split("|")[1] + self[word[1]] = int(word[2], 16) + + +class Attribute(object): + """ + Definition of one attribute. + """ + + def __init__(self, name, type = None, footnotes = None, default = None, value = None, unimplemented = False): + assert value is None or default is None + self.name = name + self.type = type + self.footnotes = footnotes + self.default = self.convert_integers(default) + self.value = self.convert_integers(value) + self.unimplemented = unimplemented + + @staticmethod + def convert_integers(val): + """ + Convert a non-negative integer initialization value into a byte array. + """ + + if not isinstance(val, (int, long)): + return val + if val < 0: + raise ValueError("Negative integers not legal here: %s" % val) + bytes = [] + while val > 0: + bytes.insert(0, val & 0xFF) + val >>= 8 + return bytes or [0] + + def inherit(self, other): + """ + Merge values from paraent attribute definition, if any. + """ + + for k in ("type", "footnotes", "default", "value"): + if getattr(self, k) is None: + setattr(self, k, getattr(other, k)) + self.unimplemented = self.unimplemented or other.unimplemented + + def format_flags(self): + """ + Generate the descriptor flags field. + """ + + flags = [] + if self.footnotes: + flags.extend(flag_names.notes[f] for f in self.footnotes) + if self.value is None and self.default is not None: + flags.append("P11_DESCRIPTOR_DEFAULT_VALUE") + flags = " | ".join(flags) + return flags or "0" + + def format_size(self): + """ + Generate the descriptor size field. + """ + + if isinstance(self.type, str) and self.type.startswith("CK_"): + return "sizeof(%s)" % self.type + elif self.type in ("rfc2279string", "biginteger", "bytearray"): + return "0" + else: + raise PKCS11ParseError("Unknown meta-type %r" % self.type) + + def format_length(self): + """ + Generate the descriptor length field. + """ + + value = self.value or self.default + if isinstance(value, list): + return "sizeof(const_0x%s)" % "".join("%02x" % v for v in value) + elif value and isinstance(self.type, str) and self.type.startswith("CK_"): + return "sizeof(%s)" % self.type + else: + return "0" + + def format_value(self): + """ + Generate the descriptor value field. + """ + + value = self.value or self.default + if not value: + return "NULL_PTR" + elif isinstance(value, list): + return "const_0x" + "".join("%02x" % v for v in value) + else: + return "&const_" + value + + def format_constant(self, constants): + """ + Generate constant initializer values. These are merged so that we + only end up declaring one copy of each initializer value no matter + how many attributes use it. + """ + + value = self.value or self.default + if not self.unimplemented and value: + if isinstance(value, list): + constants.add("static const CK_BYTE const_%s[] = { %s };" % ( + "0x" + "".join("%02x" % v for v in value), + ", ".join("0x%02x" % v for v in value))) + else: + constants.add("static const %s const_%s = %s;" % (self.type, value, value)) + + def generate(self): + """ + Generate the descriptor line for this attribute. + """ + + if not self.unimplemented: + args.output_file.write(" { %s, %s, %s, %s, %s },\n" % ( + self.name, self.format_size(), self.format_length(), self.format_value(), self.format_flags())) + + +class Class(object): + """ + A PKCS #11 class. + """ + + def __init__(self, db, name, superclass = None, concrete = False, **attrs): + assert all(a.startswith("CKA_") for a in attrs), "Non-attribute: %r" % [a for a in attrs if not a.startswith("CKA_")] + self.attributes = dict((k, Attribute(k, **v)) for k, v in attrs.iteritems()) + self.db = db + self.name = name + self.superclass = superclass + self.concrete = concrete + + def inherit(self, other): + """ + Inherit attributes from parent type. + """ + + for k, v in other.attributes.iteritems(): + if k not in self.attributes: + self.attributes[k] = v + else: + self.attributes[k].inherit(v) + + def collect_constants(self, constants): + """ + Collect initialization constants for all attributes. + """ + + if self.concrete: + for a in self.attributes.itervalues(): + a.format_constant(constants) + + def generate(self): + """ + Generate a descriptor for this type. + """ + + if self.concrete: + + write_lines("", + "static const p11_attribute_descriptor_t p11_attribute_descriptor_%(name)s[] = {", + name = self.name) + + for a in sorted(self.attributes, key = lambda x: attribute_numbers[x]): + self.attributes[a].generate() + + write_lines("};", + "", + "static const p11_descriptor_t p11_descriptor_%(name)s = {", + " p11_attribute_descriptor_%(name)s,", + " sizeof(p11_attribute_descriptor_%(name)s)/sizeof(p11_attribute_descriptor_t)", + "};", + name = self.name) + + def keyclassmap(self): + """ + Generate a keyclass map entry if this is a concrete key type. + """ + + if self.concrete and all(k in self.attributes and self.attributes[k].value for k in ("CKA_CLASS", "CKA_KEY_TYPE")): + write_lines(" { %s, %s, &p11_descriptor_%s }," % ( + self.attributes["CKA_CLASS"].value, self.attributes["CKA_KEY_TYPE"].value, self.name)) + + +class DB(object): + """ + Object type database parsed from YAML + """ + + def __init__(self, y): + self.ordered = [Class(self, **y) for y in y] + self.named = dict((c.name, c) for c in self.ordered) + for c in self.ordered: + if c.superclass is not None: + c.inherit(self.named[c.superclass]) + + def generate(self): + """ + Generate output for everything in the database. + """ + + constants = set() + for c in self.ordered: + c.collect_constants(constants) + for constant in sorted(constants): + write_lines(constant) + for c in self.ordered: + c.generate() + write_lines("", + "static const p11_descriptor_keyclass_map_t p11_descriptor_keyclass_map[] = {") + for c in self.ordered: + c.keyclassmap() + write_lines("};") + +# Main program + +parser = argparse.ArgumentParser(description = __doc__, formatter_class = argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument("--pkcs11t-file", help = "Alternate location for pkcs11t.h", default = "pkcs11t.h") +parser.add_argument("yaml_file", help = "Input YAML file", nargs = "?", type = argparse.FileType("r"), default = sys.stdin) +parser.add_argument("output_file", help = "Output .h file", nargs = "?", type = argparse.FileType("w"), default = sys.stdout) +args = parser.parse_args() + +attribute_numbers = AttributeNumbers(args.pkcs11t_file) + +db = DB(yaml.load(args.yaml_file)) + +args.output_file.write('''\ +/* + * This file was generated automatically from %(input)s by %(script)s. Do not edit this file directly. + */ + +typedef struct { + CK_ATTRIBUTE_TYPE type; + CK_ULONG size; /* Size in bytes if this is a fixed-length attribute */ + CK_ULONG length; /* Length in bytes of the object to which value points */ + const void *value; /* Default or constant depending on P11_DESCRIPTOR_DEFAULT_VALUE */ + unsigned long flags; /* (NULL value with P11_DESCRIPTOR_DEFAULT_VALUE means zero length default */ +} p11_attribute_descriptor_t; + +typedef struct { + const p11_attribute_descriptor_t *attributes; + CK_ULONG n_attributes; +} p11_descriptor_t; + +typedef struct { + CK_OBJECT_CLASS object_class; + CK_KEY_TYPE key_type; + const p11_descriptor_t *descriptor; +} p11_descriptor_keyclass_map_t; + +''' % dict(script = os.path.basename(sys.argv[0]), input = args.yaml_file.name)) + +flag_names = Flags() +define_flags(flag_names) +flag_names.write() +write_lines("") +db.generate() |