-- SQLite3 schema for Cryptech PKCS #11 implementation.
--
-- 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.
-- Notes:
--
-- The CHECK constraints in the attribute tables are checking
-- CKA_TOKEN, to make sure we don't accidently file token objects in
-- the session table or vice versa.
--
-- temp.object.token_object_id is a foreign-key reference to
-- main.token_object.id, but we can't use a real foreign key reference
-- because they're in different databases. If we're careful about how
-- we do our joins, this is harmless, but may lead to some clutter if
-- a long running session has handles on token objects which some
-- other process deletes from the database. If this happens and we
-- care for some reason, we can clean up such clutter with something
-- like:
--
-- WITH
-- known AS (SELECT token_object_id FROM token_object)
-- DELETE FROM object
-- WHERE token_object_id IS NOT NULL
-- AND token_object_id NOT IN known;
PRAGMA foreign_keys = ON;
-- Values we have to store somewhere and for which we have no better
-- place. This is a table with exactly one row (enforced by the CHECK
-- clause on the primary index). All columns must either allow NULL
-- or provide default values.
CREATE TABLE IF NOT EXISTS global (
global_id INTEGER PRIMARY KEY NOT NULL DEFAULT 1 CHECK (global_id = 1),
-- Key-encryption-key (KEK)
--
-- The KEK **really** should be somewhere else, like in RAM
-- protected by tamper detection circuitry, but we don't have
-- that yet. Not obvious that a separate file would be more
-- secure, so keep it here until we do have a better place.
kek BLOB CHECK (kek IS NULL OR (typeof(kek) = "blob" AND length(kek) IN (16, 32))),
-- PBKDF2-based PIN storage and check values.
--
-- "so_pin" and "user_pin" are PBKDF2 output, so only
-- moderately sensitive.
--
-- Not obvious that PKCS #11 ever really allows "so_pin" to be
-- unset, so it may want a NOT NULL constraint, but in that
-- case we'll need to provide a default value, which doesn't
-- seem like much of an improvement. "so_pin" probably
-- requires out-of-band initialization. "user-pin" is allowed
-- to be unset, there's an error code specifically for that
-- situation.
--
-- Numeric minima for PBKDF2 iterations, length of PIN, and
-- length of PBKDF2 salt are somewhat arbitrary, and will
-- probably change over time (which is why they are minima).
-- Initial testing was with 100000, which takes about 8 seconds
-- on a Novena with the current SHA256 and PBKDF2
-- implementation, which seems a bit slow, so backed that down
-- a bit. Feel free to suggest better minima.
pbkdf2_iterations INTEGER NOT NULL DEFAULT 20000,
so_pin BLOB,
user_pin BLOB,
so_pin_salt, BLOB,
user_pin_salt BLOB,
CHECK ((pbkdf2_iterations >= 10000) AND
(so_pin IS NULL OR (typeof(so_pin) = "blob" AND length(so_pin) >= 32)) AND
(user_pin IS NULL OR (typeof(user_pin) = "blob" AND length(user_pin) >= 32)) AND
(so_pin_salt IS NULL OR (typeof(so_pin_salt) = "blob" AND length(so_pin_salt) >= 16)) AND
(user_pin_salt IS NULL OR (typeof(user_pin_salt) = "blob" AND length(user_pin_salt) >= 16)))
);
INSERT OR IGNORE INTO global DEFAULT VALUES;
CREATE TEMPORARY TABLE IF NOT EXISTS session (
session_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
session_handle INTEGER NOT NULL UNIQUE
CHECK (session_handle > 0 AND session_handle <= 0xFFFFFFFF)
);
CREATE TEMPORARY TABLE IF NOT EXISTS object (
object_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
object_handle INTEGER NOT NULL UNIQUE
CHECK (object_handle > 0 AND object_handle <= 0xFFFFFFFF),
session_id INTEGER REFERENCES session
ON DELETE CASCADE ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
token_object_id INTEGER,
session_object_id INTEGER REFERENCES session_object
ON DELETE CASCADE ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
CHECK (token_object_id IS NULL OR (session_id IS NULL AND session_object_id IS NULL)),
UNIQUE (token_object_id),
UNIQUE (session_id, session_object_id)
);
CREATE TEMPORARY TABLE IF NOT EXISTS session_object (
session_object_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
private_key BLOB UNIQUE,
object_id INTEGER NOT NULL UNIQUE
REFERENCES object
ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TEMPORARY TABLE IF NOT EXISTS session_attribute (
type INTEGER NOT NULL,
session_object_id INTEGER NOT NULL REFERENCES session_object
ON DELETE CASCADE ON UPDATE CASCADE,
value BLOB NOT NULL,
UNIQUE (type, session_object_id),
CHECK (type <> 1 OR value = X'00')
);
CREATE TABLE IF NOT EXISTS token_object (
token_object_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
private_key BLOB UNIQUE
);
CREATE TABLE IF NOT EXISTS token_attribute (
type INTEGER NOT NULL,
token_object_id INTEGER NOT NULL REFERENCES token_object
ON DELETE CASCADE ON UPDATE CASCADE,
value BLOB NOT NULL,
UNIQUE (type, token_object_id),
CHECK (type <> 1 OR value <> X'00')
);
-- http://sqlite.org/foreignkeys.html says we might want these.
CREATE INDEX IF NOT EXISTS temp.object__session ON object(session_id);
CREATE INDEX IF NOT EXISTS temp.object__session_object ON object(session_object_id);
CREATE INDEX IF NOT EXISTS temp.session_object__object ON session_object(object_id);
CREATE INDEX IF NOT EXISTS temp.session_attribute__session_object ON session_attribute(session_object_id);
CREATE INDEX IF NOT EXISTS token_attribute__token_object ON token_attribute(token_object_id);
-- Local variables:
-- indent-tabs-mode: nil
-- End: