github_webhook_extract/
lib.rs1#[cfg(feature = "axum")]
10mod axum;
11mod types;
12
13use bytes::Bytes;
14use std::env;
15pub use types::*;
16
17use digest::CtOutput;
18use generic_array::GenericArray;
19use hmac::{Hmac, Mac};
20use sha1::Sha1;
21use sha2::Sha256;
22use thiserror::Error;
23use tracing::instrument;
24use uuid::Uuid;
25
26#[instrument(skip_all)]
36pub fn verify(
37 guid: Uuid,
38 signature_sha1: Option<String>,
39 signature_sha256: Option<String>,
40 bytes: Bytes,
41 json: &str,
42) -> Result<GithubPayload, VerifyError> {
43 match (&signature_sha1, &signature_sha256) {
45 (Some(sha1), None) => {
46 tracing::debug!("using sha1");
47 let token = env::var("GITHUB_TOKEN").map_err(|_| {
48 tracing::error!("secret github token is missing");
49 VerifyError::TokenMissing
50 })?;
51
52 let mut mac = Hmac::<Sha1>::new_from_slice(token.as_bytes()).map_err(|e| {
53 tracing::error!("error creating hmac: {:?}", e);
54 VerifyError::HmacCreation
55 })?;
56 mac.update(&bytes);
57 let result = mac.finalize();
58 let signature = hex::decode(sha1.split_once('=').ok_or(VerifyError::Sha1ParseError)?.1)
59 .map_err(|e| {
60 tracing::debug!(?e);
61 VerifyError::HexParseError
62 })?;
63
64 if result != CtOutput::new(*GenericArray::from_slice(&signature)) {
65 return Err(VerifyError::NotVerified);
66 }
67 }
68 (_, Some(sha256)) => {
69 tracing::debug!("using sha256");
70 let token = env::var("GITHUB_TOKEN").map_err(|_| {
71 tracing::error!("secret github token is missing");
72 VerifyError::TokenMissing
73 })?;
74
75 let mut mac = Hmac::<Sha256>::new_from_slice(token.as_bytes()).map_err(|e| {
76 tracing::error!("error creating hmac: {:?}", e);
77 VerifyError::HmacCreation
78 })?;
79 mac.update(&bytes);
80 let result = mac.finalize();
81 let signature = hex::decode(
82 sha256
83 .split_once('=')
84 .ok_or(VerifyError::Sha256ParseError)?
85 .1,
86 )
87 .map_err(|e| {
88 tracing::debug!(?e);
89 VerifyError::HexParseError
90 })?;
91
92 if result != CtOutput::new(*GenericArray::from_slice(&signature)) {
93 return Err(VerifyError::NotVerified);
94 }
95 }
96 (None, None) => tracing::debug!("no signature verification"),
97 }
98
99 let deserializer = &mut serde_json::Deserializer::from_str(json);
100 let event: Event = serde_path_to_error::deserialize(deserializer).map_err(|e| {
101 tracing::warn!("failed to deserialize event: {}", e);
102 VerifyError::EventParseError
103 })?;
104
105 tracing::debug!("finished extracting github payload");
106 Ok(GithubPayload {
107 guid,
108 signature_sha1,
109 signature_sha256,
110 event,
111 })
112}
113
114#[derive(Debug, Error)]
116pub enum VerifyError {
117 #[error("github token not found in environment")]
118 TokenMissing,
119 #[error("could not create hmac")]
120 HmacCreation,
121 #[error("could not parse sha1 header")]
122 Sha1ParseError,
123 #[error("could not parse sha256 header")]
124 Sha256ParseError,
125 #[error("could not parse hex signature")]
126 HexParseError,
127 #[error("payload not verified correctly")]
128 NotVerified,
129 #[error("could not parse event")]
130 EventParseError,
131}