Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
rnp-rs
Idiomatic Rust binding to the RNP OpenPGP (RFC 9580) C library.
RNP is the C++ OpenPGP implementation that powers Mozilla Thunderbird.
This crate provides a thin, safe Rust wrapper over its public C FFI
(declared in include/rnp/rnp.h).
Status
The crate wraps ~250 of librnp's ~309 public functions across all major
OpenPGP concerns: signing, verification, encryption, decryption, key
generation, keyring management, ASCII armor, packet dumps, security
profile, and feature queries. PQC and crypto-refresh (RFC 9580 v6)
support is feature-gated. MSRV is Rust 1.88 (let-chains in
build.rs).
Cargo features
| Feature | Default | Description |
|---|---|---|
vendored |
off | Statically link the bundled librnp.a + libsexpp.a + libjson-c.a from prebuilt/<target>/. All you need is Botan 3 at runtime. |
pqc |
off | Pass -DRNP_EXPERIMENTAL_PQC to bindgen so PQC algorithm constants and Encryptor::prefer_pqc_enc_subkey are exposed. Requires librnp built with ENABLE_PQC=ON. |
crypto-refresh |
off | Pass -DRNP_EXPERIMENTAL_CRYPTO_REFRESH to bindgen so v6 keys, crypto-refresh algorithm names, and v6 PKESK/SKESK are exposed. |
logging |
off | Gate Context::set_log_fd / set_log_file for directing librnp's diagnostic output. |
Quick start
use ;
let ctx = new?;
let key = new
.bits
.userid
.hash
.add_usage
.add_usage
.build?;
let message = b"hello, world";
let signed = sign?;
let result = verify?;
assert!;
# Ok::
See examples/ for end-to-end demos of sign/verify, encrypt/decrypt,
key generation, and multi-signer signatures.
Feature coverage
Each subsection below lists the Rust surface and the librnp C functions it wraps.
Context, password provider, and key provider
Context::new() is the entry point — it owns an rnp_ffi_t and ties
the lifetimes of every derived handle to it. A Context accepts a
PasswordProvider trait object (used by rnp_request_password and any
operation that needs to unlock a key) and a KeyProvider trait object
(used by rnp_ffi_set_key_provider for verify/decrypt key lookup).
use ;
;
let mut ctx = new?;
ctx.set_password_provider;
| Rust | C function |
|---|---|
Context::new / Drop |
rnp_ffi_create / rnp_ffi_destroy |
Context::set_password_provider |
rnp_ffi_set_password_provider |
Context::set_key_provider |
rnp_ffi_set_key_provider |
Context::borrow_ffi |
(re-entrant thunk plumbing) |
Context::set_log_file / set_log_fd |
rnp_ffi_set_log_fd / set_log_file |
Context::load_keys / save_keys |
rnp_load_keys / rnp_save_keys |
Context::unload_keys |
rnp_unload_keys |
Context::import_keys / import_signatures |
rnp_import_keys / rnp_import_signatures |
Context::find_key |
rnp_locate_key |
Context::default_homedir / detect_homedir |
rnp_get_default_homedir / detect_* |
Key generation
KeyBuilder is the primary surface; SubkeyBuilder builds subkeys off
a primary in one composite call. Both wrap rnp_op_generate_*.
use ;
let ctx = new?;
let key = new
.bits
.userid
.hash
.add_usage
.add_usage
.add_pref_hash
.clear_pref_hash // reset and add a fresh set
.add_pref_hash
.add_subkey
.build?;
| Rust | C function |
|---|---|
KeyBuilder::new / build |
rnp_op_generate_create / execute |
KeyBuilder::bits / hash / dsa_qbits |
rnp_op_generate_set_{bits,hash,dsa_qbits} |
KeyBuilder::curve |
rnp_op_generate_set_curve |
KeyBuilder::userid |
rnp_op_generate_set_userid |
KeyBuilder::expiration |
rnp_op_generate_set_expiration |
KeyBuilder::add_usage / clear_usage |
rnp_op_generate_{add,clear}_usage |
KeyBuilder::add_pref_hash / clear_pref_hash |
rnp_op_generate_{add,clear}_pref_hash |
KeyBuilder::add_pref_cipher / clear_pref_cipher |
rnp_op_generate_{add,clear}_pref_cipher |
KeyBuilder::add_pref_compression / clear_pref_compression |
rnp_op_generate_{add,clear}_pref_compression |
KeyBuilder::pref_keyserver |
rnp_op_generate_set_pref_keyserver |
KeyBuilder::protection |
rnp_op_generate_set_protection_* |
KeyBuilder::request_password |
rnp_op_generate_set_request_password |
KeyBuilder::v6 (crypto-refresh) |
rnp_op_generate_set_v6_key |
SubkeyBuilder::build_with_ffi |
rnp_op_generate_subkey_create |
generate_key_json |
rnp_generate_key_json |
Algorithm coverage: RSA, DSA, ElGamal, ECDSA/ECDH (NIST/Brainpool
curves), EdDSA, X25519, SM2, plus the PQC composites (ML-KEM,
ML-DSA, SLH-DSA) when built with the pqc feature.
Key inspection
Key<'ctx> is a borrowed handle. All getters return Result<T> so a
deleted/expired/revoked underlying key surfaces as an error rather than
a panic.
# use *;
# let ctx = new?;
# let key = new.bits
# .userid.build?;
println!;
println!;
println!;
println!;
for uid in key.uids?
# Ok::
| Rust | C function |
|---|---|
Key::alg / bits / curve / version |
rnp_key_get_alg / get_bits / get_curve / get_version |
Key::keyid / fingerprint / grip |
rnp_key_get_keyid / get_fprint / get_grip |
Key::primary_fprint / primary_grip |
rnp_key_get_primary_fprint / get_primary_grip |
Key::creation / expiration |
rnp_key_get_creation / get_expiration |
Key::is_{locked,protected,primary,sub,valid,revoked,compromised,expired,superseded,retired} |
matching rnp_key_is_* |
Key::have_public / have_secret |
rnp_key_have_public / rnp_key_have_secret |
Key::protection_{mode,type,cipher,hash,iterations} |
rnp_key_get_protection_* |
Key::valid_till / valid_till_64 |
rnp_key_valid_till / rnp_key_valid_till64 |
Key::uid_count / uids |
rnp_key_get_uid_count / get_uid_handle_at |
Key::subkey_count / subkeys / subkey_at |
rnp_key_get_subkey_count / get_subkey_at |
Key::signature_count / signatures |
rnp_key_get_signature_count / get_signature_at |
Key::default_key_for(usage) |
rnp_key_default_key_for |
UID, Subkey, Signature handles
Uid<'key>, Subkey<'key>, and Signature<'key> borrow the parent
Key. They each carry a Drop that calls the matching
rnp_*_handle_destroy.
# use *;
# let ctx = new?;
# let key = new.bits
# .userid.build?;
let uid = key.uids?.into_iter.next.unwrap;
println!;
println!;
let sig = key.signatures?.into_iter.next.unwrap;
for sp in sig.subpackets?
# Ok::
| Rust | C function |
|---|---|
Uid::uid_type / data / data_string |
rnp_uid_get_type / rnp_uid_get_data |
Uid::is_primary / is_valid / is_revoked |
rnp_uid_is_primary / is_valid / is_revoked |
Uid::signature_count / signatures / signature_at |
rnp_uid_get_signature_count / get_signature_at |
Uid::revocation_signature |
rnp_uid_get_revocation_signature |
Uid::remove(&key) |
rnp_uid_remove |
Subkey (newtype over Key) |
rnp_key_get_subkey_at |
Signature::sig_type / sig_type_enum |
rnp_signature_get_type |
Signature::alg / hash / keyid / creation |
rnp_signature_get_* |
Signature::signer_key |
rnp_signature_get_signer |
Signature::subpacket_count / subpackets / find_subpacket |
rnp_signature_subpacket_{count,at,find} |
Signature::export / remove_from_key |
rnp_signature_export / rnp_signature_remove |
Subpacket accessors |
rnp_signature_subpacket_info / _data |
Key mutation
# use *;
# let ctx = new?;
# let key = new.bits
# .userid.build?;
key.add_uid?;
key.protect?;
key.lock?;
key.unlock?;
# Ok::
| Rust | C function |
|---|---|
Key::protect / unprotect / lock / unlock |
rnp_key_{protect,unprotect,lock,unlock} |
Key::add_uid |
rnp_key_add_uid |
Key::revoke |
rnp_key_revoke |
Key::set_expiration |
rnp_key_set_expiration |
Key::remove |
rnp_key_remove |
Key::remove_signatures |
rnp_key_remove_signatures |
Key::export_revocation |
rnp_key_export_revocation |
Key::export_autocrypt |
rnp_key_export_autocrypt |
Key::export |
rnp_key_export |
CertificationBuilder / DirectSignatureBuilder / RevocationSignatureBuilder |
rnp_key_{certification,direct_signature,revocation_signature}_create |
The three signature-creation builders share a single
SignatureSetterOps trait — adding a new subpacket setter adds one
method to the trait and a delegating impl per concrete builder.
Sign and verify
Three signing modes (inline / detached / cleartext) all flow through
one Signer builder, so new output formats are a Mode variant rather
than a new top-level function.
# use *;
# let ctx = new?;
# let k1 = new.bits.userid
# .add_usage.build?;
# let k2 = new.bits.userid
# .add_usage.build?;
let now = now
.duration_since.unwrap.as_secs as u32;
let sig = new
.add_signer
.add_signer_with_options
.armor
.build_to_memory?;
# Ok::
| Rust | C function |
|---|---|
sign / sign_detached / sign_cleartext |
rnp_op_sign_*_create + execute |
Signer::add_signer / _with_hash / _with_options |
rnp_op_sign_add_signature |
Signer::hash (default) / per-signer |
rnp_op_sign_set_hash / _signature_set_hash |
Signer::armor |
rnp_op_sign_set_armor |
add_signer_with_options (creation/expiration) |
rnp_op_sign_signature_set_creation_time / _set_expiration_time |
verify / verify_detached |
rnp_op_verify_*_create + execute |
VerifyResult::signature_count / signature_at |
rnp_op_verify_get_signature_count / _at |
VerifySignature::status / key / keyid |
rnp_signature_get_status etc. |
VerifyOp::flags |
rnp_op_verify_set_flags |
generate_revocation_certificate[_with] |
rnp_key_export_revocation |
Encrypt and decrypt
# use *;
# let ctx = new?;
# let key = new.bits
# .userid
# .add_usage.build?;
let mut ct = to_memory?;
new?
.add_recipient
.armor
.aead
.build?;
let ciphertext = ct.into_bytes?;
let result = new.build?;
assert_eq!;
# Ok::
| Rust | C function |
|---|---|
Encryptor::new / build |
rnp_op_encrypt_create + execute |
Encryptor::add_recipient / add_password / add_signature |
rnp_op_encrypt_add_recipient / _add_password / _add_signature |
Encryptor::{cipher,hash,compression,aead,aead_bits,armor,file_name,file_mtime,creation_time,expiration_time,flags} |
matching rnp_op_encrypt_set_* |
Encryptor::prefer_pqc_enc_subkey (pqc) |
rnp_op_encrypt_prefer_pqc_enc_subkey |
Encryptor::enable_pkesk_v6 / enable_skesk_v6 |
rnp_op_encrypt_set_* v6 PKESK/SKESK |
Decryptor::new / build |
rnp_decrypt (and verify-op plumbing) |
DecryptResult::{recipient,symenc,protection_info,file_info,format} |
rnp_decrypt_get_* |
Recipient / Symenc handles |
rnp_recipient_* / rnp_symenc_* |
ASCII armor, packet dumps, JSON serialization
# use *;
let armored = enarmor?;
let raw = dearmor?;
let json = dump_packets_to_json?;
let key_json = key.to_json?;
# Ok::
| Rust | C function |
|---|---|
enarmor / dearmor / dearmor_bytes / armor_bytes |
rnp_armor_* / rnp_dearmor_* |
guess_contents |
rnp_guess_contents |
dump_packets_to_output / _to_json / _bytes_to_json |
rnp_dump_packets_to_* |
Key::to_json / Signature::to_json |
rnp_key_to_json / rnp_signature_packet_to_json |
DumpFlags / JsonDumpFlags / JsonFlags |
RNP_DUMP_* / RNP_JSON_* bit constants |
Security profile and feature queries
# use *;
let ctx = new?;
let rule = new?;
ctx.add_security_rule?;
let iters = calculate_iterations?;
let features = supported_features?;
assert!;
# Ok::
| Rust | C function |
|---|---|
Context::add_security_rule / get_security_rule / remove_security_rule |
rnp_add_security_rule / rnp_get_security_rule / rnp_remove_security_rule |
SecurityLevel / SecurityFlags |
RNP_SECURITY_* constants |
supports_feature / supported_features |
rnp_supports_feature / rnp_supported_features |
calculate_iterations |
rnp_calculate_iterations |
request_password |
rnp_request_password (returns SecretString) |
Context::set_timestamp |
rnp_set_timestamp |
Secret-string hygiene
SecretString is a Vec<u8> wrapper that zeros its bytes on Drop
via rnp_buffer_clear. request_password returns one; user-supplied
passwords that you want to scrub should also live in a SecretString.
PQC and crypto-refresh
Gated on the pqc and crypto-refresh Cargo features. The crate
probes the linked librnp at runtime (librnp_supports_pqc) so a binary
that enables PQC still degrades cleanly on a non-PQC librnp.
Version and debug
println!;
let = decompose;
println!;
// Emit librnp diagnostic output to stderr (or a named file).
enable_debug;
disable_debug;
| Rust | C function |
|---|---|
version / decompose |
rnp_version / _major / _minor / _patch |
version_string / version_string_full |
rnp_version_string / _full |
version_for / version_commit_timestamp |
rnp_version_for / _commit_timestamp |
backend_string / backend_version |
rnp_backend_string / _version |
enable_debug / disable_debug |
rnp_enable_debug / rnp_disable_debug |
Linking
rnp-rs links against a system-installed librnp (-lrnp) by
default. Use --features vendored to skip that requirement entirely.
macOS (Homebrew)
Linux
# Fedora
# Debian/Ubuntu (if packaged)
Pointing at a source checkout
RNP_INCLUDE_DIR=/path/to/rnp/install/include \
RNP_LIB_DIR=/path/to/rnp/install/lib \
Vendored (no system librnp)
build.rs selects prebuilt/<target>/ matching your target triple and
statically links librnp.a + libsexpp.a + libjson-c.a. Botan 3 is
still dynamic at this stage — install via brew install botan
(macOS) or your distro's botan package. Pre-builts are produced by
the .github/workflows/prebuild.yml matrix for x86_64/aarch64 Linux
and macOS (native runners).
Architecture
Three layers, top to bottom:
src/ffi.rs— bindgen-generatedinclude!of$OUT_DIR/bindings.rs. The module is#![allow(non_camel_case_types, non_snake_case, dead_code)]because we mirror the C API verbatim.src/error.rs—Error(snafu),Result<T>, andcheck(rnp_result_t)that maps non-zero toError::Rnp { code, message }viarnp_result_to_string.- Safe wrappers — one directory module per OpenPGP concern:
context.rs,key/,uid.rs,subkey.rs,keygen/,signature.rs,encrypt/,verify/,armor.rs,dump.rs,security.rs,signature_handle/,key_signature_builder/,callbacks/,secret.rs,ops/(IO + string-conversion helpers).
Every handle type borrows its parent (via PhantomData) and owns a
Drop that calls the matching rnp_*_handle_destroy. Never add
unsafe impl Send — librnp handles are not thread-safe.
License
BSD-2-Clause, matching the upstream RNP project. See LICENSE.md.