aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2015-04-28 15:29:12 -0400
committerRob Austein <sra@hactrn.net>2015-04-28 15:29:12 -0400
commit0c8d1d765783bbc09cc1ca63ffdd233f0ce31613 (patch)
tree65114ff0b424e0eb6aa8862c12c305bf26282fcb /scripts
First public commit of PKCS #11 implementation.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/build-attributes403
-rw-r--r--scripts/convert-schema.sed66
-rwxr-xr-xscripts/format-attribute-comments85
-rwxr-xr-xscripts/test-hsmcheck180
4 files changed, 734 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()
diff --git a/scripts/convert-schema.sed b/scripts/convert-schema.sed
new file mode 100644
index 0000000..55aaadc
--- /dev/null
+++ b/scripts/convert-schema.sed
@@ -0,0 +1,66 @@
+# Generate schema.h from schema.sql.
+#
+# If this script gets any more complicated, it should probably be
+# recoded in Python and have done.
+#
+# 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.
+
+
+# Add header. Note that both newlines and leading spaces need to be
+# quoted with backslashes, be careful....
+1i\
+ /*\
+\ * Automatically generated from schema.sql, edit that file instead of this one.\
+\ */\
+\
+
+# Debugging hack: ordinarily we keep all the per-session stuff in the
+# "temp" database, but debugging is easier when we let it all go to
+# disk. Uncomment these lines to remove all the "TEMPORARY" and
+# "temp." qualifiers.
+#s/ TEMPORARY / /g
+#s/ temp[.]/ /g
+
+# Delete comment lines, trailing whitespace, and blank lines.
+/^[ ]*--/d
+s/[ ]*$//
+/^$/d
+
+# Quote backslashes and doublequotes, if any.
+s/\\/\\\\/g
+s/"/\\"/g
+
+# Quote each line of text. Literal transcription would be:
+#
+# s/^.*$/"&\\n"/
+#
+# but SQL doesn't need the line breaks, so we can use
+# whitespace to generate something a bit more readable.
+#
+s/^.*$/" &"/
diff --git a/scripts/format-attribute-comments b/scripts/format-attribute-comments
new file mode 100755
index 0000000..3c13bba
--- /dev/null
+++ b/scripts/format-attribute-comments
@@ -0,0 +1,85 @@
+#!/bin/sh -
+#
+# Script to extract tables from the PKCS #11 specification and format
+# them as YAML comment blocks.
+#
+# This isn't even half-assed, more like quarter-assed. If I thought
+# we'd be using it a lot I'd rewrite it in Python.
+#
+# 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.
+
+url=http://www.cryptsoft.com/pkcs11doc/download/pkcs11doc-v230.tgz
+
+tar=${url##*/}
+
+test -r $tar ||
+wget $url ||
+exit
+
+tar -tf $tar |
+
+awk '
+ /group__SEC__(9|11)__.*\.html/ {
+
+ n = split($0, a, "[/.]");
+ title = a[n-1];
+
+ n = split($0, a, /__/);
+ s1 = a[3];
+ s2 = (a[4] ~ /^[0-9]+$/) ? a[4] : 0;
+ s3 = (a[5] ~ /^[0-9]+$/) ? a[5] : 0;
+ idx = sprintf("%04d%04d%04d", s1, s2, s3);
+
+ print idx, $0, title;
+ }
+' |
+
+sort -n |
+
+while read idx fn title
+do
+
+ tar -xOf $tar $fn |
+
+ w3m -dump -O us-ascii -T text/html |
+
+ awk -v title=$title '
+ BEGIN {
+ print "";
+ print "###";
+ print "#", title;
+ print "###";
+ print "";
+ }
+ /^[|+]/ {
+ print "#", $0;
+ }
+ '
+
+done
diff --git a/scripts/test-hsmcheck b/scripts/test-hsmcheck
new file mode 100755
index 0000000..b7a5643
--- /dev/null
+++ b/scripts/test-hsmcheck
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+
+"""
+Run the OpenDNSSEC libhsm/check/hsmcheck tool with Cryptech PKCS #11,
+using DNSpython to verify the DNSSEC data produced by"hsmcheck -s".
+
+This script knows far too much about the output generated by hsmcheck,
+but what were you expecting from an ad hoc test tool that gets its
+input by screen scraping the output of another ad hoc test tool?
+"""
+
+# 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.
+
+import os
+import sys
+
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, FileType as ArgumentFileType
+from tempfile import NamedTemporaryFile
+from subprocess import check_call, check_output
+from xml.etree.ElementTree import ElementTree, Element, SubElement
+
+
+def write_config():
+ """
+ Write hsmcheck configuration file.
+ """
+
+ e = Element("Configuration")
+ r = SubElement(e, "RepositoryList")
+ r = SubElement(r, "Repository", name = "default")
+ SubElement(r, "Module").text = args.driver
+ SubElement(r, "TokenLabel").text = args.token_label
+ SubElement(r, "PIN").text = args.pin
+ ElementTree(e).write(args.write_config)
+ args.write_config.flush()
+
+
+def hsmcheck(flag):
+ """
+ Run hsmcheck program with appropriate options and verbosity.
+ """
+
+ assert flag in "rgsd"
+ cmd = (args.hsmcheck_binary, "-c", args.write_config.name, "-" + flag)
+ if args.verbose:
+ sys.stdout.write("Running: %s\n" % " ".join(cmd))
+ if flag == "s":
+ text = check_output(cmd)
+ sys.stdout.write(text)
+ if not args.no_dnssec:
+ check_dnssec(text)
+ else:
+ check_call(cmd)
+
+
+def check_dnssec(text):
+ """
+ Use DNSPython to attempt DNSSEC validation on "hsmcheck -s" output.
+
+ This requires the DNSPython toolkit, which in turn requires
+ PyCrypto; ECDSA support (not yet tested) requires a third package.
+ On Debian-family Linux, you can install these with:
+
+ sudo apt-get install python-dnspython python-crypto python-ecdsa
+
+ Equivalent packages exist for other platforms.
+ """
+
+ try:
+ from dns.exception import DNSException
+ import dns.dnssec
+ import dns.rrset
+ import Crypto.PublicKey.RSA
+ #import ecdsa.ecdsa
+ except ImportError:
+ sys.exit("Problem importing DNSPython or supporting crypto packages, are they installed?")
+
+ wired_ttl = "3600"
+ wired_rdclass = "IN"
+
+ rrs = {}
+
+ for line in text.splitlines():
+
+ try:
+ name, ttl, rdclass, rdtype, rdata = line.split(None, 4)
+ except ValueError:
+ continue
+
+ if ttl != wired_ttl or rdclass != wired_rdclass:
+ continue
+
+ try:
+ rrs[name, rdtype].append(rdata)
+ except KeyError:
+ rrs[name, rdtype] = [rdata]
+
+ # Done parsing. We expect to have seen an A RRset, an RRSIG of that
+ # A RRset, and the DNSKEY that we'll need to verify the RRSIG.
+
+ if len(rrs) != 3:
+ sys.exit("Expected two RRsets and an RRSIG, got %r" % rrs)
+
+ rrs = dict((rdtype, dns.rrset.from_text_list(name, int(wired_ttl), wired_rdclass, rdtype, rrs[name, rdtype]))
+ for name, rdtype in rrs)
+
+ try:
+ dns.dnssec.validate(rrs["A"], rrs["RRSIG"], { rrs["DNSKEY"].name : rrs["DNSKEY"] })
+ except DNSException, e:
+ sys.exit("DNSSEC verification failed: %s" % e)
+
+ sys.stdout.write("\nDNSSEC verification successful!\n\n")
+
+
+# Main program.
+
+try:
+ default_config = NamedTemporaryFile()
+ default_hsmcheck = os.getenv("HSMCHECK", "hsmcheck")
+ default_driver = os.getenv("PKCS11_DRIVER",
+ os.path.realpath(os.path.join(os.path.dirname(sys.argv[0]), "..", "libpkcs11.so")))
+
+ parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter)
+ one_of = parser.add_mutually_exclusive_group()
+ one_of.add_argument("-a", "--all", "--rgsd", const = "rgsd", dest = "test", action = "store_const", help = "run all tests")
+ one_of.add_argument("-r", "--random", const = "r", dest = "test", action = "store_const", help = "just test random numbers")
+ one_of.add_argument("-g", "--generate", const = "g", dest = "test", action = "store_const", help = "just test key generation")
+ one_of.add_argument("-s", "--sign", const = "s", dest = "test", action = "store_const", help = "just test DNSSEC-signature")
+ one_of.add_argument("-d", "--delete", const = "d", dest = "test", action = "store_const", help = "just delete key")
+ parser.add_argument("-b", "--hsmcheck-binary", default = default_hsmcheck, help = "location of hsmcheck program")
+ parser.add_argument("-p", "--pin", default = "12345", help = "HSM PIN to use for tests")
+ parser.add_argument("-t", "--token-label", default = "Cryptech Token", help = "PKCS #11 label of Cryptech token")
+ parser.add_argument("-n", "--no-dnssec", action = "store_true", help = "do not attempt DNSSEC validation")
+ parser.add_argument("-v", "--verbose", action = "store_true", help = "bark more")
+ parser.add_argument("-D", "--driver", default = default_driver, help = "location of PKCS #11 driver")
+ parser.add_argument("-w", "--write-config", default = default_config, help = "write generated configuration to this file",
+ type = ArgumentFileType("w"))
+ parser.add_argument("--debug", action = "store_true", help = "debug this script")
+ parser.set_defaults(test = "rgsd")
+ args = parser.parse_args()
+
+ try:
+ write_config()
+ for flag in args.test:
+ hsmcheck(flag)
+
+ except Exception as e:
+ if args.debug:
+ raise
+ sys.exit("Failed: %s" % e)
+
+finally:
+ default_config.close()
+