polyproto 0.17.1

(Generic) Rust types and traits to quickly get a polyproto implementation up and running
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Example: Ed25519 Certificate
//!
//! This example shows how to create an actor ID-CSR and sign it into an
//! ID-Cert using the typed polyproto API.

#![allow(clippy::unwrap_used, missing_docs, clippy::missing_docs_in_private_items)]

use std::{str::FromStr, time::Duration};

use der::{
    Encode,
    asn1::{BitString, UtcTime},
};
use ed25519_dalek::{Signature as Ed25519DalekSignature, Signer, SigningKey, VerifyingKey};
use polyproto::{
    certs::{
        PublicKeyInfo,
        capabilities::ActorCapabilities,
        idcert::{IdCertActor, traits::IdCertBehaviors as _},
        idcerttbs::IdCertTbsActor,
        idcsr::{IdCsrActor, IdCsrBehaviors as _},
    },
    errors::composite::{CertificateConversionError, PublicKeyError},
    key::{PrivateKey, PublicKey},
    signature::Signature,
    types::{
        DomainName, FederationId, LocalName, SessionId,
        pdn::{ActorDN, HomeServerDN},
        x509_cert::SerialNumber,
    },
};
use rand::rngs::OsRng;
use spki::{AlgorithmIdentifierOwned, ObjectIdentifier, SignatureBitStringEncoding};
use x509_cert::{
    Certificate,
    name::RelativeDistinguishedName,
    time::{Time, Validity},
};

/// This example creates an actor ID-CSR, writes it to a DER file, then signs
/// it into an ID-Cert and writes that to another DER file.
///
/// If you have openssl installed, you can inspect the CSR by running:
///
/// ```sh
/// openssl req -in cert.csr -verify -inform der
/// ```
///
/// The cert can be validated using:
///
/// ```sh
/// openssl x509 -in cert.der -text -noout -inform der
/// ```
#[allow(clippy::unwrap_used)]
fn main() {
    let mut csprng = rand::rngs::OsRng;
    let priv_key = Ed25519PrivateKey::gen_keypair(&mut csprng);
    println!("Private Key is: {:?}", priv_key.key.to_bytes());
    println!("Public Key is: {:?}", priv_key.public_key.key.to_bytes());
    println!();

    let actor_dn = ActorDN {
        federation_id: FederationId::new("flori@polyphony.chat").unwrap(),
        local_name: LocalName::new("flori").unwrap(),
        domain_name: DomainName::new("polyphony.chat").unwrap(),
        session_id: SessionId::new_validated("client1").unwrap(),
        additional_fields: RelativeDistinguishedName::default(),
    };

    let home_server_dn = HomeServerDN {
        domain_name: DomainName::new("polyphony.chat").unwrap(),
        additional_fields: RelativeDistinguishedName::default(),
    };

    let validity = Validity {
        not_before: Time::UtcTime(UtcTime::from_unix_duration(Duration::from_secs(10)).unwrap()),
        not_after: Time::UtcTime(UtcTime::from_unix_duration(Duration::from_secs(1000)).unwrap()),
    };

    // Create an actor ID-CSR using ActorCapabilities for type-safe validation.
    let csr = IdCsrActor::new(actor_dn.clone(), &priv_key, ActorCapabilities::default()).unwrap();

    // Write the CSR to a file.
    let csr_der = csr.clone().to_der().unwrap();
    std::fs::write("cert.csr", &csr_der).unwrap();
    println!("Wrote actor CSR to cert.csr ({} bytes)", csr_der.len());

    // Build the TBS (to-be-signed) certificate from the CSR fields.
    // In a real flow this would be done by the CA (home server) after
    // verifying the CSR.
    let tbs = IdCertTbsActor {
        serial_number: SerialNumber::from_bytes_be(&8932489u64.to_be_bytes()).unwrap(),
        signature_algorithm: Ed25519Signature::algorithm_identifier(),
        issuer: home_server_dn.clone(),
        validity,
        subject: actor_dn,
        subject_public_key: priv_key.pubkey().clone(),
        capabilities: ActorCapabilities::default(),
        s: Default::default(),
    };

    // Sign the TBS cert with the CA key to produce the final ID-Cert.
    let cert = IdCertActor::try_from_tbs(
        &tbs,
        &priv_key,
        SerialNumber::from_bytes_be(&8932489u64.to_be_bytes()).unwrap(),
        home_server_dn,
        validity,
    )
    .unwrap();

    // Write the cert to a file.
    let cert_der = Certificate::try_from(cert).unwrap().to_der().unwrap();
    #[cfg(not(target_arch = "wasm32"))]
    std::fs::write("cert.der", &cert_der).unwrap();
    println!("Wrote actor ID-Cert to cert.der ({} bytes)", cert_der.len());
}

// As mentioned in the README, we start by implementing the signature trait.

// Here, we start by defining the signature type, which is a wrapper around the
// signature type from the ed25519-dalek crate.
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(missing_docs)]
struct Ed25519Signature {
    #[allow(missing_docs)]
    signature: Ed25519DalekSignature,
    #[allow(missing_docs)]
    algorithm: AlgorithmIdentifierOwned,
}

impl std::fmt::Display for Ed25519Signature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.signature)
    }
}

// We implement the Signature trait for our signature type.
impl Signature for Ed25519Signature {
    // We define the signature type from the ed25519-dalek crate as the associated
    // type.
    type Signature = Ed25519DalekSignature;

    fn as_bytes(&self) -> Vec<u8> {
        self.as_signature().to_vec()
    }

    // This is straightforward: we return a reference to the signature.
    fn as_signature(&self) -> &Self::Signature {
        &self.signature
    }

    // The algorithm identifier for a given signature implementation is constant. We
    // just need to define it here.
    fn algorithm_identifier() -> AlgorithmIdentifierOwned {
        AlgorithmIdentifierOwned {
            // This is the OID for Ed25519. It is defined in the IANA registry.
            oid: ObjectIdentifier::from_str("1.3.101.112").unwrap(),
            // For this example, we don't need or want any parameters.
            parameters: None,
        }
    }

    #[cfg(not(tarpaulin_include))]
    fn from_bytes(signature: &[u8]) -> Result<Ed25519Signature, polyproto::errors::InvalidInput> {
        if signature.len() != 64 {
            return Err(polyproto::errors::InvalidInput::Length {
                min_length: 64,
                max_length: 64,
                actual_length: signature.len().to_string(),
            });
        }

        let signature_array: [u8; 64] = {
            let mut array = [0; 64];
            array.copy_from_slice(signature);
            array
        };
        Ok(Self {
            signature: Ed25519DalekSignature::from_bytes(&signature_array),
            algorithm: Self::algorithm_identifier(),
        })
    }
}

// The `SignatureBitStringEncoding` trait is used to convert a signature to a
// bit string. We implement it for our signature type.
impl SignatureBitStringEncoding for Ed25519Signature {
    fn to_bitstring(&self) -> der::Result<der::asn1::BitString> {
        BitString::from_bytes(&self.as_signature().to_bytes())
    }
}

// Next, we implement the key traits. We start by defining the private key type.
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
struct Ed25519PrivateKey {
    // Defined below
    #[allow(missing_docs)]
    public_key: Ed25519PublicKey,
    // The private key from the ed25519-dalek crate
    #[allow(missing_docs)]
    key: SigningKey,
}

impl PrivateKey<Ed25519Signature> for Ed25519PrivateKey {
    type PublicKey = Ed25519PublicKey;

    // Return a reference to the public key
    fn pubkey(&self) -> &Self::PublicKey {
        &self.public_key
    }

    // Signs a message. The beauty of having to wrap the ed25519-dalek crate is that
    // we can harness all of its functionality, such as the `sign` method.
    fn sign(&self, data: &[u8]) -> Ed25519Signature {
        let signature = self.key.sign(data);
        Ed25519Signature { signature, algorithm: self.algorithm_identifier() }
    }
}

impl Ed25519PrivateKey {
    // Let's also define a handy method to generate a key pair.
    #[allow(missing_docs)]
    pub fn gen_keypair(csprng: &mut OsRng) -> Self {
        let key = SigningKey::generate(csprng);
        let public_key = Ed25519PublicKey { key: key.verifying_key() };
        Self { public_key, key }
    }
}

// Same thing as above for the public key type.
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
struct Ed25519PublicKey {
    // The public key type from the ed25519-dalek crate
    #[allow(missing_docs)]
    key: VerifyingKey,
}

impl PublicKey<Ed25519Signature> for Ed25519PublicKey {
    // Verifies a signature. We use the `verify_strict` method from the
    // ed25519-dalek crate. This method is used to mitigate weak key forgery.
    #[cfg(not(tarpaulin_include))]
    fn verify_signature(
        &self,
        signature: &Ed25519Signature,
        data: &[u8],
    ) -> Result<(), PublicKeyError> {
        match self.key.verify_strict(data, signature.as_signature()) {
            Ok(_) => Ok(()),
            Err(_) => Err(PublicKeyError::BadSignature),
        }
    }

    // Returns the public key info. Public key info is used to encode the public key
    // in a certificate or a CSR. It is named after the `SubjectPublicKeyInfo`
    // type from the X.509 standard, and thus includes the information needed to
    // encode the public key in a certificate or a CSR.
    fn public_key_info(&self) -> Result<PublicKeyInfo, PublicKeyError> {
        let bitstring = BitString::from_bytes(&self.key.to_bytes())
            .map_err(|_| PublicKeyError::BadPublicKeyInfo)?;

        Ok(PublicKeyInfo {
            algorithm: Ed25519Signature::algorithm_identifier(),
            public_key_bitstring: bitstring,
        })
    }

    #[cfg(not(tarpaulin_include))]
    fn try_from_public_key_info(
        public_key_info: PublicKeyInfo,
    ) -> std::result::Result<Ed25519PublicKey, CertificateConversionError> {
        let mut key_vec = public_key_info.public_key_bitstring.raw_bytes().to_vec();
        key_vec.resize(32, 0);
        let signature_array: [u8; 32] = {
            let mut array = [0; 32];
            array.copy_from_slice(&key_vec[..]);
            array
        };
        Ok(Self { key: VerifyingKey::from_bytes(&signature_array).unwrap() })
    }
}

#[test]
fn test_example() {
    main()
}