我正在开发网络身份验证系统,用户将对随机令牌进行数字签名,并将使用存储在服务器上的 X509 证书进行检查。
因此,我必须在 PostgreSQL 数据库中存储几个 X509 证书(PEM 或 DER 格式)。听起来很简单,但我希望能够搜索具有主题、颁发者、notBefore、notAfter 和类似条件的证书。
我的想法是在数据库中有以下列:X509data、notAfter、notBefore、subject、issuer 等。然后我将使用 add_new_X509()、find_X509(search criteria) 等方法创建表示 X509 证书的对象(在 SQL alchemy 中)。所以每当我将使用 add_new_X509() 方法添加新证书,它将自动从证书中读取所有数据并填充其余列并将原始证书放入 X509data 列。
不幸的是,这个解决方案有两个缺点:
- 我将两次存储相同的信息(在 X509 证书本身和单独的列中以便于搜索)
- 每当我想读取 X509 证书时,我的应用程序必须交叉检查 notAfter、notBefore、主题、颁发者以及存储在原始证书中的信息(这是出于安全原因,以防有人试图修改此字段)。
所以..有人有更好的想法或建议吗?也许有人看到此解决方案可能出现的任何其他安全问题?
复制并不理想,但在这种情况下可能是最佳选择。设置表权限,以便表所有者不是您的应用程序运行的日常数据库用户,并且只有
GRANT
您的应用程序能够写入证书数据列,而不是具有到期等的“缓存”列。有一个SECURITY DEFINER
触发器函数拦截写入证书字段并作为特权用户更新索引缓存列,方法是使用 X.509 库在验证后从证书中提取字段。或者,您可以编写一个 PL/Python、PL/Perl,甚至是一个 C SQL 函数来调用 X.509 证书解析器库来提取字段并返回它们。所以你会说
extract_x509_field(cert, 'subject')
。或者甚至可能是一个行返回形式,例如SELECT subject, issuerName FROM (SELECT extract_x509_fields(cert) FROM the_table)
whereextract_x509_fields
返回一行所有相关证书数据。使用这种方法,您可以创建CREATE INDEX cert_issuer ON certificate_table( extract_x509_field(cert,'issuer') );
可用于匹配WHERE
表达式的功能索引。您根本不需要为提取的数据提供表列。不利的一面是,这可能会更慢,因为在创建索引、重新检查索引等期间,证书会被多次解析。无论哪种方式,您的应用程序都必须以 PostgreSQL 用户身份运行,该用户不是数据库所有者、超级用户,也不是相关表和索引的所有者。它应该是
GRANT
必要的最低权利,仅此而已。如果您有完全不同的任务(例如,只读与写入和更新),请考虑为它们使用不同的数据库用户,这样即使您的应用程序的“读取”部分被欺骗尝试写入字段/更新证书/etc,它没有权限。我在我的博客Storing X.509 Digital Certificates (And Other Messy Things)和一些较早的评论中解决了类似的问题。(在这里剪切和粘贴太长了。)如果您可以创建一个用户定义的函数来提取您需要缓存的字段,那么这里提出的许多要点会容易得多。
解决上面的另一点 -可以编写一个使用 SPI 的触发器来验证证书的颁发者是否存在于数据库中。您需要包括自签名根证书和其他人提供的“受信任”证书的例外情况。这一点,加上其他一些健全性检查(例如,发行者是否具有“基本”能力?是否有 DN 限制?)将为您提供更强大的存储库。
这样做是否明智?我一直在这方面来回走动。我目前的想法是,如果您发布和管理自己的证书,它会起作用,但如果您合并第三方证书,那将是一个巨大的麻烦。问题是那里有很多垃圾证书。如果你很严格,你会排除很多正在使用的证书,如果你很松懈,那么为什么还要为额外的逻辑烦恼呢?
我不太明白为什么要单独存储各种主题/颁发者字段,除非您要从数据库中查询信息;特别是因为您必须阅读证书以验证其详细信息(列表中的第二项)。
如果您将其存储为自动过期/失效证书,您可以运行一个单独的任务来执行此操作。
此外,由于 postgresql 允许您使用 python 作为过程语言;您可以编写一个触发器或视图来返回您的应用程序的“已解析”信息 - 如果您真的想从您的应用程序中卸载它。
此外,由于您要存储证书,因此我会执行以下操作:
确保客户端和服务器之间的连接是加密的(无论如何你都应该这样做)。请参阅文档中的第17.9节。
确保表/列已加密。请参阅
pycrypto
模块文档。添加到 Craig 的出色回复中,一旦您开始从 X509 库中提取信息,您就可以用它做一些其他有趣的事情。我不知道 SQLAlchemy 是否总是使用表别名,或者您是否可以强制它这样做,但如果可以,那么您可以通过直接调用 X509 证书上的解析例程来避免重复信息,您甚至可以索引输出正如他所提到的那样,您不必在选择时间调用它们(即函数在插入/更新时间运行)。
我要指出的一件事是,您可以创建表方法,如果您可以让您的 ORM始终使用表别名,则可以代替列。例如:
然后可以通过以下方式找到:
请注意,以下内容无效,因此我对 ORM 的评论:
原因是如果没有颁发者列,解析器会将第一条语句转换为:
请注意,为大量列建立索引会使大量计算时间从读取转移到写入(使插入/更新更慢,但读取操作更快)。
最后,如果不确切知道您在做什么,您的预期工作负载是什么等,很难提出完整的建议。复制的优点是它将数据与读取和写入的提取函数分开,允许更多优化成为可能。保持一切正常运行(假设您的 ORM 支持这一点)的优势在于它提供了数据完整性的额外保证。