From d8a1ae82d96b740dbd04c7fec9e1ac6f5d64cc1d Mon Sep 17 00:00:00 2001 From: Kraemii Date: Mon, 19 Aug 2024 07:46:57 +0200 Subject: [PATCH] Add: NASL bultin functions for certificate handling Added functions: cert_open, cert_query, cert_close --- rust/Cargo.lock | 238 ++++++++++++++--- rust/Cargo.toml | 42 ++- rust/src/nasl/builtin/cert/mod.rs | 415 ++++++++++++++++++++++++++++++ rust/src/nasl/builtin/mod.rs | 4 +- rust/typos.toml | 8 +- 5 files changed, 662 insertions(+), 45 deletions(-) create mode 100644 rust/src/nasl/builtin/cert/mod.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b7b300353..ff7bf1770 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ascii-canvas" @@ -146,6 +146,45 @@ dependencies = [ "term", ] +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "async-trait" version = "0.1.82" @@ -183,9 +222,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5055edc4a9a1b2a917a818258cdfb86a535947feebd9981adc99667a062c6f85" +checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" dependencies = [ "bindgen", "cc", @@ -229,6 +268,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bindgen" version = "0.69.4" @@ -336,9 +385,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "capnp" @@ -384,9 +433,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -721,6 +770,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "dbl" version = "0.3.2" @@ -741,6 +796,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1352,7 +1421,7 @@ dependencies = [ "hyper 1.4.1", "hyper-util", "log", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -1382,9 +1451,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2009,12 +2078,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2043,6 +2131,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -2187,6 +2284,16 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2542,9 +2649,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -2681,11 +2788,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -2708,15 +2824,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -2771,9 +2887,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "aws-lc-rs", "ring", @@ -2872,7 +2988,7 @@ dependencies = [ "redis", "regex", "ripemd", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pemfile 1.0.4", "rustls-pemfile 2.1.3", "sequoia-ipc", @@ -2894,6 +3010,8 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "x509-certificate", + "x509-parser", ] [[package]] @@ -3137,6 +3255,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -3375,6 +3502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -3487,7 +3615,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -3529,9 +3657,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "serde", @@ -3669,24 +3797,24 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -4160,6 +4288,42 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x509-certificate" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der", + "hex", + "pem", + "ring", + "signature", + "spki", + "thiserror", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.12" @@ -4237,6 +4401,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] [[package]] name = "zerovec" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2aa8b410b..406102b6b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,14 +6,14 @@ license = "GPL-2.0-or-later" [dependencies] aes = "0.8.2" -aes-gcm = { version = "0.10.1"} +aes-gcm = { version = "0.10.1" } anyhow = "1.0.75" async-trait = "0.1.68" base64 = "0.21.2" -cbc = { version = "0.1.2", features = ["alloc"]} +cbc = { version = "0.1.2", features = ["alloc"] } ccm = "0.5.0" chacha20 = "0.9.1" -chrono = { version = "0.4.23", default-features = false, features = ["clock"]} +chrono = { version = "0.4.23", default-features = false, features = ["clock"] } clap = { version = "4.3.0", features = ["derive", "env"] } cmac = "0.7.2" configparser = "3" @@ -56,8 +56,10 @@ rustls = "0.23.5" rustls-pemfile = "2.1.2" rustls-pemfile-old = { version = "1.0.2", package = "rustls-pemfile" } sequoia-ipc = "0.30.1" -sequoia-openpgp = { version ="1.16.1", default-features = false, features = ["crypto-openssl"] } -serde = { version = "1.0", features = ["derive"]} +sequoia-openpgp = { version = "1.16.1", default-features = false, features = [ + "crypto-openssl", +] } +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.96" sha1 = "0.10.5" sha2 = "0.10.7" @@ -72,26 +74,28 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } urlencoding = "2.1.2" uuid = { version = "1", features = ["v4", "fast-rng", "serde"] } walkdir = "2" +x509-certificate = "0.23.1" +x509-parser = "0.16.0" rayon = { version = "1.8.0", optional = true } pcap = { version = "1.0.0", optional = true } pnet_base = { version = "0.33.0", optional = true } pnet = { version = "0.33.0", optional = true } -socket2 = {version = "0.5.2", features = ["all"], optional = true} +socket2 = { version = "0.5.2", features = ["all"], optional = true } pnet_macros = { version = "0.33.0", optional = true } pnet_macros_support = { version = "0.33.0", optional = true } -libssh-rs = {version = "~0.2", features = ["vendored-openssl", "vendored"], optional = true} +libssh-rs = { version = "~0.2", features = [ + "vendored-openssl", + "vendored", +], optional = true } nasl-function-proc-macro = { path = "crates/nasl-function-proc-macro" } nasl-c-lib = { path = "crates/nasl-c-lib", optional = true } [workspace] resolver = "2" -members = [ - "crates/smoketest", - "crates/nasl-function-proc-macro", -] +members = ["crates/smoketest", "crates/nasl-function-proc-macro"] [dev-dependencies] tracing-test = "0.2.5" @@ -101,9 +105,21 @@ criterion = "0" dep-graph-parallel = ["rayon", "crossbeam-channel"] openvas_serde_support = [] serde_support = [] -default = ["dep-graph-parallel", "openvas_serde_support", "enforce-no-trailing-arguments", "serde_support"] +default = [ + "dep-graph-parallel", + "openvas_serde_support", + "enforce-no-trailing-arguments", + "serde_support", +] -nasl-builtin-raw-ip = ["pcap", "pnet_base", "pnet", "socket2", "pnet_macros", "pnet_macros_support",] +nasl-builtin-raw-ip = [ + "pcap", + "pnet_base", + "pnet", + "socket2", + "pnet_macros", + "pnet_macros_support", +] nasl-builtin-ssh = ["libssh-rs"] experimental = ["nasl-builtin-raw-ip", "nasl-builtin-ssh", "nasl-c-lib"] diff --git a/rust/src/nasl/builtin/cert/mod.rs b/rust/src/nasl/builtin/cert/mod.rs new file mode 100644 index 000000000..ef22891e3 --- /dev/null +++ b/rust/src/nasl/builtin/cert/mod.rs @@ -0,0 +1,415 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::collections::HashMap; +use std::sync::RwLock; + +use nasl_function_proc_macro::nasl_function; +use x509_certificate::X509Certificate; +use x509_parser::prelude::GeneralName; + +use crate::{ + function_set, + nasl::{FunctionErrorKind, NaslValue}, +}; + +use super::string::encode_hex; + +fn sign_alg_oid_to_name(oid: &str) -> String { + match oid { + "1.2.840.10040.4.1" => "id-dsa", + "1.2.840.10046.2.1" => "dhpublicnumber", + "2.16.840.1.101.2.1.1.22" => "id-keyExchangeAlgorithm", + "1.2.840.10045.1.1" => "prime-field", + "1.2.840.10045.2.1" => "id-ecPublicKey", + "1.2.840.10045.4.1" => "ecdsa-with-SHA1", + "1.2.840.10045.4.3.1" => "ecdsa-with-SHA224", + "1.2.840.10045.4.3.2" => "ecdsa-with-SHA256", + "1.2.840.10045.4.3.3" => "ecdsa-with-SHA384", + "1.2.840.10045.4.3.4" => "ecdsa-with-SHA512", + "1.3.132.1.12" => "id-ecDH", + "1.2.840.10045.2.13" => "id-ecMQV", + "1.2.840.113549.1.1.10" => "id-RSASSA-PSS", + "1.2.840.113549.1.1.11" => "sha256WithRSAEncryption", + "1.2.840.113549.1.1.12" => "sha384WithRSAEncryption", + "1.2.840.113549.1.1.13" => "sha512WithRSAEncryption", + "1.2.840.113549.1.1.14" => "sha224WithRSAEncryption", + "1.2.840.113549.1.1.8" => "id-mgf1", + "1.2.840.113549.2.2" => "md2", + "1.2.840.113549.2.4" => "md4", + "1.2.840.113549.2.5" => "md5", + "1.2.840.113549.1.1.1" => "rsaEncryption", + "1.2.840.113549.1.1.2" => "md2WithRSAEncryption", + "1.2.840.113549.1.1.3" => "md4WithRSAEncryption", + "1.2.840.113549.1.1.4" => "md5WithRSAEncryption", + "1.2.840.113549.1.1.6" => "rsaOAEPEncryptionSET", + "1.2.840.10045.3.1.1" => "secp192r1", + "1.3.132.0.1" => "sect163k1", + "1.3.132.0.15" => "sect163r2", + "1.3.132.0.33" => "secp224r1", + "1.3.132.0.26" => "sect233k1", + "1.3.132.0.27" => "sect233r1", + "1.2.840.10045.3.1.7" => "secp256r1", + "1.3.132.0.16" => "sect283k1", + "1.3.132.0.17" => "sect283r1", + "1.3.132.0.34" => "secp384r1", + "1.3.132.0.36" => "sect409k1", + "1.3.132.0.37" => "sect409r1", + "1.3.132.0.35" => "sect521r1", + "1.3.132.0.38" => "sect571k1", + "1.3.132.0.39" => "sect571r1", + "2.16.840.1.101.3.4.3.1" => "id-dsa-with-sha224", + "2.16.840.1.101.3.4.3.2" => "id-dsa-with-sha256", + "2.16.840.1.101.3.4.2.1" => "sha256", + "2.16.840.1.101.3.4.2.2" => "sha384", + "2.16.840.1.101.3.4.2.3" => "sha512", + "2.16.840.1.101.3.4.2.4" => "sha224", + _ => "unknown", + } + .to_string() +} + +fn pub_key_alg_oid_to_name(name: &str) -> String { + match name { + "1.2.840.113549.1.1.1" => "RSA", + "2.5.8.1.1" => "RSA (X.509)", + "1.2.840.113549.1.1.4" => "RSA (MD5)", + "1.2.840.113549.1.1.5" => "RSA (SHA1)", + "1.2.840.10040.4.1" => "DSA", + "1.2.643.2.2.19" => "GOST R 34.10-2001", + "1.2.643.2.2.20" => "GOST R 34.10-94", + "1.2.840.10045.2.1" => "EC", + _ => "unknown", + } + .to_string() +} + +fn subject_oid_to_name(oid: &str) -> String { + match oid { + "2.5.4.6" => "C", + "2.5.4.8" => "ST", + "2.5.4.7" => "L", + "2.5.4.10" => "O", + "2.5.4.3" => "CN", + "2.5.4.11" => "OU", + "2.5.4.12" => "T", + "2.5.4.42" => "GN", + "2.5.4.43" => "I", + "2.5.4.4" => "SN", + _ => oid, + } + .to_string() +} + +/// This structure holds a list of certificates. The entries of the list are +/// Optional to allow for the removal of certificates. The closed list holds +/// the indexes of the removed certificates. +#[derive(Default)] +struct Handles { + certs: HashMap, + next: usize, +} + +#[derive(Default)] +pub struct NaslCerts(RwLock); + +impl NaslCerts { + fn insert(&self, cert: X509Certificate) -> usize { + let mut handle = self.0.write().unwrap(); + let index = handle.next; + handle.certs.insert(index, cert); + handle.next += 1; + handle.next - 1 + } + + /// Create a certificate object. + /// + /// Takes a string/data as unnamed argument and returns an identifier + /// used with the other cert functions. The data is usually the BER + /// encoded certificate but the function will also try a PEM encoding + /// on failure to parse BER encoded one. + /// + /// On success the function returns a cert identifier that can be used + /// for further operations. + #[nasl_function] + fn cert_open(&self, cert: &[u8]) -> Result { + if let Ok(cert) = X509Certificate::from_der(cert) { + return Ok(self.insert(cert)); + } + if let Ok(cert) = X509Certificate::from_pem(cert) { + return Ok(self.insert(cert)); + } + if let Ok(cert) = X509Certificate::from_ber(cert) { + return Ok(self.insert(cert)); + } + + Err(FunctionErrorKind::WrongArgument( + "The given string is not a valid DER, BER or PEM encoded X.509 certificate." + .to_string(), + )) + } + + /// Release a certificate object. + /// + /// Takes a cert identifier as returned by cert_open and releases the + /// associated resources. + #[nasl_function] + fn cert_close(&self, cert_handle: usize) { + let mut handle = self.0.write().unwrap(); + handle.certs.remove(&cert_handle); + } + + fn subject(cert: &X509Certificate, idx: usize) -> Option { + // The error originates from the io::Write trait. Internally a Vec is used, which + // implementation of that trait is infallible. Therefore we can unwrap here. + let der = cert.encode_der().unwrap(); + let (_, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + + if idx == 0 { + Some(cert.subject.to_string()) + } else { + cert.subject_alternative_name() + .ok() + .flatten() + .and_then(|san| san.value.general_names.get(idx - 1)) + .map(|san| Some(san.to_string())) + .unwrap_or(None) + } + } + + fn issuer(cert: &X509Certificate, idx: usize) -> Option { + let subject = cert.issuer_name(); + subject.get(idx).map(|entry| { + entry + .iter() + .filter_map(|val| { + val.value.to_string().ok().map(|value| { + format!("{}={}", subject_oid_to_name(&val.typ.to_string()), value) + }) + }) + .collect::>() + .join(", ") + }) + } + + fn hostnames(cert: &X509Certificate) -> Vec { + let mut ret = vec![]; + if let Some(cn) = cert.subject_common_name() { + ret.push(cn); + } + + let der = cert.encode_der().unwrap(); + let (_, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + + if let Ok(Some(san)) = cert.subject_alternative_name() { + for name in san.value.general_names.iter() { + if let GeneralName::DNSName(dns) = name { + ret.push(dns.to_string()); + } + } + } + + ret + } + + fn key_size(cert: &X509Certificate) -> Option { + if let Some(algorithm) = cert.key_algorithm() { + match algorithm { + x509_certificate::KeyAlgorithm::Rsa => { + if let Ok(data) = cert.rsa_public_key_data() { + return Some(((data.modulus.into_bytes().len() - 1) * 8) as i64); + } + } + _ => { + if let Ok(data) = cert.rsa_public_key_data() { + return Some((data.public_exponent.into_bytes().len() * 8) as i64); + } + } + } + } + None + } + + /// Query a certificate object. + /// + /// Takes a cert identifier as first unnamed argument and a command + /// string as second argument. That command is used to select specific + /// information from the certificate. For certain commands the named + /// argument @a idx is used as well. Depending on this command the + /// return value may be a number, a string, or an array of strings. + /// Supported commands are: + /// + /// - serial The serial number of the certificate as a hex string. + /// + /// - issuer Returns the issuer. The returned value is a string in + /// rfc-2253 format. + + /// - subject Returns the subject. The returned value is a string in + /// rfc-2253 format. To query the subjectAltName the + /// named parameters @a idx with values starting at 1 can + /// be used. In this case the format is either an rfc2253 + /// string as used above, an rfc2822 mailbox name + /// indicated by the first character being a left angle + /// bracket or an S-expression in advanced format for all + /// other types of subjectAltnames which is indicated by + /// an opening parentheses. + /// + /// - not-before The notBefore time as UTC value in ISO time format + /// (e.g. "20120930T143521"). + /// + /// - not-after The notAfter time as UTC value in ISO time format + /// (e.g. "20280929T143520"). + /// + /// - all Return all available information in a human readable + /// format. Not yet implemented. + /// + /// - hostnames Return an array with all hostnames listed in the + /// certificates, i.e. the CN part of the subject and all dns-name + /// type subjectAltNames. + /// + /// - fpr-sha-256 The SHA-256 fingerprint of the certificate. The + /// fingerprint is, as usual, computed over the entire + /// DER encode certificate. + /// + /// - fpr-sha-1 The SHA-1 fingerprint of the certificate. The + /// fingerprint is, as usual, computed over the entire + /// DER encode certificate. + /// + /// - image Return the entire certificate as binary data. + /// + /// - algorithm-name Same as signature-algorithm-name. TODO: Remove it and + /// leave only signature-algorithm-name. + /// + /// - signature-algorithm-name Return the algorithm name used to sign the + /// certificate. Get the OID of the digest + /// algorithm and translated to a name from a + /// list from Wireshark. + /// See epan/dissectors/packet-pkcs1.c + /// + /// - public-key-algorithm-name Return the algorithm name of the public key. + /// + /// - modulus Return the RSA public key's modulus found in the + /// structure of the given cert. + /// + /// - exponent Return the RSA public key's exponent found in + /// the structure of the given cert. + /// + /// - key-size Return the size to hold the parameters size in bits. + /// For RSA the bits returned is the modulus. + /// For DSA the bits returned are of the public exponent. + /// + /// + /// The following arguments are required: + /// - pos(0): Object id of the certificate. + /// + /// - pos(1): A string with the command to select what to return; see above. + /// + /// The following arguments are optional: + /// - idx Used by certain commands to select the n-th value of a set + /// of values. If not given 0 is assumed. + /// + /// A NASL type depending on the used command. + #[nasl_function(named(idx))] + fn cert_query( + &self, + cert_handle: usize, + query: &str, + idx: Option, + ) -> Result { + let idx = idx.unwrap_or(0); + let handle = self.0.read().unwrap(); + + let cert = handle.certs.get(&cert_handle).ok_or_else(|| { + FunctionErrorKind::WrongArgument("The given file descriptor is not valid.".to_string()) + })?; + let result = match query { + "serial" => { + let serial = cert.serial_number_asn1().clone().into_bytes(); + NaslValue::String(encode_hex(&serial)) + } + "subject" => Self::subject(cert, idx) + .map(NaslValue::String) + .unwrap_or(NaslValue::Null), + "issuer" => Self::issuer(cert, idx) + .map(NaslValue::String) + .unwrap_or(NaslValue::Null), + "not-before" => { + let not_before = cert.validity_not_before().format("%Y%m%dT%H%M%S"); + NaslValue::String(not_before.to_string()) + } + "not-after" => { + let not_after = cert.validity_not_after().format("%Y%m%dT%H%M%S"); + NaslValue::String(not_after.to_string()) + } + "fpr-sha-256" => cert + .sha256_fingerprint() + .map(|fpr| NaslValue::String(encode_hex(fpr.as_ref()))) + .map_err(|_| { + FunctionErrorKind::Diagnostic( + "Unable to calculate SHA256 fingerprint".to_string(), + None, + ) + })?, + "fpr-sha-1" => cert + .sha1_fingerprint() + .map(|fpr| NaslValue::String(encode_hex(fpr.as_ref()))) + .map_err(|_| { + FunctionErrorKind::Diagnostic( + "Unable to calculate SHA1 fingerprint".to_string(), + None, + ) + })?, + "all" => { + return Err(FunctionErrorKind::Diagnostic( + "Query parameter 'all' is not implemented yet".to_string(), + None, + )) + } + "hostnames" => NaslValue::Array( + Self::hostnames(cert) + .into_iter() + .map(NaslValue::String) + .collect::>(), + ), + "image" => NaslValue::Data(cert.encode_der().unwrap_or_default()), + "algorithm-name" | "signature-algorithm-name" => { + let signature_algorithm = + sign_alg_oid_to_name(&cert.signature_algorithm_oid().to_string()); + NaslValue::String(signature_algorithm) + } + "public-key-algorithm-name" => { + let public_key_algorithm = + pub_key_alg_oid_to_name(&cert.key_algorithm_oid().to_string()); + NaslValue::String(public_key_algorithm) + } + "modulus" => cert + .rsa_public_key_data() + .map(|data| NaslValue::Data(data.modulus.into_bytes().to_vec())) + .unwrap_or(NaslValue::Null), + "exponent" => cert + .rsa_public_key_data() + .map(|data| NaslValue::Data(data.public_exponent.into_bytes().to_vec())) + .unwrap_or(NaslValue::Null), + "key-size" => Self::key_size(cert) + .map(NaslValue::Number) + .unwrap_or(NaslValue::Null), + _ => { + return Err(FunctionErrorKind::WrongArgument( + "The given query is not valid.".to_string(), + )) + } + }; + Ok(result) + } +} + +function_set! { + NaslCerts, + sync_stateful, + ( + (NaslCerts::cert_open, "cert_open"), + (NaslCerts::cert_close, "cert_close"), + (NaslCerts::cert_query, "cert_query"), + ) +} diff --git a/rust/src/nasl/builtin/mod.rs b/rust/src/nasl/builtin/mod.rs index b206c9d51..8195425c1 100644 --- a/rust/src/nasl/builtin/mod.rs +++ b/rust/src/nasl/builtin/mod.rs @@ -5,6 +5,7 @@ #![doc = include_str!("README.md")] mod array; +mod cert; mod cryptographic; mod description; mod host; @@ -46,7 +47,8 @@ pub fn nasl_std_functions() -> Executor { .add_set(regex::RegularExpressions) .add_set(cryptographic::Cryptographic) .add_set(description::Description) - .add_set(isotime::NaslIsotime); + .add_set(isotime::NaslIsotime) + .add_set(cert::NaslCerts::default()); #[cfg(feature = "nasl-builtin-ssh")] executor.add_set(ssh::Ssh::default()); diff --git a/rust/typos.toml b/rust/typos.toml index 25e73c048..411162901 100644 --- a/rust/typos.toml +++ b/rust/typos.toml @@ -5,6 +5,12 @@ des_ede_cbc_encrypt = "des_ede_cbc_encrypt" [default.extend-words] hd = "hd" guid = "guid" +GOST = "GOST" +fpr = "fpr" [files] -extend-exclude = ["data/osp/response_*.xml", "crates/smoketest/configs/client_sample.cert", "*.notus"] +extend-exclude = [ + "data/osp/response_*.xml", + "crates/smoketest/configs/client_sample.cert", + "*.notus", +]