1c26fd69fSDavid Howells /* Instantiate a public key crypto key from an X.509 Certificate 2c26fd69fSDavid Howells * 3c26fd69fSDavid Howells * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. 4c26fd69fSDavid Howells * Written by David Howells (dhowells@redhat.com) 5c26fd69fSDavid Howells * 6c26fd69fSDavid Howells * This program is free software; you can redistribute it and/or 7c26fd69fSDavid Howells * modify it under the terms of the GNU General Public Licence 8c26fd69fSDavid Howells * as published by the Free Software Foundation; either version 9c26fd69fSDavid Howells * 2 of the Licence, or (at your option) any later version. 10c26fd69fSDavid Howells */ 11c26fd69fSDavid Howells 12c26fd69fSDavid Howells #define pr_fmt(fmt) "X.509: "fmt 13c26fd69fSDavid Howells #include <linux/module.h> 14c26fd69fSDavid Howells #include <linux/kernel.h> 15c26fd69fSDavid Howells #include <linux/slab.h> 16c26fd69fSDavid Howells #include <linux/err.h> 17c26fd69fSDavid Howells #include <linux/mpi.h> 18c26fd69fSDavid Howells #include <linux/asn1_decoder.h> 19c26fd69fSDavid Howells #include <keys/asymmetric-subtype.h> 20c26fd69fSDavid Howells #include <keys/asymmetric-parser.h> 213be4beafSMimi Zohar #include <keys/system_keyring.h> 22c26fd69fSDavid Howells #include <crypto/hash.h> 23c26fd69fSDavid Howells #include "asymmetric_keys.h" 24c26fd69fSDavid Howells #include "public_key.h" 25c26fd69fSDavid Howells #include "x509_parser.h" 26c26fd69fSDavid Howells 2732c4741cSDmitry Kasatkin static bool use_builtin_keys; 2846963b77SDavid Howells static struct asymmetric_key_id *ca_keyid; 29ffb70f61SDmitry Kasatkin 30ffb70f61SDmitry Kasatkin #ifndef MODULE 31f2b3dee4SMimi Zohar static struct { 32f2b3dee4SMimi Zohar struct asymmetric_key_id id; 33f2b3dee4SMimi Zohar unsigned char data[10]; 34f2b3dee4SMimi Zohar } cakey; 35f2b3dee4SMimi Zohar 36ffb70f61SDmitry Kasatkin static int __init ca_keys_setup(char *str) 37ffb70f61SDmitry Kasatkin { 38ffb70f61SDmitry Kasatkin if (!str) /* default system keyring */ 39ffb70f61SDmitry Kasatkin return 1; 40ffb70f61SDmitry Kasatkin 4146963b77SDavid Howells if (strncmp(str, "id:", 3) == 0) { 42f2b3dee4SMimi Zohar struct asymmetric_key_id *p = &cakey.id; 43f2b3dee4SMimi Zohar size_t hexlen = (strlen(str) - 3) / 2; 44f2b3dee4SMimi Zohar int ret; 45f2b3dee4SMimi Zohar 46f2b3dee4SMimi Zohar if (hexlen == 0 || hexlen > sizeof(cakey.data)) { 47f2b3dee4SMimi Zohar pr_err("Missing or invalid ca_keys id\n"); 48f2b3dee4SMimi Zohar return 1; 49f2b3dee4SMimi Zohar } 50f2b3dee4SMimi Zohar 51f2b3dee4SMimi Zohar ret = __asymmetric_key_hex_to_key_id(str + 3, p, hexlen); 52f2b3dee4SMimi Zohar if (ret < 0) 53f2b3dee4SMimi Zohar pr_err("Unparsable ca_keys id hex string\n"); 54f2b3dee4SMimi Zohar else 5546963b77SDavid Howells ca_keyid = p; /* owner key 'id:xxxxxx' */ 5646963b77SDavid Howells } else if (strcmp(str, "builtin") == 0) { 5732c4741cSDmitry Kasatkin use_builtin_keys = true; 5846963b77SDavid Howells } 59ffb70f61SDmitry Kasatkin 60ffb70f61SDmitry Kasatkin return 1; 61ffb70f61SDmitry Kasatkin } 62ffb70f61SDmitry Kasatkin __setup("ca_keys=", ca_keys_setup); 63ffb70f61SDmitry Kasatkin #endif 64ffb70f61SDmitry Kasatkin 655ce43ad2SDavid Howells /** 665ce43ad2SDavid Howells * x509_request_asymmetric_key - Request a key by X.509 certificate params. 675ce43ad2SDavid Howells * @keyring: The keys to search. 684573b64aSDavid Howells * @id: The issuer & serialNumber to look for or NULL. 694573b64aSDavid Howells * @skid: The subjectKeyIdentifier to look for or NULL. 70f1b731dbSDmitry Kasatkin * @partial: Use partial match if true, exact if false. 715ce43ad2SDavid Howells * 724573b64aSDavid Howells * Find a key in the given keyring by identifier. The preferred identifier is 734573b64aSDavid Howells * the issuer + serialNumber and the fallback identifier is the 744573b64aSDavid Howells * subjectKeyIdentifier. If both are given, the lookup is by the former, but 754573b64aSDavid Howells * the latter must also match. 763be4beafSMimi Zohar */ 775ce43ad2SDavid Howells struct key *x509_request_asymmetric_key(struct key *keyring, 784573b64aSDavid Howells const struct asymmetric_key_id *id, 794573b64aSDavid Howells const struct asymmetric_key_id *skid, 80f1b731dbSDmitry Kasatkin bool partial) 813be4beafSMimi Zohar { 824573b64aSDavid Howells struct key *key; 834573b64aSDavid Howells key_ref_t ref; 844573b64aSDavid Howells const char *lookup; 854573b64aSDavid Howells char *req, *p; 864573b64aSDavid Howells int len; 874573b64aSDavid Howells 884573b64aSDavid Howells if (id) { 894573b64aSDavid Howells lookup = id->data; 904573b64aSDavid Howells len = id->len; 914573b64aSDavid Howells } else { 924573b64aSDavid Howells lookup = skid->data; 934573b64aSDavid Howells len = skid->len; 944573b64aSDavid Howells } 953be4beafSMimi Zohar 9646963b77SDavid Howells /* Construct an identifier "id:<keyid>". */ 974573b64aSDavid Howells p = req = kmalloc(2 + 1 + len * 2 + 1, GFP_KERNEL); 984573b64aSDavid Howells if (!req) 993be4beafSMimi Zohar return ERR_PTR(-ENOMEM); 1003be4beafSMimi Zohar 101f1b731dbSDmitry Kasatkin if (partial) { 10246963b77SDavid Howells *p++ = 'i'; 10346963b77SDavid Howells *p++ = 'd'; 104f1b731dbSDmitry Kasatkin } else { 105f1b731dbSDmitry Kasatkin *p++ = 'e'; 106f1b731dbSDmitry Kasatkin *p++ = 'x'; 107f1b731dbSDmitry Kasatkin } 10846963b77SDavid Howells *p++ = ':'; 1094573b64aSDavid Howells p = bin2hex(p, lookup, len); 11046963b77SDavid Howells *p = 0; 1113be4beafSMimi Zohar 1124573b64aSDavid Howells pr_debug("Look up: \"%s\"\n", req); 1133be4beafSMimi Zohar 1144573b64aSDavid Howells ref = keyring_search(make_key_ref(keyring, 1), 1154573b64aSDavid Howells &key_type_asymmetric, req); 1164573b64aSDavid Howells if (IS_ERR(ref)) 1174573b64aSDavid Howells pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref)); 1184573b64aSDavid Howells kfree(req); 1193be4beafSMimi Zohar 1204573b64aSDavid Howells if (IS_ERR(ref)) { 1214573b64aSDavid Howells switch (PTR_ERR(ref)) { 1223be4beafSMimi Zohar /* Hide some search errors */ 1233be4beafSMimi Zohar case -EACCES: 1243be4beafSMimi Zohar case -ENOTDIR: 1253be4beafSMimi Zohar case -EAGAIN: 1263be4beafSMimi Zohar return ERR_PTR(-ENOKEY); 1273be4beafSMimi Zohar default: 1284573b64aSDavid Howells return ERR_CAST(ref); 1293be4beafSMimi Zohar } 1303be4beafSMimi Zohar } 1313be4beafSMimi Zohar 1324573b64aSDavid Howells key = key_ref_to_ptr(ref); 1334573b64aSDavid Howells if (id && skid) { 1344573b64aSDavid Howells const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); 1354573b64aSDavid Howells if (!kids->id[1]) { 1364573b64aSDavid Howells pr_debug("issuer+serial match, but expected SKID missing\n"); 1374573b64aSDavid Howells goto reject; 1384573b64aSDavid Howells } 1394573b64aSDavid Howells if (!asymmetric_key_id_same(skid, kids->id[1])) { 1404573b64aSDavid Howells pr_debug("issuer+serial match, but SKID does not\n"); 1414573b64aSDavid Howells goto reject; 1424573b64aSDavid Howells } 1434573b64aSDavid Howells } 1444573b64aSDavid Howells 1454573b64aSDavid Howells pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key)); 1464573b64aSDavid Howells return key; 1474573b64aSDavid Howells 1484573b64aSDavid Howells reject: 1494573b64aSDavid Howells key_put(key); 1504573b64aSDavid Howells return ERR_PTR(-EKEYREJECTED); 1513be4beafSMimi Zohar } 152cf5b50fdSDavid Howells EXPORT_SYMBOL_GPL(x509_request_asymmetric_key); 1533be4beafSMimi Zohar 154c26fd69fSDavid Howells /* 155b426beb6SDavid Howells * Set up the signature parameters in an X.509 certificate. This involves 156b426beb6SDavid Howells * digesting the signed data and extracting the signature. 157c26fd69fSDavid Howells */ 158b426beb6SDavid Howells int x509_get_sig_params(struct x509_certificate *cert) 159c26fd69fSDavid Howells { 160c26fd69fSDavid Howells struct crypto_shash *tfm; 161c26fd69fSDavid Howells struct shash_desc *desc; 162c26fd69fSDavid Howells size_t digest_size, desc_size; 163b426beb6SDavid Howells void *digest; 164c26fd69fSDavid Howells int ret; 165c26fd69fSDavid Howells 166c26fd69fSDavid Howells pr_devel("==>%s()\n", __func__); 167c26fd69fSDavid Howells 16841559420SDavid Howells if (cert->unsupported_crypto) 16941559420SDavid Howells return -ENOPKG; 170b426beb6SDavid Howells if (cert->sig.rsa.s) 171b426beb6SDavid Howells return 0; 172b426beb6SDavid Howells 173b426beb6SDavid Howells cert->sig.rsa.s = mpi_read_raw_data(cert->raw_sig, cert->raw_sig_size); 174b426beb6SDavid Howells if (!cert->sig.rsa.s) 175b426beb6SDavid Howells return -ENOMEM; 176b426beb6SDavid Howells cert->sig.nr_mpi = 1; 177b426beb6SDavid Howells 178c26fd69fSDavid Howells /* Allocate the hashing algorithm we're going to need and find out how 179c26fd69fSDavid Howells * big the hash operational data will be. 180c26fd69fSDavid Howells */ 1813fe78ca2SDmitry Kasatkin tfm = crypto_alloc_shash(hash_algo_name[cert->sig.pkey_hash_algo], 0, 0); 18241559420SDavid Howells if (IS_ERR(tfm)) { 18341559420SDavid Howells if (PTR_ERR(tfm) == -ENOENT) { 18441559420SDavid Howells cert->unsupported_crypto = true; 18541559420SDavid Howells return -ENOPKG; 18641559420SDavid Howells } 18741559420SDavid Howells return PTR_ERR(tfm); 18841559420SDavid Howells } 189c26fd69fSDavid Howells 190c26fd69fSDavid Howells desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); 191c26fd69fSDavid Howells digest_size = crypto_shash_digestsize(tfm); 192c26fd69fSDavid Howells 193b426beb6SDavid Howells /* We allocate the hash operational data storage on the end of the 194b426beb6SDavid Howells * digest storage space. 195c26fd69fSDavid Howells */ 196c26fd69fSDavid Howells ret = -ENOMEM; 197b426beb6SDavid Howells digest = kzalloc(digest_size + desc_size, GFP_KERNEL); 198b426beb6SDavid Howells if (!digest) 199b426beb6SDavid Howells goto error; 200c26fd69fSDavid Howells 201b426beb6SDavid Howells cert->sig.digest = digest; 202b426beb6SDavid Howells cert->sig.digest_size = digest_size; 203c26fd69fSDavid Howells 204b426beb6SDavid Howells desc = digest + digest_size; 205c26fd69fSDavid Howells desc->tfm = tfm; 206c26fd69fSDavid Howells desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; 207c26fd69fSDavid Howells 208c26fd69fSDavid Howells ret = crypto_shash_init(desc); 209c26fd69fSDavid Howells if (ret < 0) 210c26fd69fSDavid Howells goto error; 211b426beb6SDavid Howells might_sleep(); 212b426beb6SDavid Howells ret = crypto_shash_finup(desc, cert->tbs, cert->tbs_size, digest); 213c26fd69fSDavid Howells error: 214c26fd69fSDavid Howells crypto_free_shash(tfm); 215c26fd69fSDavid Howells pr_devel("<==%s() = %d\n", __func__, ret); 216c26fd69fSDavid Howells return ret; 217c26fd69fSDavid Howells } 218b426beb6SDavid Howells EXPORT_SYMBOL_GPL(x509_get_sig_params); 219b426beb6SDavid Howells 220b426beb6SDavid Howells /* 221b426beb6SDavid Howells * Check the signature on a certificate using the provided public key 222b426beb6SDavid Howells */ 223b426beb6SDavid Howells int x509_check_signature(const struct public_key *pub, 224b426beb6SDavid Howells struct x509_certificate *cert) 225b426beb6SDavid Howells { 226b426beb6SDavid Howells int ret; 227b426beb6SDavid Howells 228b426beb6SDavid Howells pr_devel("==>%s()\n", __func__); 229b426beb6SDavid Howells 230b426beb6SDavid Howells ret = x509_get_sig_params(cert); 231b426beb6SDavid Howells if (ret < 0) 232b426beb6SDavid Howells return ret; 233b426beb6SDavid Howells 234b426beb6SDavid Howells ret = public_key_verify_signature(pub, &cert->sig); 23541559420SDavid Howells if (ret == -ENOPKG) 23641559420SDavid Howells cert->unsupported_crypto = true; 237b426beb6SDavid Howells pr_debug("Cert Verification: %d\n", ret); 238b426beb6SDavid Howells return ret; 239b426beb6SDavid Howells } 240b426beb6SDavid Howells EXPORT_SYMBOL_GPL(x509_check_signature); 241c26fd69fSDavid Howells 242c26fd69fSDavid Howells /* 2433be4beafSMimi Zohar * Check the new certificate against the ones in the trust keyring. If one of 2443be4beafSMimi Zohar * those is the signing key and validates the new certificate, then mark the 2453be4beafSMimi Zohar * new certificate as being trusted. 2463be4beafSMimi Zohar * 2473be4beafSMimi Zohar * Return 0 if the new certificate was successfully validated, 1 if we couldn't 2483be4beafSMimi Zohar * find a matching parent certificate in the trusted list and an error if there 2493be4beafSMimi Zohar * is a matching certificate but the signature check fails. 2503be4beafSMimi Zohar */ 2513be4beafSMimi Zohar static int x509_validate_trust(struct x509_certificate *cert, 2523be4beafSMimi Zohar struct key *trust_keyring) 2533be4beafSMimi Zohar { 2543be4beafSMimi Zohar struct key *key; 2553be4beafSMimi Zohar int ret = 1; 2563be4beafSMimi Zohar 2573be4beafSMimi Zohar if (!trust_keyring) 2583be4beafSMimi Zohar return -EOPNOTSUPP; 2593be4beafSMimi Zohar 260b92e6570SDavid Howells if (ca_keyid && !asymmetric_key_id_partial(cert->akid_skid, ca_keyid)) 261ffb70f61SDmitry Kasatkin return -EPERM; 262ffb70f61SDmitry Kasatkin 2634573b64aSDavid Howells key = x509_request_asymmetric_key(trust_keyring, 2644573b64aSDavid Howells cert->akid_id, cert->akid_skid, 265f1b731dbSDmitry Kasatkin false); 2663be4beafSMimi Zohar if (!IS_ERR(key)) { 26732c4741cSDmitry Kasatkin if (!use_builtin_keys 26832c4741cSDmitry Kasatkin || test_bit(KEY_FLAG_BUILTIN, &key->flags)) 269*146aa8b1SDavid Howells ret = x509_check_signature(key->payload.data[asym_crypto], 270*146aa8b1SDavid Howells cert); 2713be4beafSMimi Zohar key_put(key); 2723be4beafSMimi Zohar } 2733be4beafSMimi Zohar return ret; 2743be4beafSMimi Zohar } 2753be4beafSMimi Zohar 2763be4beafSMimi Zohar /* 277c26fd69fSDavid Howells * Attempt to parse a data blob for a key as an X509 certificate. 278c26fd69fSDavid Howells */ 279c26fd69fSDavid Howells static int x509_key_preparse(struct key_preparsed_payload *prep) 280c26fd69fSDavid Howells { 28146963b77SDavid Howells struct asymmetric_key_ids *kids; 282c26fd69fSDavid Howells struct x509_certificate *cert; 28346963b77SDavid Howells const char *q; 284c26fd69fSDavid Howells size_t srlen, sulen; 28546963b77SDavid Howells char *desc = NULL, *p; 286c26fd69fSDavid Howells int ret; 287c26fd69fSDavid Howells 288c26fd69fSDavid Howells cert = x509_cert_parse(prep->data, prep->datalen); 289c26fd69fSDavid Howells if (IS_ERR(cert)) 290c26fd69fSDavid Howells return PTR_ERR(cert); 291c26fd69fSDavid Howells 292c26fd69fSDavid Howells pr_devel("Cert Issuer: %s\n", cert->issuer); 293c26fd69fSDavid Howells pr_devel("Cert Subject: %s\n", cert->subject); 2942ecdb23bSDavid Howells 2952ecdb23bSDavid Howells if (cert->pub->pkey_algo >= PKEY_ALGO__LAST || 2962ecdb23bSDavid Howells cert->sig.pkey_algo >= PKEY_ALGO__LAST || 2972ecdb23bSDavid Howells cert->sig.pkey_hash_algo >= PKEY_HASH__LAST || 2982ecdb23bSDavid Howells !pkey_algo[cert->pub->pkey_algo] || 2992ecdb23bSDavid Howells !pkey_algo[cert->sig.pkey_algo] || 3003fe78ca2SDmitry Kasatkin !hash_algo_name[cert->sig.pkey_hash_algo]) { 3012ecdb23bSDavid Howells ret = -ENOPKG; 3022ecdb23bSDavid Howells goto error_free_cert; 3032ecdb23bSDavid Howells } 3042ecdb23bSDavid Howells 30567f7d60bSDavid Howells pr_devel("Cert Key Algo: %s\n", pkey_algo_name[cert->pub->pkey_algo]); 306fd19a3d1SDavid Howells pr_devel("Cert Valid period: %lld-%lld\n", cert->valid_from, cert->valid_to); 307c7c8bb23SDmitry Kasatkin pr_devel("Cert Signature: %s + %s\n", 308c7c8bb23SDmitry Kasatkin pkey_algo_name[cert->sig.pkey_algo], 3093fe78ca2SDmitry Kasatkin hash_algo_name[cert->sig.pkey_hash_algo]); 310c26fd69fSDavid Howells 31167f7d60bSDavid Howells cert->pub->algo = pkey_algo[cert->pub->pkey_algo]; 312c26fd69fSDavid Howells cert->pub->id_type = PKEY_ID_X509; 313c26fd69fSDavid Howells 31417334cabSDavid Howells /* Check the signature on the key if it appears to be self-signed */ 3154573b64aSDavid Howells if ((!cert->akid_skid && !cert->akid_id) || 3164573b64aSDavid Howells asymmetric_key_id_same(cert->skid, cert->akid_skid) || 3174573b64aSDavid Howells asymmetric_key_id_same(cert->id, cert->akid_id)) { 3183be4beafSMimi Zohar ret = x509_check_signature(cert->pub, cert); /* self-signed */ 319c26fd69fSDavid Howells if (ret < 0) 320c26fd69fSDavid Howells goto error_free_cert; 3213be4beafSMimi Zohar } else if (!prep->trusted) { 3223be4beafSMimi Zohar ret = x509_validate_trust(cert, get_system_trusted_keyring()); 3233be4beafSMimi Zohar if (!ret) 3243be4beafSMimi Zohar prep->trusted = 1; 325c26fd69fSDavid Howells } 326c26fd69fSDavid Howells 327c26fd69fSDavid Howells /* Propose a description */ 328c26fd69fSDavid Howells sulen = strlen(cert->subject); 329dd2f6c44SDavid Howells if (cert->raw_skid) { 330dd2f6c44SDavid Howells srlen = cert->raw_skid_size; 331dd2f6c44SDavid Howells q = cert->raw_skid; 332dd2f6c44SDavid Howells } else { 33346963b77SDavid Howells srlen = cert->raw_serial_size; 33446963b77SDavid Howells q = cert->raw_serial; 335dd2f6c44SDavid Howells } 33646963b77SDavid Howells 337c26fd69fSDavid Howells ret = -ENOMEM; 33846963b77SDavid Howells desc = kmalloc(sulen + 2 + srlen * 2 + 1, GFP_KERNEL); 339c26fd69fSDavid Howells if (!desc) 340c26fd69fSDavid Howells goto error_free_cert; 34146963b77SDavid Howells p = memcpy(desc, cert->subject, sulen); 34246963b77SDavid Howells p += sulen; 34346963b77SDavid Howells *p++ = ':'; 34446963b77SDavid Howells *p++ = ' '; 34546963b77SDavid Howells p = bin2hex(p, q, srlen); 34646963b77SDavid Howells *p = 0; 34746963b77SDavid Howells 34846963b77SDavid Howells kids = kmalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL); 34946963b77SDavid Howells if (!kids) 35046963b77SDavid Howells goto error_free_desc; 35146963b77SDavid Howells kids->id[0] = cert->id; 35246963b77SDavid Howells kids->id[1] = cert->skid; 353c26fd69fSDavid Howells 354c26fd69fSDavid Howells /* We're pinning the module by being linked against it */ 355c26fd69fSDavid Howells __module_get(public_key_subtype.owner); 356*146aa8b1SDavid Howells prep->payload.data[asym_subtype] = &public_key_subtype; 357*146aa8b1SDavid Howells prep->payload.data[asym_key_ids] = kids; 358*146aa8b1SDavid Howells prep->payload.data[asym_crypto] = cert->pub; 359c26fd69fSDavid Howells prep->description = desc; 360c26fd69fSDavid Howells prep->quotalen = 100; 361c26fd69fSDavid Howells 362c26fd69fSDavid Howells /* We've finished with the certificate */ 363c26fd69fSDavid Howells cert->pub = NULL; 36446963b77SDavid Howells cert->id = NULL; 36546963b77SDavid Howells cert->skid = NULL; 366c26fd69fSDavid Howells desc = NULL; 367c26fd69fSDavid Howells ret = 0; 368c26fd69fSDavid Howells 36946963b77SDavid Howells error_free_desc: 37046963b77SDavid Howells kfree(desc); 371c26fd69fSDavid Howells error_free_cert: 372c26fd69fSDavid Howells x509_free_certificate(cert); 373c26fd69fSDavid Howells return ret; 374c26fd69fSDavid Howells } 375c26fd69fSDavid Howells 376c26fd69fSDavid Howells static struct asymmetric_key_parser x509_key_parser = { 377c26fd69fSDavid Howells .owner = THIS_MODULE, 378c26fd69fSDavid Howells .name = "x509", 379c26fd69fSDavid Howells .parse = x509_key_preparse, 380c26fd69fSDavid Howells }; 381c26fd69fSDavid Howells 382c26fd69fSDavid Howells /* 383c26fd69fSDavid Howells * Module stuff 384c26fd69fSDavid Howells */ 385c26fd69fSDavid Howells static int __init x509_key_init(void) 386c26fd69fSDavid Howells { 387c26fd69fSDavid Howells return register_asymmetric_key_parser(&x509_key_parser); 388c26fd69fSDavid Howells } 389c26fd69fSDavid Howells 390c26fd69fSDavid Howells static void __exit x509_key_exit(void) 391c26fd69fSDavid Howells { 392c26fd69fSDavid Howells unregister_asymmetric_key_parser(&x509_key_parser); 393c26fd69fSDavid Howells } 394c26fd69fSDavid Howells 395c26fd69fSDavid Howells module_init(x509_key_init); 396c26fd69fSDavid Howells module_exit(x509_key_exit); 397e19aaa7dSKonstantin Khlebnikov 398e19aaa7dSKonstantin Khlebnikov MODULE_DESCRIPTION("X.509 certificate parser"); 399e19aaa7dSKonstantin Khlebnikov MODULE_LICENSE("GPL"); 400