Summary

This RFC introduces two procedural macros, SecretDebug and SecretDisplay, to derive the traits Debug and Display for secret material types in order to avoid leaking their internals.

Motivation

Secret materials, private keys for example, are types that are expected to remain private and be shared/leaked under no circumstances. However, these types may be wrapped in other higher level types, a wallet for example, that may want to implement Debug and/or Display. Not implementing these traits on the secret types would then not permit it. At the same time, secret materials should not be exposed by logging them. The consensus is then to implement Debug and Display but to not actually log the inner secret. To make things easier, two procedural macros are proposed to automatically derive such implementations.

Detailed design

Crate

Procedural macros need to be defined in a different type of crate so a new dedicated crate has to be created with the following in its Cargo.toml:

[lib]
proc-macro = true

Derivation

This feature is expected to be used by deriving SecretDebug and/or SecretDisplay.


#![allow(unused)]
fn main() {
#[derive(SecretDebug, SecretDisplay)]
struct PrivateKey {
    ...
}
}

Output

When an attempt to display or debug a secret type is made, a generic message should be displayed instead. This RFC proposes <Omitted secret>.

Implementation

This feature makes use of the syn and quote crates to create derivation macros.

Implementation of SecretDebug:


#![allow(unused)]
fn main() {
#[proc_macro_derive(SecretDebug)]
pub fn derive_secret_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);
    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;
    // Get the different implementation elements from the input.
    let (impl_generics, ty_generics, _) = input.generics.split_for_impl();

    let expanded = quote! {
        // The generated implementation.
        impl #impl_generics std::fmt::Debug for #name #ty_generics {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "<Omitted secret>")
            }
        }
    };

    // Hand the output tokens back to the compiler.
    expanded.into()
}
}

Implementation of SecretDisplay is similar.

Drawbacks

User may actually really want to debug/display their secret types. With this feature in place, they would have to rely on APIs providing getters to the inner secret and/or serialization primitives.

Rationale and alternatives

By providing macros, we offer a consistent way to approach the problem and potentially reduce the risk of users leaking their secret materials by accident.

Unresolved questions

No questions at the moment.