alexa-verifier 0.1.2

Verify that requests were sent by Alexa for custom webservice skills, in Rust
Documentation

Alexa Rust Webservice Verifier

Crates.io

Verify that incoming requests are from Alexa for custom, webservice skills.

About

  • RequestVerifier caches certs on the first request
  • Initialize with RequestVerifier::new()
  • Method verify() -> Result<(), Error> returns Ok if verified successfully and Error if failed
    pub struct RequestVerifier {
        cert_cache: HashMap<String, Vec<u8>>,
    }

    impl Default for RequestVerifier {
        fn default() -> Self {
            RequestVerifier {
                cert_cache: HashMap::new(),
            }
        }
    }

    impl RequestVerifier {
        pub fn new() -> Self {
            RequestVerifier::default()
        }

        pub fn verify(
            &mut self,
            signature_cert_chain_url: &str,
            signature: &str,
            body: &[u8],
            timestamp: &str,
            timestamp_tolerance_millis: Option<u64>,
        ) -> Result<(), Error> { 
            ...
        }
    }

Example using Rouille server and alexa_sdk

use crate::skill::process_request; // Entry point to custom skill
use alexa_verifier::RequestVerifier; // Struct provided by this crate
use log::{debug, error, info};
use rouille::{router, Request, Response};
use std::{
    io::Read,
    sync::{Mutex, MutexGuard},
};

fn note_routes(request: &Request, verifier: &mut MutexGuard<RequestVerifier>) -> Response {
    router!(request,
        (POST) (/) => {
            info!("Request received...");

            // Get request body data
            let mut body = request.data().unwrap();
            let mut body_bytes: Vec<u8> = vec![];
            body.read_to_end(&mut body_bytes).unwrap();

            // Get needed headers, default to blank (will cause verification to fail)
            let signature_cert_chain_url = request.header("SignatureCertChainUrl").unwrap_or("");
            let signature = request.header("Signature").unwrap_or("");

            // Deserialize using alexa_sdk::Request
            let _request = serde_json::from_slice::<alexa_sdk::Request>(&body_bytes); 
            if let Err(e) = _request {
                error!("Could not deserialize request");
                error!("{:?}", e);
                let response = Response::empty_400();
                info!("Sending back response...");
                debug!("{:?}", response);
                return response;
            }
            let request = _request.unwrap();
            debug!("{:?}", request);

            // alexa-verifier used here, return 400 if verification fails
            if verifier
                .verify(
                    signature_cert_chain_url,
                    signature,
                    &body_bytes,
                    request.body.timestamp.as_str(),
                    None
                ).is_err() {
                    error!("Could not validate request came from Alexa");
                    let response = Response::empty_400();
                    info!("Sending back response...");
                    debug!("{:?}", response);
                    return response;
                };
            debug!("Request is validated...");

            // Entry point to custom skill, returning alexa_sdk::Response
            let response = Response::json(&process_request(request));
            info!("Sending back response...");
            debug!("{:?}", response);
            response
    },
        _ => Response::empty_404()
    )
}

pub fn run() -> std::io::Result<()> {
    info!("Starting server on 0.0.0.0:8086");
    let verifier = Mutex::from(RequestVerifier::new());

    rouille::start_server("0.0.0.0:8086", move |request| {
        let mut verifier = verifier.lock().unwrap();
        note_routes(&request, &mut verifier)
    });
}