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
//! Example: HTTP API
//!
//! This example shows how to use the polyproto HTTP API.

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

#[cfg(feature = "reqwest")]
/// Example implementation of a polyproto HTTP API client using `reqwest`.
mod reqwest_example {
    use std::str::FromStr;

    use der::asn1::BitString;
    use ed25519_dalek::{
        Signature as Ed25519DalekSignature, SigningKey, VerifyingKey, ed25519::signature::Signer,
    };
    use httptest::{Expectation, Server, matchers::request, responders::json_encoded};
    use polyproto::{
        api::{HttpClient, core::current_unix_time},
        certs::PublicKeyInfo,
        errors::{CertificateConversionError, composite::PublicKeyError},
        key::{PrivateKey, PublicKey},
        signature::Signature,
        types::routes::core::v1::GET_CHALLENGE_STRING,
    };
    use rand::rngs::OsRng;
    use serde_json::json;
    use spki::{AlgorithmIdentifierOwned, ObjectIdentifier, SignatureBitStringEncoding};
    use url::Url;

    /// Setup example server
    pub fn setup_example() -> Server {
        let server = Server::run();
        server.expect(
            Expectation::matching(request::method_path(
                GET_CHALLENGE_STRING.method.as_str(),
                GET_CHALLENGE_STRING.path,
            ))
            .respond_with(json_encoded(json!({
                "challenge": "abcd".repeat(8),
                "expires": current_unix_time() + 100
            }))),
        );
        server
    }

    /// Run example
    pub async fn run_example() {
        let server = setup_example();
        let url = format!("http://{}", server.addr());

        // The actual example starts here.
        // Create a new HTTP client
        let client = HttpClient::new().unwrap();
        // Create an authorized session, if you need it
        let _session: polyproto::api::Session<Ed25519Signature, Ed25519PrivateKey> =
            polyproto::api::Session::new(&client, "12345", Url::parse(&url).unwrap(), None);
        // You can now use the client and session to make requests to the polyproto home
        // server! The client is responsible for all unauthenticated requests,
        // while sessions handle all the routes needing authentication of some
        // sort. Routes are documented under <https://docs.polyphony.chat/APIs/core/>, and each route has a
        // corresponding method in the `HttpClient` struct. For example, if we wanted to
        // get the certificate of the home server, we'd call:
        let cert = client
            .get_server_id_cert(None, &Url::parse("https://example.com/").unwrap())
            .await
            .unwrap();
        dbg!(cert);
    }

    #[cfg(test)]
    pub fn test_main() {
        tokio::runtime::Runtime::new().unwrap().block_on(run_example())
    }

    /// Main entry point for the example.
    #[tokio::main]
    pub async fn main() {
        run_example().await
    }

    /// Ed25519 signature type for polyproto.
    #[derive(Debug, PartialEq, Eq, Clone)]
    pub(crate) struct Ed25519Signature {
        /// The inner signature from the `ed25519-dalek` crate.
        pub(crate) signature: Ed25519DalekSignature,
        /// The algorithm identifier for this signature.
        pub(crate) 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;

        // 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,
            }
        }

        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(),
            })
        }

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

    // 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())
        }
    }

    /// Ed25519 private key type for polyproto.
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub(crate) struct Ed25519PrivateKey {
        /// The corresponding public key.
        pub(crate) public_key: Ed25519PublicKey,
        /// The inner signing key from the `ed25519-dalek` crate.
        pub(crate) key: SigningKey,
    }

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

        fn pubkey(&self) -> &Self::PublicKey {
            &self.public_key
        }

        fn sign(&self, data: &[u8]) -> Ed25519Signature {
            let signature = self.key.sign(data);
            Ed25519Signature { signature, algorithm: self.algorithm_identifier() }
        }
    }

    impl Ed25519PrivateKey {
        /// Generate a new key pair.
        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 }
        }
    }

    /// Ed25519 public key type for polyproto.
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub(crate) struct Ed25519PublicKey {
        /// The inner verifying key from the `ed25519-dalek` crate.
        pub(crate) 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.
        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, polyproto::errors::PublicKeyError> {
            let bitstring = BitString::from_bytes(&self.key.to_bytes())
                .map_err(|_| polyproto::errors::PublicKeyError::BadPublicKeyInfo)?;

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

        fn try_from_public_key_info(
            public_key_info: PublicKeyInfo,
        ) -> Result<Self, 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() })
        }
    }
}

/// Main entry point for the example when the `reqwest` feature is enabled.
#[cfg(feature = "reqwest")]
#[tokio::main]
async fn main() {
    reqwest_example::run_example().await
}

/// Main entry point for the example when the `reqwest` feature is disabled.
#[cfg(not(feature = "reqwest"))]
fn main() {
    eprintln!("This example requires you to compile with the 'reqwest' feature enabled.")
}