Module pki_rpk

Source
Expand description

Types and traits related to (D)TLS with raw public keys and/or public key infrastructure support for CoAP.

In order to configure PKI and/or RPK support, the following general steps need to be followed:

  1. Create a key definition for the desired DTLS variant, see PkiKeyDef and RpkKeyDef for more detailed information.
  2. Create a PkiRpkContextBuilder using the provided key and (optionally) make some additional configuration changes (see the builder struct documentation).
  3. Call PkiRpkContextBuilder::build to create a PkiRpkContext.
  4. Provide the created context to CoapClientSession::connect_dtls (for client-side sessions) or CoapContext::set_pki_rpk_context (for server-side sessions).
  5. On servers, run CoapContext::add_endpoint_dtls to add a DTLS endpoint.

Note that PkiRpkContextBuilder uses generics with the marker structs Pki and Rpk to statically indicate the DTLS variant and NonCertVerifying and CertVerifying to indicate whether the peer certificate should be verified (PKI only, RPK will always use NonCertVerifying).

§Examples

Creating and connecting a client-side session with DTLS RPK configured:

use libcoap_rs::CoapContext;
use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
use libcoap_rs::session::{CoapClientSession, CoapSession};

// RPK is only supported if the key is provided as a byte array in memory, providing file paths
// directly is unsupported.
let client_private_key = Vec::from(include_str!("../../../resources/test-keys/client/client.key.pem"));
let client_public_key = Vec::from(include_str!("../../../resources/test-keys/client/client.pub.pem"));

// Create key definition.
let client_key_def = RpkKeyDef::with_pem_memory(client_public_key, client_private_key);

// Create the cryptography context. Note that you might have to explicitly specify that
// PKI certificate validation should not be performed, even though enabling it while passing a
// RPK key definition is impossible due to a lack of a constructor for
// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
// This is a type system limitation.
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(client_key_def);

let key_validator = |asn1_public_key: &[u8], session: &CoapSession, validated: bool| {
    if !validated {
        false
    } else {
        // Here, you would add code to validate that the peer's public key is actually the one you
        // expect, and return either true (accept key) or false (reject key).
        // `asn1_encoded_key` should be the certificate structure defined in
        // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
        // might be able to parse with crates like
        // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
        // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
        // SubjectPublicKeyInformation encoded within.
        //
        // Instead of using a lambda like this, you could also implement `RpkValidator` on any
        // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
        // keys.
    }
};

// Set the RPK validator and build the context.
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();

let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
let session = CoapClientSession::connect_dtls(&mut coap_ctx, "example.com:5684".parse().unwrap(), crypto_ctx);

// The session might not be immediately established, but you can already create and send
// requests as usual after this point.
// To check for errors and/or disconnections, you might want to call and check the return value
// of `session.state()` occasionally.
// For error handling, you might also want to register an event handler with the CoAP context.
// Remaining code omitted for brevity, see the crate-level docs for a full example of client
// operation.

Creating a server that supports DTLS RPK configured:

use libcoap_rs::CoapContext;
use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
use libcoap_rs::session::{CoapClientSession, CoapSession};

fn key_validator(asn1_public_key: &[u8], session: &CoapSession, validated: bool) -> bool {
    // Here, you would add code to validate that the peer's public key is actually the one you
    // expect, and return either true (accept key) or false (reject key).
    // `asn1_encoded_key` should be the certificate structure defined in
    // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
    // might be able to parse with crates like
    // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
    // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
    // SubjectPublicKeyInformation encoded within.
    //
    // Instead of using a function like this, you could also implement `RpkValidator` on any
    // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
    // keys.
}

// RPK is only supported if the key is provided as a byte array in memory, providing file paths
// directly is unsupported.
let server_private_key = Vec::from(include_str!("../../../resources/test-keys/server/server.key.pem"));
let server_public_key = Vec::from(include_str!("../../../resources/test-keys/server/server.pub.pem"));

// Create key definition.
let server_key_def = RpkKeyDef::with_pem_memory(server_public_key, server_private_key);

// Create the cryptography context. Note that you might have to explicitly specify that
// PKI certificate validation should not be performed, even though enabling it while passing a
// RPK key definition is impossible due to a lack of a constructor for
// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
// This is a type system limitation.
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(server_key_def);
// Set the RPK validator and build the context.
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();

let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
coap_ctx.set_pki_rpk_context(crypto_ctx);
coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");

// For error handling, you might want to register an event handler with the CoAP context.
// Remaining code omitted for brevity, see the crate-level docs for a full example of server
// operation.

Creating and connecting a client-side session with DTLS PKI configured:

use std::ffi::{c_uint, CStr};
use std::net::SocketAddr;
use libcoap_rs::CoapContext;
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder};
use libcoap_rs::session::{CoapClientSession, CoapSession};
use std::ffi::CString;

// Paths to private key and certificate.
// The certificate may also contain intermediates. However, they might *not* be provided to the
// peer (i.e., the peer might have to already know all intermediates beforehand in order for
// validation to succeed).
let client_private_key = "../../../resources/test-keys/client/client.key.pem";
let client_public_cert = "../../../resources/test-keys/client/client.crt.pem";

// Create key definition.
// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
// It is exclusively used to determine a list of CA names that a server will provide to a client
// to indicate to it which certificates it should send.
// For client-side operation, `ca_cert` is not used.
let client_key_def = PkiKeyDef::with_pem_files(
                        None::<String>,
                        client_public_cert,
                        client_private_key);

// The name of the server we want to connect to.
let server_name = "example.com";

// Validator function for certificate common names.
// Typically, this function should have the following behavior:
// - If !validated, something went wrong during TLS-level certificate checks, so reject.
// - If depth == 0, we are checking the client certificate, whose common name should equal the
//   server name we want to connect to, return the equality check result.
// - If depth > 0, we are checking an intermediate or root CA certificate. As we usually trust
//   all CAs in the trust store and validation of those is already performed by the TLS library,
//   always accept.
let c_server_name = CString::new(server_name).unwrap();
let cn_validator = |
     cn: &CStr,
     asn1_public_cert: &[u8],
     session: &CoapSession,
     depth: c_uint,
     validated: bool| {
    if !validated {
        false
    } else if depth == 0 {
        cn == c_server_name.as_c_str()
    } else {
        true
    }
};

// Create the cryptography context. Note that you must explicitly specify whether
// PKI certificate validation should be performed using the context builder's generics.
let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(client_key_def)
                 // Provide the server with a Server Name Indication (might be required by
                 // some servers to use the right certificate).
                 .client_sni(server_name).unwrap()
                 // Use the CN validator we defined earlier.
                 .cn_validator(cn_validator)
                 // Enable certificate chain validation (in case you have intermediate CAs) and set
                 // verification depth (recommended value here is 3).
                 .cert_chain_validation(3)
                 .build();

let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
let session = CoapClientSession::connect_dtls(
             &mut coap_ctx,
             SocketAddr::new(server_name.parse().expect("error in name resolution"), 5684),
             crypto_ctx);

// The session might not be immediately established, but you can already create and send
// requests as usual after this point.
// To check for errors and/or disconnections, you might want to call and check the return value
// of `session.state()` occasionally.
// For error handling, you might also want to register an event handler with the CoAP context.
// Remaining code omitted for brevity, see the crate-level docs for a full example of client
// operation.

Creating a server that supports DTLS RPK configured:

use std::ffi::{c_uint, CStr};
use std::net::SocketAddr;
use libcoap_rs::CoapContext;
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder, KeyDef, Pki};
use libcoap_rs::session::{CoapClientSession, CoapSession};
use std::ffi::CString;

// Paths to private key and certificate.
// The certificate may also contain intermediates. However, they might *not* be provided to the
// peer (i.e., the peer might have to already know all intermediates beforehand in order for
// validation to succeed).
let server_private_key = "../../../resources/test-keys/server/server.key.pem";
let server_public_cert = "../../../resources/test-keys/server/server.crt.pem";
let ca_cert = "../../../resources/test-keys/ca/ca.crt.pem";

// Create key definition.
// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
// It is exclusively used to determine a list of CA names that a server will provide to a client
// to indicate to it which certificates it should send.
let server_key_def = PkiKeyDef::with_pem_files(Some(ca_cert), server_public_cert, server_private_key);

// The name of the server we use.
let server_name = "example.com";

// Key provider for Server Name Indications.
// If the client provides a server name using the Server Name Indication extension, this
// callback is called to determine the key the server should use instead of the one provided as
// the default to `PkiRpkContextBuilder::new`.
// Typically, you would want to maintain a map from potential server names to key definitions,
// and return either `Some(Box::new(key))` for the appropriate map entry or `None` if the server
// name is unknown.
let c_server_name = CString::new(server_name).unwrap();
let sni_cb = |sni: &CStr| -> Option<Box<dyn KeyDef<KeyType = Pki>>> {
    (sni == c_server_name.as_c_str()).then_some(Box::new(server_key_def.clone()))
};

// Just like the client, the server may also have a CN validator defined to determine whether
// the common name of the client is acceptable. Here, we omit this validator for brevity.

// Create the cryptography context. Note that you must explicitly specify whether
// PKI certificate validation should be performed using the context builder's generics.
let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(server_key_def.clone())
                 .sni_key_provider(sni_cb)
                 // Enable certificate chain validation (in case you have intermediate CAs) and set
                 // verification depth (recommended value here is 3).
                 .cert_chain_validation(3)
                 .build();

let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
coap_ctx.set_pki_rpk_context(crypto_ctx);
coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");

// For error handling, you might want to register an event handler with the CoAP context.
// Remaining code omitted for brevity, see the crate-level docs for a full example of server
// operation.

Structs§

CertVerifying
Marker indicating that a cryptographic context does perform TLS library-side certificate verification.
DerFileKeyComponent
Key component that is stored in a DER-encoded file with the given path.
DerMemoryKeyComponent
Key component that is stored in memory as a DER-encoded sequence of bytes.
EngineKeyComponent
Key component that is passed to the TLS library verbatim (only supported by OpenSSL).
NonCertVerifying
Marker indicating that a cryptographic context does not do TLS library-side certificate verification.
PemFileKeyComponent
Key component that is stored in a PEM-encoded file with the given path.
PemMemoryKeyComponent
Key component that is stored in memory as a PEM-encoded sequence of bytes.
Pkcs11KeyComponent
Key component that is stored as a PKCS11 URI.
Pki
(Marker) key type for keys with a certificate signed by a trusted CA.
PkiKeyDef
Key definition for a DTLS key consisting of a private key and a CA-signed certificate.
PkiRpkContext
A configuration context for PKI or RPK based DTLS operation.
PkiRpkContextBuilder
Builder for a PKI or RPK configuration context.
Rpk
(Marker) key type for asymmetric DTLS keys not signed by a CA (raw public keys).
RpkKeyDef
Key definition for a DTLS key consisting of a private and public key component without a signed certificate.

Enums§

Asn1PrivateKeyType
Private key type for DER/ASN.1 encoded keys.
ServerPkiRpkCryptoContext
A context configuration for server-side PKI or RPK based DTLS encryption.

Traits§

CertVerificationMode
Trait for markers that indicate whether a PKI/RPK DTLS context performs certificate validation.
KeyComponent
Trait indicating that a type can be used as a DTLS key component of the given KeyType KTY.
KeyDef
Trait for types that can be used as a libcoap DTLS asymmetric key definition (RPK or PKI).
KeyType
Trait for marker structs that describe different types of asymmetric DTLS keys (RPK or PKI).
PkiCnValidator
Trait for types that can check whether a peer’s or CA certificate’s common name is allowed/as expected for a session.
PkiRpkSniKeyProvider
Trait for things that can provide RPK/PKI DTLS keys for a given Server Name Indication.
RpkValidator
Trait for types that can validate that a raw public key is the one expected for a given peer.