diff options
Diffstat (limited to 'third_party/rust/nss-gk-api')
28 files changed, 2235 insertions, 0 deletions
diff --git a/third_party/rust/nss-gk-api/.cargo-checksum.json b/third_party/rust/nss-gk-api/.cargo-checksum.json new file mode 100644 index 0000000000..43178d2668 --- /dev/null +++ b/third_party/rust/nss-gk-api/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"484e9876017a5f297ed6921c5336e65235b1e4783753f4f3f31e0256e86ef266","README.md":"a76b467337dd5c5ac8f0e315b1e1f584f2d8c3b6fe57aba16306a3faf9f8fde7","bindings/bindings.toml":"4fbb1e31f56f068e040842408e0e9a5f0f1f35651298afa86836765049a2928b","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nspr_types.h":"6e1ca8e760c913e08507dcb8645748661c81b649ac148ee046d2e4e95f39cede","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_prelude.h":"3952a566aaa497b4c3094bc6c338bba987134a2d5f336c409d1bd6d31e14a8f8","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"3d8a2d22ca23653d6421a7bb2752c6c532e4cf823b2b3a6622730fd46586665d","src/err.rs":"6c7e785f9c52e7606bc4de3fa4e6805b73cf828430850fd6c4e87bff20f4530e","src/exp.rs":"01e999dc4be93a12e5890b2d8c4ce62d3f5afecf9f8373f150353633731445f9","src/lib.rs":"7b50ec7ddb61cdef461449e4380402569e4ffde7940341dbafc6b60922482132","src/p11.rs":"f79fbc3b639fe6ae7a2c68e2f89de0be9c40b44adcdaba1631fd980c24de77e6","src/prio.rs":"bdfef0e3898876ee223218aedc0b2d2f43575e4302ffdd0fa5a719d4f6e468e0","src/prtypes.rs":"270effec36a2d6836329e672e8043bf277f48fe136c8844f5eb503c0e32511f9","src/ssl.rs":"a36251e63484e382ff70d8c1008ec456746c7826e4c628fe42818880ecf1596b","src/time.rs":"1fce901a535d67baaef59c42f39c5d8eb5a4ac6cbb69026fbc7088dd822fa404","src/util.rs":"f3c163ff609b4211e1ebab08604f770e4bcf11a6946c9d0ecb4165594a4bb886"},"package":"1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"}
\ No newline at end of file diff --git a/third_party/rust/nss-gk-api/Cargo.toml b/third_party/rust/nss-gk-api/Cargo.toml new file mode 100644 index 0000000000..0993117459 --- /dev/null +++ b/third_party/rust/nss-gk-api/Cargo.toml @@ -0,0 +1,54 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.57.0" +name = "nss-gk-api" +version = "0.2.1" +authors = [ + "Martin Thomson <mt@lowentropy.net>", + "Andy Leiserson <aleiserson@mozilla.com>", + "John M. Schanck <jschanck@mozilla.com>", +] +description = "Gecko API for NSS" +readme = "README.md" +license = "MIT/Apache-2.0" +repository = "https://github.com/mozilla/nss-gk-api" + +[dependencies.once_cell] +version = "1" + +[dependencies.pkcs11-bindings] +version = ">= 0.1.3" + +[build-dependencies.bindgen] +version = ">= 0.59.2" +features = ["runtime"] +default-features = false + +[build-dependencies.mozbuild] +version = "0.1" +optional = true + +[build-dependencies.serde] +version = "1.0" + +[build-dependencies.serde_derive] +version = "1.0" + +[build-dependencies.toml] +version = "0.5" + +[features] +default = ["deny-warnings"] +deny-warnings = [] +gecko = ["mozbuild"] diff --git a/third_party/rust/nss-gk-api/README.md b/third_party/rust/nss-gk-api/README.md new file mode 100644 index 0000000000..f86711a931 --- /dev/null +++ b/third_party/rust/nss-gk-api/README.md @@ -0,0 +1,6 @@ +# (UNSTABLE) Gecko API for NSS + +nss-gk-api is intended to provide a safe and idiomatic Rust interface to NSS. It is based on code from neqo-crypto, but has been factored out of mozilla-central so that it can be used in standalone applications and libraries such as authenticator-rs. That said, it is *primarily* for use in Gecko, and will not be extended to support arbitrary use cases. + +This is work in progress and major changes are expected. In particular, a new version of the `nss-sys` crate will be factored out of this crate. + diff --git a/third_party/rust/nss-gk-api/bindings/bindings.toml b/third_party/rust/nss-gk-api/bindings/bindings.toml new file mode 100644 index 0000000000..15c640765c --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/bindings.toml @@ -0,0 +1,280 @@ +# In this file, every section corresponds to a header file. +# A corresponding binding file will be created in $OUT_DIR. +# +# This configuration is processed by build.rs to populate `exclude` for each +# header with the types/variables/functions/enums declared for generation in +# other headers. + +[nspr_types] +types = [ + "PRBool", + "PRIntn", + "PRInt16", + "PRInt32", + "PRInt64", + "PROffset32", + "PROffset64", + "PRSize", + "PRStatus", + "PRUintn", + "PRUint8", + "PRUint16", + "PRUint32", + "PRUint64", + "PRUword", +] +variables = [ + "PR_FALSE", + "PR_TRUE", +] + +[nss_prelude] +types = [ + "SECItem", + "SECItemArray", + "SECItemStr", + "SECStatus", +] +functions = [ + "SECITEM_FreeItem", +] +enums = [ + "_SECStatus", + "SECItemType", +] + +[nss_ssl] +types = [ + "SSLAlertDescription", + "SSLExtensionType", + "SSLNamedGroup", + "SSLProtocolVariant", + "SSLSignatureScheme", +] +functions = [ + "SSL_AlertSentCallback", + "SSL_AuthCertificateComplete", + "SSL_AuthCertificateHook", + "SSL_CipherPrefSet", + "SSL_ConfigServerCert", + "SSL_ConfigServerSessionIDCache", + "SSL_DestroyResumptionTokenInfo", + "SSL_GetChannelInfo", + "SSL_GetImplementedCiphers", + "SSL_GetNextProto", + "SSL_GetNumImplementedCiphers", + "SSL_GetPreliminaryChannelInfo", + "SSL_GetResumptionTokenInfo", + "SSL_ForceHandshake", + "SSL_ImportFD", + "SSL_NamedGroupConfig", + "SSL_OptionSet", + "SSL_OptionGetDefault", + "SSL_PeerCertificate", + "SSL_PeerCertificateChain", + "SSL_PeerSignedCertTimestamps", + "SSL_PeerStapledOCSPResponses", + "SSL_ResetHandshake", + "SSL_SetNextProtoNego", + "SSL_SetURL", + "SSL_VersionRangeSet", +] +enums = [ + "SSLAuthType", + "SSLCipherAlgorithm", + "SSLCompressionMethod", + "SSLExtensionType", + "SSLKEAType", + "SSLMACAlgorithm", + "SSLNamedGroup", + "SSLNextProtoState", + "SSLProtocolVariant", + "SSLSignatureScheme", +] +variables = [ + "SSL_LIBRARY_VERSION_TLS_\\d_\\d", + "SSL_NumImplementedCiphers", + "ssl_preinfo_.*", +] +opaque = [ + "SSLExtraServerCertData", +] + +[nss_sslopt] +variables = [ + "SSL_REQUEST_CERTIFICATE", + "SSL_REQUIRE_CERTIFICATE", + "SSL_NO_LOCKS", + "SSL_ENABLE_SESSION_TICKETS", + "SSL_ENABLE_OCSP_STAPLING", + "SSL_ENABLE_ALPN", + "SSL_ENABLE_EXTENDED_MASTER_SECRET", + "SSL_ENABLE_SIGNED_CERT_TIMESTAMPS", + "SSL_ENABLE_0RTT_DATA", + "SSL_RECORD_SIZE_LIMIT", + "SSL_ENABLE_TLS13_COMPAT_MODE", + "SSL_ENABLE_HELLO_DOWNGRADE_CHECK", + "SSL_SUPPRESS_END_OF_EARLY_DATA", +] + +[nss_ciphers] +variables = ["TLS_.*"] +exclude = [ + ".*_(?:EXPORT(?:1024)?|anon|DES|RC4)_.*", + ".*_(?:MD5|NULL_SHA)", +] + +[nss_secerr] +types = ["SECErrorCodes"] +enums = ["SECErrorCodes"] + +[nss_sslerr] +types = ["SSLErrorCodes"] +enums = ["SSLErrorCodes"] + +[nss_init] +functions = [ + "NSS_Initialize", + "NSS_IsInitialized", + "NSS_NoDB_Init", + "NSS_SetDomesticPolicy", + "NSS_Shutdown", + "NSS_VersionCheck", +] +variables = [ + "NSS_INIT_READONLY", + "SECMOD_DB", +] + +[nss_p11] +types = [ + "CERTCertList", + "CERTCertListNode", + "HpkeAeadId", + "HpkeKdfId", + "HpkeKemId", +] +functions = [ + "CERT_DestroyCertificate", + "CERT_DestroyCertList", + "CERT_GetCertificateDer", + "PK11_CipherOp", + "PK11_CreateContextBySymKey", + "PK11_Decrypt", + "PK11_DestroyContext", + "PK11_DigestOp", + "PK11_DigestFinal", + "PK11_Encrypt", + "PK11_ExtractKeyValue", + "PK11_FindCertFromNickname", + "PK11_FindKeyByAnyCert", + "PK11_FreeSlot", + "PK11_FreeSymKey", + "PK11_GenerateKeyPairWithOpFlags", + "PK11_GenerateRandom", + "PK11_GetBlockSize", + "PK11_GetInternalSlot", + "PK11_GetKeyData", + "PK11_GetMechanism", + "PK11_HashBuf", + "PK11_HPKE_Serialize", + "PK11_ImportDataKey", + "PK11_ImportDERPrivateKeyInfoAndReturnKey", + "PK11_ImportSymKey", + "PK11_PubDeriveWithKDF", + "PK11_ReadRawAttribute", + "PK11_ReferenceSymKey", + "SECKEY_CopyPrivateKey", + "SECKEY_CopyPublicKey", + "SECKEY_DecodeDERSubjectPublicKeyInfo", + "SECKEY_DestroyPrivateKey", + "SECKEY_DestroyPublicKey", + "SECKEY_DestroySubjectPublicKeyInfo", + "SECKEY_ExtractPublicKey", + "SECKEY_ConvertToPublicKey", + "SECOID_FindOIDByTag", +] +enums = [ + "HpkeAeadId", + "HpkeKdfId", + "HpkeKemId", + "PK11ObjectType", + "PK11Origin", + "SECOidTag", +] +opaque = [ + "CERTCertificateStr", + "PK11ContextStr", + "PK11SlotInfoStr", + "PK11SymKeyStr", + "SECKEYPrivateKeyStr", + "SECKEYPublicKeyStr", +] +variables = [ + "AES_BLOCK_SIZE", + "PK11_ATTR_INSENSITIVE", + "PK11_ATTR_PRIVATE", + "PK11_ATTR_PUBLIC", + "PK11_ATTR_SENSITIVE", + "PK11_ATTR_SESSION", + "SEC_ASN1_OBJECT_ID", + "SHA256_LENGTH", +] + +[nspr_err] +variables = ["PR_.*_ERROR"] + +[nspr_error] +types = [ + "PRErrorCode", +] +functions = [ + "PR_ErrorToName", + "PR_ErrorToString", + "PR_GetError", + "PR_SetError", +] +variables = [ + "PR_LANGUAGE_I_DEFAULT", +] + +[nspr_io] +types = [ + "PRFileDesc", + "PRFileInfo", + "PRFileInfo64", + "PRFilePrivate", + "PRIOMethods", + "PRIOVec", + "PRSeekFN", + "PRSeek64FN", +] +functions = [ + "PR_Close", + "PR_CreateIOLayerStub", + "PR_GetUniqueIdentity", +] +variables = [ + "PR_AF_INET", +] +# opaque is for the stuff we don't plan to use, but we need for function signatures. +opaque = [ + "PRFileDesc", + "PRFileInfo", + "PRFileInfo64", + "PRFilePrivate", + "PRIOVec", +] +enums = [ + "PRDescType", + "PRSeekWhence", +] + +[nspr_time] +types = ["PRTime"] +functions = ["PR_Now"] + +[mozpkix] +cplusplus = true +types = ["mozilla::pkix::ErrorCode"] +enums = ["mozilla::pkix::ErrorCode"] diff --git a/third_party/rust/nss-gk-api/bindings/mozpkix.hpp b/third_party/rust/nss-gk-api/bindings/mozpkix.hpp new file mode 100644 index 0000000000..d0a6cb5861 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/mozpkix.hpp @@ -0,0 +1 @@ +#include "mozpkix/pkixnss.h"
\ No newline at end of file diff --git a/third_party/rust/nss-gk-api/bindings/nspr_err.h b/third_party/rust/nss-gk-api/bindings/nspr_err.h new file mode 100644 index 0000000000..204e771c49 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_err.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_error.h b/third_party/rust/nss-gk-api/bindings/nspr_error.h new file mode 100644 index 0000000000..8ff8ce202d --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_error.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prerror.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_io.h b/third_party/rust/nss-gk-api/bindings/nspr_io.h new file mode 100644 index 0000000000..9997fb812e --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_io.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prio.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_time.h b/third_party/rust/nss-gk-api/bindings/nspr_time.h new file mode 100644 index 0000000000..f5596577fa --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_time.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prtime.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_types.h b/third_party/rust/nss-gk-api/bindings/nspr_types.h new file mode 100644 index 0000000000..624587560b --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_types.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prtypes.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_ciphers.h b/third_party/rust/nss-gk-api/bindings/nss_ciphers.h new file mode 100644 index 0000000000..f064f39c5d --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_ciphers.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#define SSL_DISABLE_DEPRECATED_CIPHER_SUITE_NAMES +#include "sslproto.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_init.h b/third_party/rust/nss-gk-api/bindings/nss_init.h new file mode 100644 index 0000000000..ae111bac2b --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_init.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "nss.h" +#include "ssl.h" // For NSS_SetDomesticPolicy diff --git a/third_party/rust/nss-gk-api/bindings/nss_p11.h b/third_party/rust/nss-gk-api/bindings/nss_p11.h new file mode 100644 index 0000000000..7de50eebec --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_p11.h @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "cert.h" +#include "keyhi.h" +#include "pk11pub.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_prelude.h b/third_party/rust/nss-gk-api/bindings/nss_prelude.h new file mode 100644 index 0000000000..72af8f03c9 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_prelude.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "seccomon.h" +#include "secitem.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_secerr.h b/third_party/rust/nss-gk-api/bindings/nss_secerr.h new file mode 100644 index 0000000000..c2b2d4020c --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_secerr.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "secerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_ssl.h b/third_party/rust/nss-gk-api/bindings/nss_ssl.h new file mode 100644 index 0000000000..1cde112cf2 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_ssl.h @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "sslproto.h" +#include "ssl.h" +#include "sslexp.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_sslerr.h b/third_party/rust/nss-gk-api/bindings/nss_sslerr.h new file mode 100644 index 0000000000..74a836f1e8 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_sslerr.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "sslerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_sslopt.h b/third_party/rust/nss-gk-api/bindings/nss_sslopt.h new file mode 100644 index 0000000000..a14e1e69d1 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_sslopt.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "ssl.h" diff --git a/third_party/rust/nss-gk-api/build.rs b/third_party/rust/nss-gk-api/build.rs new file mode 100644 index 0000000000..ab1892936b --- /dev/null +++ b/third_party/rust/nss-gk-api/build.rs @@ -0,0 +1,445 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] + +use bindgen::Builder; +use serde_derive::Deserialize; +use std::collections::HashMap; +use std::collections::HashSet; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const BINDINGS_DIR: &str = "bindings"; +const BINDINGS_CONFIG: &str = "bindings.toml"; + +// This is the format of a single section of the configuration file. +#[derive(Deserialize)] +struct Bindings { + /// types that are explicitly included + #[serde(default)] + types: Vec<String>, + /// functions that are explicitly included + #[serde(default)] + functions: Vec<String>, + /// variables (and `#define`s) that are explicitly included + #[serde(default)] + variables: Vec<String>, + /// types that should be explicitly marked as opaque + #[serde(default)] + opaque: Vec<String>, + /// enumerations that are turned into a module (without this, the enum is + /// mapped using the default, which means that the individual values are + /// formed with an underscore as <enum_type>_<enum_value_name>). + #[serde(default)] + enums: Vec<String>, + + /// Any item that is specifically excluded; if none of the types, functions, + /// or variables fields are specified, everything defined will be mapped, + /// so this can be used to limit that. + #[serde(default)] + exclude: Vec<String>, + + /// Whether the file is to be interpreted as C++ + #[serde(default)] + cplusplus: bool, +} + +fn is_debug() -> bool { + env::var("DEBUG") + .map(|d| d.parse::<bool>().unwrap_or(false)) + .unwrap_or(false) +} + +// bindgen needs access to libclang. +// On windows, this doesn't just work, you have to set LIBCLANG_PATH. +// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work. +fn setup_clang() { + if env::consts::OS != "windows" { + return; + } + println!("rerun-if-env-changed=LIBCLANG_PATH"); + println!("rerun-if-env-changed=MOZBUILD_STATE_PATH"); + if env::var("LIBCLANG_PATH").is_ok() { + return; + } + let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") { + PathBuf::from(dir.trim()) + } else { + eprintln!("warning: Building without a gecko setup is not likely to work."); + eprintln!(" A working libclang is needed to build nss-sys."); + eprintln!(" Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set."); + eprintln!(); + eprintln!(" We recommend checking out https://github.com/mozilla/gecko-dev"); + eprintln!(" Then run `./mach bootstrap` which will retrieve clang."); + eprintln!(" Make sure to export MOZBUILD_STATE_PATH when building."); + return; + }; + let libclang_dir = mozbuild_root.join("clang").join("lib"); + if libclang_dir.is_dir() { + env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap()); + println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap()); + } else { + println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko"); + } +} + +fn nss_dir() -> PathBuf { + let dir = if let Ok(dir) = env::var("NSS_DIR") { + let path = PathBuf::from(dir.trim()); + assert!( + !path.is_relative(), + "The NSS_DIR environment variable is expected to be an absolute path." + ); + path + } else { + let out_dir = env::var("OUT_DIR").unwrap(); + let dir = Path::new(&out_dir).join("nss"); + if !dir.exists() { + Command::new("hg") + .args(&[ + "clone", + "https://hg.mozilla.org/projects/nss", + dir.to_str().unwrap(), + ]) + .status() + .expect("can't clone nss"); + } + let nspr_dir = Path::new(&out_dir).join("nspr"); + if !nspr_dir.exists() { + Command::new("hg") + .args(&[ + "clone", + "https://hg.mozilla.org/projects/nspr", + nspr_dir.to_str().unwrap(), + ]) + .status() + .expect("can't clone nspr"); + } + dir + }; + assert!(dir.is_dir(), "NSS_DIR {:?} doesn't exist", dir); + // Note that this returns a relative path because UNC + // paths on windows cause certain tools to explode. + dir +} + +fn get_bash() -> PathBuf { + // When running under MOZILLABUILD, we need to make sure not to invoke + // another instance of bash that might be sitting around (like WSL). + match env::var("MOZILLABUILD") { + Ok(d) => PathBuf::from(d).join("msys").join("bin").join("bash.exe"), + Err(_) => PathBuf::from("bash"), + } +} + +fn build_nss(dir: PathBuf) { + let mut build_nss = vec![ + String::from("./build.sh"), + String::from("-Ddisable_tests=1"), + ]; + if is_debug() { + build_nss.push(String::from("--static")); + } else { + build_nss.push(String::from("-o")); + } + if let Ok(d) = env::var("NSS_JOBS") { + build_nss.push(String::from("-j")); + build_nss.push(d); + } + let status = Command::new(get_bash()) + .args(build_nss) + .current_dir(dir) + .status() + .expect("couldn't start NSS build"); + assert!(status.success(), "NSS build failed"); +} + +fn dynamic_link() { + let libs = if env::consts::OS == "windows" { + &["nssutil3.dll", "nss3.dll", "ssl3.dll"] + } else { + &["nssutil3", "nss3", "ssl3"] + }; + dynamic_link_both(libs); +} + +fn dynamic_link_both(extra_libs: &[&str]) { + let nspr_libs = if env::consts::OS == "windows" { + &["libplds4", "libplc4", "libnspr4"] + } else { + &["plds4", "plc4", "nspr4"] + }; + for lib in nspr_libs.iter().chain(extra_libs) { + println!("cargo:rustc-link-lib=dylib={}", lib); + } +} + +fn static_link() { + let mut static_libs = vec![ + "certdb", + "certhi", + "cryptohi", + "freebl", + "nss_static", + "nssb", + "nssdev", + "nsspki", + "nssutil", + "pk11wrap", + "pkcs12", + "pkcs7", + "smime", + "softokn_static", + "ssl", + ]; + if env::consts::OS != "macos" { + static_libs.push("sqlite"); + } + for lib in static_libs { + println!("cargo:rustc-link-lib=static={}", lib); + } + + // Dynamic libs that aren't transitively included by NSS libs. + let mut other_libs = Vec::new(); + if env::consts::OS != "windows" { + other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]); + } + if env::consts::OS == "macos" { + other_libs.push("sqlite3"); + } + dynamic_link_both(&other_libs); +} + +fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> { + let nsprinclude = nsstarget.join("include").join("nspr"); + let nssinclude = nssdist.join("public").join("nss"); + let includes = vec![nsprinclude, nssinclude]; + for i in &includes { + println!("cargo:include={}", i.to_str().unwrap()); + } + includes +} + +fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) { + let suffix = if bindings.cplusplus { ".hpp" } else { ".h" }; + let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix); + let header = header_path.to_str().unwrap(); + let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs"); + + println!("cargo:rerun-if-changed={}", header); + + let mut builder = Builder::default().header(header); + builder = builder.generate_comments(false); + builder = builder.size_t_is_usize(true); + + builder = builder.clang_arg("-v"); + + if !gecko { + builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT"); + if env::consts::OS == "windows" { + builder = builder.clang_arg("-DWIN"); + } else if env::consts::OS == "macos" { + builder = builder.clang_arg("-DDARWIN"); + } else if env::consts::OS == "linux" { + builder = builder.clang_arg("-DLINUX"); + } else if env::consts::OS == "android" { + builder = builder.clang_arg("-DLINUX"); + builder = builder.clang_arg("-DANDROID"); + } + if bindings.cplusplus { + builder = builder.clang_args(&["-x", "c++", "-std=c++11"]); + } + } + + builder = builder.clang_args(flags); + + // Apply the configuration. + for v in &bindings.types { + builder = builder.allowlist_type(v); + } + for v in &bindings.functions { + builder = builder.allowlist_function(v); + } + for v in &bindings.variables { + builder = builder.allowlist_var(v); + } + for v in &bindings.exclude { + builder = builder.blocklist_item(v); + } + for v in &bindings.opaque { + builder = builder.opaque_type(v); + } + for v in &bindings.enums { + builder = builder.constified_enum_module(v); + } + + let bindings = builder.generate().expect("unable to generate bindings"); + bindings + .write_to_file(out) + .expect("couldn't write bindings"); +} + +fn setup_standalone() -> Vec<String> { + setup_clang(); + + let nss = nss_dir(); + println!("cargo:rerun-if-env-changed={}", nss.display()); + + build_nss(nss.clone()); + + // $NSS_DIR/../dist/ + let nssdist = nss.parent().unwrap().join("dist"); + println!("cargo:rerun-if-env-changed={}", nssdist.display()); + + let nsstarget = env::var("NSS_TARGET") + .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap()); + let nsstarget = nssdist.join(nsstarget.trim()); + + let includes = get_includes(&nsstarget, &nssdist); + + let nsslibdir = nsstarget.join("lib"); + println!( + "cargo:rustc-link-search=native={}", + nsslibdir.to_str().unwrap() + ); + if is_debug() { + static_link(); + } else { + dynamic_link(); + } + + let mut flags: Vec<String> = Vec::new(); + for i in includes { + flags.push(String::from("-I") + i.to_str().unwrap()); + } + + flags +} + +#[cfg(feature = "gecko")] +fn setup_for_gecko() -> Vec<String> { + use mozbuild::TOPOBJDIR; + + let fold_libs = mozbuild::config::MOZ_FOLD_LIBS; + let libs = if fold_libs { + vec!["nss3"] + } else { + vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"] + }; + + for lib in &libs { + println!("cargo:rustc-link-lib=dylib={}", lib); + } + + if fold_libs { + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR.join("security").to_str().unwrap() + ); + } else { + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR.join("dist").join("bin").to_str().unwrap() + ); + let nsslib_path = TOPOBJDIR.join("security").join("nss").join("lib"); + println!( + "cargo:rustc-link-search=native={}", + nsslib_path.join("nss").join("nss_nss3").to_str().unwrap() + ); + println!( + "cargo:rustc-link-search=native={}", + nsslib_path.join("ssl").join("ssl_ssl3").to_str().unwrap() + ); + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR + .join("config") + .join("external") + .join("nspr") + .join("pr") + .to_str() + .unwrap() + ); + } + + let flags_path = TOPOBJDIR.join("netwerk/socket/neqo/extra-bindgen-flags"); + + println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap()); + let mut flags = fs::read_to_string(flags_path) + .expect("Failed to read extra-bindgen-flags file") + .split_whitespace() + .map(std::borrow::ToOwned::to_owned) + .collect::<Vec<String>>(); + + flags.push(String::from("-include")); + flags.push( + TOPOBJDIR + .join("dist") + .join("include") + .join("mozilla-config.h") + .to_str() + .unwrap() + .to_string(), + ); + flags +} + +#[cfg(not(feature = "gecko"))] +fn setup_for_gecko() -> Vec<String> { + unreachable!() +} + +fn process_config(config: &mut HashMap<String, Bindings>) { + let mut excludes = HashMap::new(); + for header in config.keys().cloned() { + // Collect the list of types, functions, and variables configured + // for generation in any other configured header, and add it to the list + // of items excluded from generation in this header. This ensures that + // each item only appears in one bindings module, which prevents some + // type conflicts. (However, it does mean that appropriate `use` + // declarations must be added for the generated modules.) + excludes.insert( + header.clone(), + config.iter().flat_map(|(h, b)| { + if *h != header { + vec![ + &b.types, + &b.functions, + &b.variables, + ] + } else { + vec![] + }.into_iter().flat_map(|v| v.iter()).cloned() + }).collect::<HashSet<String>>() + ); + } + + for (header, excludes) in excludes.into_iter() { + config.get_mut(&header).expect("key disappeared from config?").exclude.extend(excludes.into_iter()); + } +} + +fn main() { + let flags = if cfg!(feature = "gecko") { + setup_for_gecko() + } else { + setup_standalone() + }; + + let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG); + println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap()); + let config = fs::read_to_string(config_file).expect("unable to read binding configuration"); + let mut config: HashMap<String, Bindings> = ::toml::from_str(&config).unwrap(); + process_config(&mut config); + + for (k, v) in &config { + build_bindings(k, v, &flags[..], cfg!(feature = "gecko")); + } +} diff --git a/third_party/rust/nss-gk-api/src/err.rs b/third_party/rust/nss-gk-api/src/err.rs new file mode 100644 index 0000000000..cc52b51f63 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/err.rs @@ -0,0 +1,257 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![allow(clippy::upper_case_acronyms)] + +use std::os::raw::c_char; +use std::str::Utf8Error; + +use crate::nss_prelude::*; +use crate::prtypes::*; + +include!(concat!(env!("OUT_DIR"), "/nspr_error.rs")); +mod codes { + #![allow(non_snake_case)] + include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs")); + include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs")); + include!(concat!(env!("OUT_DIR"), "/mozpkix.rs")); +} +pub use codes::mozilla_pkix_ErrorCode as mozpkix; +pub use codes::SECErrorCodes as sec; +pub use codes::SSLErrorCodes as ssl; +pub mod nspr { + include!(concat!(env!("OUT_DIR"), "/nspr_err.rs")); +} + +pub type Res<T> = Result<T, Error>; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub enum Error { + AeadError, + CertificateLoading, + CipherInitFailure, + CreateSslSocket, + EchRetry(Vec<u8>), + HkdfError, + InternalError, + IntegerOverflow, + InvalidEpoch, + MixedHandshakeMethod, + NoDataAvailable, + NssError { + name: String, + code: PRErrorCode, + desc: String, + }, + OverrunError, + SelfEncryptFailure, + StringError, + TimeTravelError, + UnsupportedCipher, + UnsupportedVersion, +} + +impl Error { + pub(crate) fn last_nss_error() -> Self { + Self::from(unsafe { PR_GetError() }) + } +} + +impl std::error::Error for Error { + #[must_use] + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } + #[must_use] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Error: {:?}", self) + } +} + +impl From<std::num::TryFromIntError> for Error { + #[must_use] + fn from(_: std::num::TryFromIntError) -> Self { + Self::IntegerOverflow + } +} +impl From<std::ffi::NulError> for Error { + #[must_use] + fn from(_: std::ffi::NulError) -> Self { + Self::InternalError + } +} +impl From<Utf8Error> for Error { + fn from(_: Utf8Error) -> Self { + Self::StringError + } +} +impl From<PRErrorCode> for Error { + fn from(code: PRErrorCode) -> Self { + let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR"); + let desc = wrap_str_fn( + || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) }, + "...", + ); + Self::NssError { name, code, desc } + } +} + +use std::ffi::CStr; + +fn wrap_str_fn<F>(f: F, dflt: &str) -> String +where + F: FnOnce() -> *const c_char, +{ + unsafe { + let p = f(); + if p.is_null() { + return dflt.to_string(); + } + CStr::from_ptr(p).to_string_lossy().into_owned() + } +} + +pub fn is_blocked(result: &Res<()>) -> bool { + match result { + Err(Error::NssError { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR, + _ => false, + } +} + +pub trait IntoResult +{ + /// The `Ok` type for the result. + type Ok; + + /// Unsafe in our implementors because they take a pointer and have no way + /// to ensure that the pointer is valid. An invalid pointer could cause UB + /// in `impl Drop for Scoped`. + unsafe fn into_result(self) -> Result<Self::Ok, Error>; +} + +pub unsafe fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { + if ptr.is_null() { + Err(Error::last_nss_error()) + } else { + Ok(ptr) + } +} + +// This can be used to implement `IntoResult` for pointer types that do not make +// sense as smart pointers. For smart pointers use `scoped_ptr!`. +macro_rules! impl_into_result { + ($pointer:ty) => { + impl $crate::err::IntoResult for *mut $pointer { + type Ok = *mut $pointer; + + unsafe fn into_result(self) -> Result<Self::Ok, $crate::err::Error> { + $crate::err::into_result(self) + } + } + } +} + +impl IntoResult for SECStatus { + type Ok = (); + + unsafe fn into_result(self) -> Result<(), Error> { + if self == SECSuccess { + Ok(()) + } else { + Err(Error::last_nss_error()) + } + } +} + +pub fn secstatus_to_res(code: SECStatus) -> Res<()> { + // Unsafe in the trait, but this impl should be safe. + unsafe { SECStatus::into_result(code) } +} + +#[cfg(test)] +mod tests { + use crate::err::{self, is_blocked, secstatus_to_res, Error, PRErrorCode, PR_SetError}; + use crate::ssl::{SECFailure, SECSuccess}; + use test_fixture::fixture_init; + + fn set_error_code(code: PRErrorCode) { + // This code doesn't work without initializing NSS first. + fixture_init(); + unsafe { + PR_SetError(code, 0); + } + } + + #[test] + fn error_code() { + fixture_init(); + assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ); + assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL); + assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR); + } + + #[test] + fn is_ok() { + assert!(secstatus_to_res(SECSuccess).is_ok()); + } + + #[test] + fn is_err() { + set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "SSL_ERROR_BAD_MAC_READ"); + assert_eq!(code, -12273); + assert_eq!( + desc, + "SSL received a record with an incorrect Message Authentication Code." + ); + } + _ => unreachable!(), + } + } + + #[test] + fn is_err_zero_code() { + set_error_code(0); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, .. } => { + assert_eq!(name, "UNKNOWN_ERROR"); + assert_eq!(code, 0); + // Note that we don't test |desc| here because that comes from + // strerror(0), which is platform-dependent. + } + _ => unreachable!(), + } + } + + #[test] + fn blocked() { + set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + assert!(is_blocked(&r)); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "PR_WOULD_BLOCK_ERROR"); + assert_eq!(code, -5998); + assert_eq!(desc, "The operation would have blocked"); + } + _ => panic!("bad error type"), + } + } +} diff --git a/third_party/rust/nss-gk-api/src/exp.rs b/third_party/rust/nss-gk-api/src/exp.rs new file mode 100644 index 0000000000..9f2255c1dc --- /dev/null +++ b/third_party/rust/nss-gk-api/src/exp.rs @@ -0,0 +1,25 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/* TODO +macro_rules! experimental_api { + ( $n:ident ( $( $a:ident : $t:ty ),* $(,)? ) ) => { + #[allow(non_snake_case)] + #[allow(clippy::too_many_arguments)] + pub(crate) unsafe fn $n ( $( $a : $t ),* ) -> Result<(), crate::err::Error> { + const EXP_FUNCTION: &str = stringify!($n); + let n = ::std::ffi::CString::new(EXP_FUNCTION)?; + let f = crate::ssl::SSL_GetExperimentalAPI(n.as_ptr()); + if f.is_null() { + return Err(crate::err::Error::InternalError); + } + let f: unsafe extern "C" fn( $( $t ),* ) -> crate::SECStatus = ::std::mem::transmute(f); + let rv = f( $( $a ),* ); + crate::err::secstatus_to_res(rv) + } + }; +} +*/ diff --git a/third_party/rust/nss-gk-api/src/lib.rs b/third_party/rust/nss-gk-api/src/lib.rs new file mode 100644 index 0000000000..1cd0f2a6e8 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/lib.rs @@ -0,0 +1,173 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] +// Bindgen auto generated code +// won't adhere to the clippy rules below +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::unseparated_literal_suffix)] +#![allow(clippy::used_underscore_binding)] + +#[macro_use] +pub mod err; +#[macro_use] +mod exp; +#[macro_use] +mod util; + +pub mod p11; +mod prio; +mod ssl; +pub mod time; + +pub use err::{Error, IntoResult, secstatus_to_res}; +pub use p11::{PrivateKey, PublicKey, SymKey}; +pub use util::*; + +use once_cell::sync::OnceCell; + +use std::ffi::CString; +use std::path::{Path, PathBuf}; +use std::ptr::null; + +const MINIMUM_NSS_VERSION: &str = "3.74"; + +#[allow(non_snake_case)] +#[allow(non_upper_case_globals)] +pub mod nss_prelude { + pub use crate::prtypes::*; + pub use _SECStatus::*; + include!(concat!(env!("OUT_DIR"), "/nss_prelude.rs")); +} +pub use nss_prelude::{SECItem, SECItemArray, SECItemType, SECStatus}; + +#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)] +#[allow(clippy::upper_case_acronyms)] +#[allow(unknown_lints, clippy::borrow_as_ptr)] +mod nss { + use crate::nss_prelude::*; + include!(concat!(env!("OUT_DIR"), "/nss_init.rs")); +} + +pub mod prtypes; +pub use prtypes::*; + +// Shadow these bindgen created values to correct their type. +pub const PR_FALSE: PRBool = prtypes::PR_FALSE as PRBool; +pub const PR_TRUE: PRBool = prtypes::PR_TRUE as PRBool; + +enum NssLoaded { + External, + NoDb, + Db(Box<Path>), +} + +impl Drop for NssLoaded { + fn drop(&mut self) { + if !matches!(self, Self::External) { + unsafe { + secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed"); + } + } + } +} + +static INITIALIZED: OnceCell<NssLoaded> = OnceCell::new(); + +fn already_initialized() -> bool { + unsafe { nss::NSS_IsInitialized() != 0 } +} + +fn version_check() { + let min_ver = CString::new(MINIMUM_NSS_VERSION).unwrap(); + assert_ne!( + unsafe { nss::NSS_VersionCheck(min_ver.as_ptr()) }, + 0, + "Minimum NSS version of {} not supported", + MINIMUM_NSS_VERSION, + ); +} + +/// Initialize NSS. This only executes the initialization routines once, so if there is any chance that +pub fn init() { + // Set time zero. + time::init(); + INITIALIZED.get_or_init(|| { + unsafe { + version_check(); + if already_initialized() { + return NssLoaded::External; + } + + secstatus_to_res(nss::NSS_NoDB_Init(null())).expect("NSS_NoDB_Init failed"); + secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); + + NssLoaded::NoDb + } + }); +} + +/// This enables SSLTRACE by calling a simple, harmless function to trigger its +/// side effects. SSLTRACE is not enabled in NSS until a socket is made or +/// global options are accessed. Reading an option is the least impact approach. +/// This allows us to use SSLTRACE in all of our unit tests and programs. +#[cfg(debug_assertions)] +fn enable_ssl_trace() { + let opt = ssl::Opt::Locking.as_int(); + let mut v: ::std::os::raw::c_int = 0; + secstatus_to_res(unsafe { ssl::SSL_OptionGetDefault(opt, &mut v) }) + .expect("SSL_OptionGetDefault failed"); +} + +/// Initialize with a database. +/// # Panics +/// If NSS cannot be initialized. +pub fn init_db<P: Into<PathBuf>>(dir: P) { + time::init(); + INITIALIZED.get_or_init(|| { + unsafe { + version_check(); + if already_initialized() { + return NssLoaded::External; + } + + let path = dir.into(); + assert!(path.is_dir()); + let pathstr = path.to_str().expect("path converts to string").to_string(); + let dircstr = CString::new(pathstr).unwrap(); + let empty = CString::new("").unwrap(); + secstatus_to_res(nss::NSS_Initialize( + dircstr.as_ptr(), + empty.as_ptr(), + empty.as_ptr(), + nss::SECMOD_DB.as_ptr().cast(), + nss::NSS_INIT_READONLY, + )) + .expect("NSS_Initialize failed"); + + secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); + secstatus_to_res(ssl::SSL_ConfigServerSessionIDCache( + 1024, + 0, + 0, + dircstr.as_ptr(), + )) + .expect("SSL_ConfigServerSessionIDCache failed"); + + #[cfg(debug_assertions)] + enable_ssl_trace(); + + NssLoaded::Db(path.into_boxed_path()) + } + }); +} + +/// # Panics +/// If NSS isn't initialized. +pub fn assert_initialized() { + INITIALIZED.get().expect("NSS not initialized with init or init_db"); +} diff --git a/third_party/rust/nss-gk-api/src/p11.rs b/third_party/rust/nss-gk-api/src/p11.rs new file mode 100644 index 0000000000..5eb6ea040c --- /dev/null +++ b/third_party/rust/nss-gk-api/src/p11.rs @@ -0,0 +1,194 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use crate::err::{secstatus_to_res, Error, Res}; +use crate::util::SECItemMut; + +use pkcs11_bindings::CKA_VALUE; + +use std::convert::TryFrom; +use std::os::raw::{c_int, c_uint}; + +#[must_use] +pub fn hex_with_len(buf: impl AsRef<[u8]>) -> String { + use std::fmt::Write; + let buf = buf.as_ref(); + let mut ret = String::with_capacity(10 + buf.len() * 2); + write!(&mut ret, "[{}]: ", buf.len()).unwrap(); + for b in buf { + write!(&mut ret, "{:02x}", b).unwrap(); + } + ret +} + +#[allow(clippy::upper_case_acronyms)] +#[allow(clippy::unreadable_literal)] +#[allow(unknown_lints, clippy::borrow_as_ptr)] +mod nss_p11 { + use crate::prtypes::*; + use crate::nss_prelude::*; + include!(concat!(env!("OUT_DIR"), "/nss_p11.rs")); +} + +use crate::prtypes::*; +pub use nss_p11::*; + +// Shadow these bindgen created values to correct their type. +pub const SHA256_LENGTH: usize = nss_p11::SHA256_LENGTH as usize; +pub const AES_BLOCK_SIZE: usize = nss_p11::AES_BLOCK_SIZE as usize; + +scoped_ptr!(Certificate, CERTCertificate, CERT_DestroyCertificate); +scoped_ptr!(CertList, CERTCertList, CERT_DestroyCertList); + +scoped_ptr!(SubjectPublicKeyInfo, CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo); + +scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey); +impl_clone!(PublicKey, SECKEY_CopyPublicKey); + +impl PublicKey { + /// Get the HPKE serialization of the public key. + /// + /// # Errors + /// When the key cannot be exported, which can be because the type is not supported. + /// # Panics + /// When keys are too large to fit in `c_uint/usize`. So only on programming error. + pub fn key_data(&self) -> Res<Vec<u8>> { + let mut buf = vec![0; 100]; + let mut len: c_uint = 0; + secstatus_to_res(unsafe { + PK11_HPKE_Serialize( + **self, + buf.as_mut_ptr(), + &mut len, + c_uint::try_from(buf.len()).unwrap(), + ) + })?; + buf.truncate(usize::try_from(len).unwrap()); + Ok(buf) + } +} + +impl std::fmt::Debug for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.key_data() { + write!(f, "PublicKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque PublicKey") + } + } +} + +scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey); +impl_clone!(PrivateKey, SECKEY_CopyPrivateKey); + +impl PrivateKey { + /// Get the bits of the private key. + /// + /// # Errors + /// When the key cannot be exported, which can be because the type is not supported + /// or because the key data cannot be extracted from the PKCS#11 module. + /// # Panics + /// When the values are too large to fit. So never. + pub fn key_data(&self) -> Res<Vec<u8>> { + let mut key_item = SECItemMut::make_empty(); + secstatus_to_res(unsafe { + PK11_ReadRawAttribute( + PK11ObjectType::PK11_TypePrivKey, + (**self).cast(), + CKA_VALUE, + key_item.as_mut(), + ) + })?; + Ok(key_item.as_slice().to_owned()) + } +} + +impl std::fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.key_data() { + write!(f, "PrivateKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque PrivateKey") + } + } +} + +scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot); + +impl Slot { + pub fn internal() -> Res<Self> { + unsafe { Slot::from_ptr(PK11_GetInternalSlot()) } + } +} + +// Note: PK11SymKey is internally reference counted +scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey); +impl_clone!(SymKey, PK11_ReferenceSymKey); + +impl SymKey { + /// You really don't want to use this. + /// + /// # Errors + /// Internal errors in case of failures in NSS. + pub fn as_bytes(&self) -> Res<&[u8]> { + secstatus_to_res(unsafe { PK11_ExtractKeyValue(**self) })?; + + let key_item = unsafe { PK11_GetKeyData(**self) }; + // This is accessing a value attached to the key, so we can treat this as a borrow. + match unsafe { key_item.as_mut() } { + None => Err(Error::InternalError), + Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }), + } + } +} + +impl std::fmt::Debug for SymKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.as_bytes() { + write!(f, "SymKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque SymKey") + } + } +} + +unsafe fn destroy_pk11_context(ctxt: *mut PK11Context) { + PK11_DestroyContext(ctxt, PRBool::from(true)); +} +scoped_ptr!(Context, PK11Context, destroy_pk11_context); + +/// Generate a randomized buffer. +/// # Panics +/// When `size` is too large or NSS fails. +#[must_use] +pub fn random(size: usize) -> Vec<u8> { + let mut buf = vec![0; size]; + secstatus_to_res(unsafe { + PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap()) + }) + .unwrap(); + buf +} + +impl_into_result!(SECOidData); + +#[cfg(test)] +mod test { + use super::random; + use test_fixture::fixture_init; + + #[test] + fn randomness() { + fixture_init(); + // If this ever fails, there is either a bug, or it's time to buy a lottery ticket. + assert_ne!(random(16), random(16)); + } +} diff --git a/third_party/rust/nss-gk-api/src/prio.rs b/third_party/rust/nss-gk-api/src/prio.rs new file mode 100644 index 0000000000..8468e08eb3 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/prio.rs @@ -0,0 +1,21 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] +#![allow( + dead_code, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::empty_enum, + clippy::too_many_lines, + unknown_lints, + clippy::borrow_as_ptr +)] + +use crate::prtypes::*; + +include!(concat!(env!("OUT_DIR"), "/nspr_io.rs")); diff --git a/third_party/rust/nss-gk-api/src/prtypes.rs b/third_party/rust/nss-gk-api/src/prtypes.rs new file mode 100644 index 0000000000..2416ec5ddc --- /dev/null +++ b/third_party/rust/nss-gk-api/src/prtypes.rs @@ -0,0 +1,22 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] +#![allow( + dead_code, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::empty_enum, + clippy::too_many_lines, + unknown_lints, + clippy::borrow_as_ptr +)] + +pub use PRStatus_PR_FAILURE as PR_FAILURE; +pub use PRStatus_PR_SUCCESS as PR_SUCCESS; + +include!(concat!(env!("OUT_DIR"), "/nspr_types.rs")); diff --git a/third_party/rust/nss-gk-api/src/ssl.rs b/third_party/rust/nss-gk-api/src/ssl.rs new file mode 100644 index 0000000000..1d74154bc7 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/ssl.rs @@ -0,0 +1,157 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow( + dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::too_many_lines, + clippy::upper_case_acronyms, + unknown_lints, + clippy::borrow_as_ptr +)] + +use crate::err::{secstatus_to_res, Res}; +use crate::nss_prelude::*; +use crate::prio::PRFileDesc; + +mod nss_ssl { + use crate::err::PRErrorCode; + use crate::nss_prelude::*; + use crate::p11::CERTCertList; + use crate::prio::{ + PRFileDesc, + PRFileInfo, + PRFileInfo64, + PRIOVec, + }; + + include!(concat!(env!("OUT_DIR"), "/nss_ssl.rs")); +} +pub use nss_ssl::*; + +mod SSLOption { + include!(concat!(env!("OUT_DIR"), "/nss_sslopt.rs")); +} + +#[derive(Debug, Copy, Clone)] +pub enum Opt { + Locking, + Tickets, + OcspStapling, + Alpn, + ExtendedMasterSecret, + SignedCertificateTimestamps, + EarlyData, + RecordSizeLimit, + Tls13CompatMode, + HelloDowngradeCheck, + SuppressEndOfEarlyData, +} + +impl Opt { + // Cast is safe here because SSLOptions are within the i32 range + #[allow(clippy::cast_possible_wrap)] + pub(crate) fn as_int(self) -> PRInt32 { + let i = match self { + Self::Locking => SSLOption::SSL_NO_LOCKS, + Self::Tickets => SSLOption::SSL_ENABLE_SESSION_TICKETS, + Self::OcspStapling => SSLOption::SSL_ENABLE_OCSP_STAPLING, + Self::Alpn => SSLOption::SSL_ENABLE_ALPN, + Self::ExtendedMasterSecret => SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET, + Self::SignedCertificateTimestamps => SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, + Self::EarlyData => SSLOption::SSL_ENABLE_0RTT_DATA, + Self::RecordSizeLimit => SSLOption::SSL_RECORD_SIZE_LIMIT, + Self::Tls13CompatMode => SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE, + Self::HelloDowngradeCheck => SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK, + Self::SuppressEndOfEarlyData => SSLOption::SSL_SUPPRESS_END_OF_EARLY_DATA, + }; + i as PRInt32 + } + + // Some options are backwards, like SSL_NO_LOCKS, so use this to manage that. + fn map_enabled(self, enabled: bool) -> PRIntn { + let v = match self { + Self::Locking => !enabled, + _ => enabled, + }; + PRIntn::from(v) + } + + pub(crate) fn set(self, fd: *mut PRFileDesc, value: bool) -> Res<()> { + secstatus_to_res(unsafe { SSL_OptionSet(fd, self.as_int(), self.map_enabled(value)) }) + } +} + +/* + * TODO: these will be moved to a dedicated module + * +experimental_api!(SSL_GetCurrentEpoch( + fd: *mut PRFileDesc, + read_epoch: *mut u16, + write_epoch: *mut u16, +)); +experimental_api!(SSL_HelloRetryRequestCallback( + fd: *mut PRFileDesc, + cb: SSLHelloRetryRequestCallback, + arg: *mut c_void, +)); +experimental_api!(SSL_RecordLayerWriteCallback( + fd: *mut PRFileDesc, + cb: SSLRecordWriteCallback, + arg: *mut c_void, +)); +experimental_api!(SSL_RecordLayerData( + fd: *mut PRFileDesc, + epoch: Epoch, + ct: SSLContentType::Type, + data: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SendSessionTicket( + fd: *mut PRFileDesc, + extra: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SetMaxEarlyDataSize(fd: *mut PRFileDesc, size: u32)); +experimental_api!(SSL_SetResumptionToken( + fd: *mut PRFileDesc, + token: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SetResumptionTokenCallback( + fd: *mut PRFileDesc, + cb: SSLResumptionTokenCallback, + arg: *mut c_void, +)); + +experimental_api!(SSL_GetResumptionTokenInfo( + token: *const u8, + token_len: c_uint, + info: *mut SSLResumptionTokenInfo, + len: c_uint, +)); + +experimental_api!(SSL_DestroyResumptionTokenInfo( + info: *mut SSLResumptionTokenInfo, +)); +*/ + +#[cfg(test)] +mod tests { + use super::{SSL_GetNumImplementedCiphers, SSL_NumImplementedCiphers}; + + #[test] + fn num_ciphers() { + assert!(unsafe { SSL_NumImplementedCiphers } > 0); + assert!(unsafe { SSL_GetNumImplementedCiphers() } > 0); + assert_eq!(unsafe { SSL_NumImplementedCiphers }, unsafe { + SSL_GetNumImplementedCiphers() + }); + } +} diff --git a/third_party/rust/nss-gk-api/src/time.rs b/third_party/rust/nss-gk-api/src/time.rs new file mode 100644 index 0000000000..1abc276dda --- /dev/null +++ b/third_party/rust/nss-gk-api/src/time.rs @@ -0,0 +1,255 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] + +use crate::nss_prelude::PRInt64; +use crate::err::{Error, Res}; +//use crate::prio::PRFileDesc; +//use crate::ssl::SSLTimeFunc; + +use once_cell::sync::OnceCell; +//use std::boxed::Box; +use std::convert::{TryFrom, TryInto}; +use std::ops::Deref; +//use std::os::raw::c_void; +//use std::pin::Pin; +use std::time::{Duration, Instant}; + +include!(concat!(env!("OUT_DIR"), "/nspr_time.rs")); + +/* TODO move to exp module +experimental_api!(SSL_SetTimeFunc( + fd: *mut PRFileDesc, + cb: SSLTimeFunc, + arg: *mut c_void, +)); +*/ + +/// This struct holds the zero time used for converting between `Instant` and `PRTime`. +#[derive(Debug)] +struct TimeZero { + instant: Instant, + prtime: PRTime, +} + +impl TimeZero { + /// This function sets a baseline from an instance of `Instant`. + /// This allows for the possibility that code that uses these APIs will create + /// instances of `Instant` before any of this code is run. If `Instant`s older than + /// `BASE_TIME` are used with these conversion functions, they will fail. + /// To avoid that, we make sure that this sets the base time using the first value + /// it sees if it is in the past. If it is not, then use `Instant::now()` instead. + pub fn baseline(t: Instant) -> Self { + let now = Instant::now(); + let prnow = unsafe { PR_Now() }; + + if now <= t { + // `t` is in the future, just use `now`. + Self { + instant: now, + prtime: prnow, + } + } else { + let elapsed = Interval::from(now.duration_since(now)); + // An error from these unwrap functions would require + // ridiculously long application running time. + let prelapsed: PRTime = elapsed.try_into().unwrap(); + Self { + instant: t, + prtime: prnow.checked_sub(prelapsed).unwrap(), + } + } + } +} + +static BASE_TIME: OnceCell<TimeZero> = OnceCell::new(); + +fn get_base() -> &'static TimeZero { + BASE_TIME.get_or_init(|| TimeZero { + instant: Instant::now(), + prtime: unsafe { PR_Now() }, + }) +} + +pub fn init() { + let _ = get_base(); +} + +/// Time wraps Instant and provides conversion functions into `PRTime`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Time { + t: Instant, +} + +impl Deref for Time { + type Target = Instant; + fn deref(&self) -> &Self::Target { + &self.t + } +} + +impl From<Instant> for Time { + /// Convert from an Instant into a Time. + fn from(t: Instant) -> Self { + // Call `TimeZero::baseline(t)` so that time zero can be set. + BASE_TIME.get_or_init(|| TimeZero::baseline(t)); + Self { t } + } +} + +impl TryFrom<PRTime> for Time { + type Error = Error; + fn try_from(prtime: PRTime) -> Res<Self> { + let base = get_base(); + if let Some(delta) = prtime.checked_sub(base.prtime) { + let d = Duration::from_micros(delta.try_into()?); + base.instant + .checked_add(d) + .map_or(Err(Error::TimeTravelError), |t| Ok(Self { t })) + } else { + Err(Error::TimeTravelError) + } + } +} + +impl TryInto<PRTime> for Time { + type Error = Error; + fn try_into(self) -> Res<PRTime> { + let base = get_base(); + let delta = self + .t + .checked_duration_since(base.instant) + .ok_or(Error::TimeTravelError)?; + if let Ok(d) = PRTime::try_from(delta.as_micros()) { + d.checked_add(base.prtime).ok_or(Error::TimeTravelError) + } else { + Err(Error::TimeTravelError) + } + } +} + +impl From<Time> for Instant { + #[must_use] + fn from(t: Time) -> Self { + t.t + } +} + +/// Interval wraps Duration and provides conversion functions into `PRTime`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Interval { + d: Duration, +} + +impl Deref for Interval { + type Target = Duration; + fn deref(&self) -> &Self::Target { + &self.d + } +} + +impl TryFrom<PRTime> for Interval { + type Error = Error; + fn try_from(prtime: PRTime) -> Res<Self> { + Ok(Self { + d: Duration::from_micros(u64::try_from(prtime)?), + }) + } +} + +impl From<Duration> for Interval { + fn from(d: Duration) -> Self { + Self { d } + } +} + +impl TryInto<PRTime> for Interval { + type Error = Error; + fn try_into(self) -> Res<PRTime> { + Ok(PRTime::try_from(self.d.as_micros())?) + } +} + +/* TODO: restore, experimental only. +/// `TimeHolder` maintains a `PRTime` value in a form that is accessible to the TLS stack. +#[derive(Debug)] +pub struct TimeHolder { + t: Pin<Box<PRTime>>, +} + +impl TimeHolder { + unsafe extern "C" fn time_func(arg: *mut c_void) -> PRTime { + let p = arg as *const PRTime; + *p.as_ref().unwrap() + } + + pub fn bind(&mut self, fd: *mut PRFileDesc) -> Res<()> { + unsafe { SSL_SetTimeFunc(fd, Some(Self::time_func), &mut *self.t as *mut _ as *mut c_void) } + } + + pub fn set(&mut self, t: Instant) -> Res<()> { + *self.t = Time::from(t).try_into()?; + Ok(()) + } +} + +impl Default for TimeHolder { + fn default() -> Self { + TimeHolder { t: Box::pin(0) } + } +} +*/ + +#[cfg(test)] +mod test { + use super::{get_base, init, Interval, PRTime, Time}; + use crate::err::Res; + use std::convert::{TryFrom, TryInto}; + use std::time::{Duration, Instant}; + + #[test] + fn convert_stable() { + init(); + let now = Time::from(Instant::now()); + let pr: PRTime = now.try_into().expect("convert to PRTime with truncation"); + let t2 = Time::try_from(pr).expect("convert to Instant"); + let pr2: PRTime = t2.try_into().expect("convert to PRTime again"); + assert_eq!(pr, pr2); + let t3 = Time::try_from(pr2).expect("convert to Instant again"); + assert_eq!(t2, t3); + } + + #[test] + fn past_time() { + init(); + let base = get_base(); + assert!(Time::try_from(base.prtime - 1).is_err()); + } + + #[test] + fn negative_time() { + init(); + assert!(Time::try_from(-1).is_err()); + } + + #[test] + fn negative_interval() { + init(); + assert!(Interval::try_from(-1).is_err()); + } + + #[test] + // We allow replace_consts here because + // std::u64::max_value() isn't available + // in all of our targets + fn overflow_interval() { + init(); + let interval = Interval::from(Duration::from_micros(u64::max_value())); + let res: Res<PRTime> = interval.try_into(); + assert!(res.is_err()); + } +} diff --git a/third_party/rust/nss-gk-api/src/util.rs b/third_party/rust/nss-gk-api/src/util.rs new file mode 100644 index 0000000000..f06542eb45 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/util.rs @@ -0,0 +1,246 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::convert::TryFrom; +use std::marker::PhantomData; +use std::mem; +use std::os::raw::c_uint; +use std::ptr::null_mut; + +use crate::prtypes::*; +use crate::nss_prelude::*; + +/// Implement a smart pointer for NSS objects. +/// +/// Most of the time the pointer is like a `Box`, but there are exceptions (e.g. +/// PK11SymKey is internally reference counted so its pointer is like an `Arc`.) +/// +/// Named "scoped" because that is what NSS calls its `unique_ptr` typedefs. +macro_rules! scoped_ptr { + ($name:ident, $target:ty, $dtor:path) => { + pub struct $name { + ptr: *mut $target, + } + + impl $name { + /// Create a new instance of `$name` from a pointer. + /// + /// # Errors + /// When passed a null pointer generates an error. + pub unsafe fn from_ptr(raw: *mut $target) -> Result<Self, $crate::err::Error> { + let ptr = $crate::err::into_result(raw)?; + Ok(Self { ptr }) + } + } + + impl $crate::err::IntoResult for *mut $target { + type Ok = $name; + + unsafe fn into_result(self) -> Result<Self::Ok, $crate::err::Error> { + $name::from_ptr(self) + } + } + + impl std::ops::Deref for $name { + type Target = *mut $target; + #[must_use] + fn deref(&self) -> &*mut $target { + &self.ptr + } + } + + // Original implements DerefMut, but is that really a good idea? + + impl Drop for $name { + fn drop(&mut self) { + unsafe { $dtor(self.ptr) }; + } + } + } +} + +macro_rules! impl_clone { + ($name:ty, $nss_fn:path) => { + impl Clone for $name { + #[must_use] + fn clone(&self) -> Self { + let ptr = unsafe { $nss_fn(self.ptr) }; + assert!(!ptr.is_null()); + Self { ptr } + } + } + } +} + +impl SECItem { + /// Return contents as a slice. + /// + /// Unsafe due to calling from_raw_parts, or if 'a outlives &self. This + /// unsafety is encapsulated by the `as_slice` method of `SECItemBorrowed` + /// and `SECItemMut`. + /// + /// Note that safe code can construct a SECItem pointing to anything. The + /// same is not true of the safe wrappers `SECItemMut` and `SECItemBorrowed` + /// because their inner SECItem is private. + pub unsafe fn as_slice<'a>(&self) -> &'a [u8] { + // Sanity check the type, as some types don't count bytes in `Item::len`. + assert_eq!(self.type_, SECItemType::siBuffer); + // Note: `from_raw_parts` requires non-null `data` even for zero-length + // slices. + if self.len != 0 { + std::slice::from_raw_parts(self.data, usize::try_from(self.len).unwrap()) + } else { + &[] + } + } +} + +/// An owned SECItem. +/// +/// The SECItem structure is allocated by Rust. The buffer referenced by the +/// SECItem is allocated by NSS. `SECITEM_FreeItem` will be called to free the +/// buffer when the SECItemMut is dropped. +/// +/// This is used with NSS functions that return a variable amount of data. +#[repr(transparent)] +pub struct SECItemMut { + inner: SECItem, +} + +impl<'a> Drop for SECItemMut { + #[allow(unused_must_use)] + fn drop(&mut self) { + // FreeItem unconditionally frees the buffer referenced by the SECItem. + // If the second argument is true, it also frees the SECItem itself, + // which we don't want to do, because rust owns that memory. + unsafe { SECITEM_FreeItem(&mut self.inner, PRBool::from(false)) }; + } +} + +impl AsRef<SECItem> for SECItemMut { + fn as_ref(&self) -> &SECItem { + &self.inner + } +} + +impl AsMut<SECItem> for SECItemMut { + fn as_mut(&mut self) -> &mut SECItem { + &mut self.inner + } +} + +impl SECItemMut { + /// Return contents as a slice. + pub fn as_slice(&self) -> &[u8] { + unsafe { self.inner.as_slice() } + } + + /// Make an empty `SECItemMut` for passing as a mutable `*SECItem` argument. + pub fn make_empty() -> SECItemMut { + SECItemMut { + inner: SECItem { + type_: SECItemType::siBuffer, + data: null_mut(), + len: 0, + } + } + } +} + +/// A borrowed SECItem. +/// +/// The SECItem structure is allocated by Rust. The buffer referenced by the +/// SECItem may be allocated either by Rust or NSS. The SECItem does not own the +/// buffer and will not free it when dropped. +/// +/// This is usually used to pass a reference to some borrowed rust memory to +/// NSS. It is occasionally used to accept non-owned output data from NSS. +#[repr(transparent)] +pub struct SECItemBorrowed<'a> { + inner: SECItem, + phantom_data: PhantomData<&'a u8>, +} + +impl<'a> AsRef<SECItem> for SECItemBorrowed<'a> { + fn as_ref(&self) -> &SECItem { + &self.inner + } +} + +impl<'a> AsMut<SECItem> for SECItemBorrowed<'a> { + /// Get a mutable reference to the underlying SECItem struct. + /// + /// Note that even if the SECItem struct is mutable, the buffer it + /// references may not be. Take care not to pass the mutable + /// SECItem to NSS routines that will violate mutability rules. + // + // TODO: Should we make the danger more obvious, by using a non-trait method + // with "unsafe" in the name, or an unsafe method? + fn as_mut(&mut self) -> &mut SECItem { + &mut self.inner + } +} + +impl<'a> SECItemBorrowed<'a> { + /// Return contents as a slice. + pub fn as_slice(&self) -> &'a [u8] { + unsafe { self.inner.as_slice() } + } + + /// Create an empty `SECItemBorrowed`. + /// + /// This can be used (1) to pass an empty item as an argument, and (2) as an + /// output parameter when NSS returns a pointer to NSS-owned memory that + /// should not be freed when the SECItem is dropped. If the memory should + /// be freed when the SECItem is dropped, use SECItemMut. + /// + /// It is safe to let the caller specify any lifetime here because no + /// borrowing is actually taking place. However, if the pointer in the + /// returned item is modified, care must be taken that the specified + /// lifetime accurately reflects the data referenced by the pointer. + pub fn make_empty() -> SECItemBorrowed<'a> { + SECItemBorrowed { + inner: SECItem { + type_: SECItemType::siBuffer, + data: null_mut(), + len: 0, + }, + phantom_data: PhantomData, + } + } + + /// Create a `SECItemBorrowed` wrapping a slice. + /// + /// Creating this object is technically safe, but using it is extremely dangerous. + /// Minimally, it can only be passed as a `const SECItem*` argument to functions, + /// or those that treat their argument as `const`. + pub fn wrap(buf: &'a [u8]) -> SECItemBorrowed<'a> { + SECItemBorrowed { + inner: SECItem { + type_: SECItemType::siBuffer, + data: buf.as_ptr() as *mut u8, + len: c_uint::try_from(buf.len()).unwrap(), + }, + phantom_data: PhantomData, + } + } + + /// Create a `SECItemBorrowed` wrapping a struct. + /// + /// Creating this object is technically safe, but using it is extremely dangerous. + /// Minimally, it can only be passed as a `const SECItem*` argument to functions, + /// or those that treat their argument as `const`. + pub fn wrap_struct<T>(v: &'a T) -> SECItemBorrowed<'a> { + SECItemBorrowed { + inner: SECItem { + type_: SECItemType::siBuffer, + data: (v as *const T as *mut T).cast(), + len: c_uint::try_from(mem::size_of::<T>()).unwrap(), + }, + phantom_data: PhantomData, + } + } +} |