diff --git a/Cargo.toml b/Cargo.toml index 82d85c46..31f4626e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,10 +30,10 @@ jwt-simple = "0.11.2" ece = "^2.2" pem = "1.1.0" sec1_decode = "^0.1.0" -base64 = "^0.13" chrono = "^0.4" log = "^0.4" async-trait = "^0.1" +ct-codecs = "1.1.3" [dev-dependencies] argparse = "^0.2" diff --git a/src/error.rs b/src/error.rs index 664b7539..cee26e2d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,6 @@ use std::string::FromUtf8Error; use std::time::{Duration, SystemTime}; use std::{convert::From, error::Error, fmt, io::Error as IoError}; -use base64::DecodeError; use http::uri::InvalidUri; use serde_json::error::Error as JsonError; @@ -107,12 +106,6 @@ impl From for WebPushError { } } -impl From for WebPushError { - fn from(_: DecodeError) -> WebPushError { - WebPushError::InvalidCryptoKeys - } -} - impl WebPushError { pub fn short_description(&self) -> &'static str { match *self { diff --git a/src/http_ece.rs b/src/http_ece.rs index 1e5717ce..b5729061 100644 --- a/src/http_ece.rs +++ b/src/http_ece.rs @@ -1,5 +1,6 @@ //! Payload encryption algorithm +use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder}; use ece::encrypt; use crate::error::WebPushError; @@ -90,7 +91,7 @@ impl<'a> HttpEce<'a> { self.add_vapid_headers(&mut headers); // ECE library base64 encodes content in aesgcm, but not aes128gcm, so decode base64 here to match the 128 API - let data = base64::decode_config(data.body(), base64::URL_SAFE_NO_PAD) + let data = Base64UrlSafeNoPadding::decode_to_vec(data.body(), None) .expect("ECE library should always base64 encode"); Ok(WebPushPayload { @@ -111,7 +112,8 @@ impl<'a> HttpEce<'a> { format!( "vapid t={}, k={}", signature.auth_t, - base64::encode_config(&signature.auth_k, base64::URL_SAFE_NO_PAD) + Base64UrlSafeNoPadding::encode_to_string(&signature.auth_k) + .expect("encoding a valid auth_k cannot overflow") ), )); } @@ -127,22 +129,22 @@ impl<'a> HttpEce<'a> { #[cfg(test)] mod tests { - use base64::{self, URL_SAFE}; use regex::Regex; use crate::error::WebPushError; use crate::http_ece::{ContentEncoding, HttpEce}; use crate::VapidSignature; use crate::WebPushPayload; + use ct_codecs::{Base64UrlSafeNoPadding, Decoder}; #[test] fn test_payload_too_big() { - let p256dh = base64::decode_config( + let p256dh = Base64UrlSafeNoPadding::decode_to_vec( "BLMaF9ffKBiWQLCKvTHb6LO8Nb6dcUh6TItC455vu2kElga6PQvUmaFyCdykxY2nOSSL3yKgfbmFLRTUaGv4yV8", - URL_SAFE, + None, ) .unwrap(); - let auth = base64::decode_config("xS03Fj5ErfTNH_l9WHE9Ig", URL_SAFE).unwrap(); + let auth = Base64UrlSafeNoPadding::decode_to_vec("xS03Fj5ErfTNH_l9WHE9Ig", None).unwrap(); let http_ece = HttpEce::new(ContentEncoding::Aes128Gcm, &p256dh, &auth, None); //This content is one above limit. let content = [0u8; 3801]; @@ -191,12 +193,12 @@ mod tests { } fn setup_payload(vapid_signature: Option, encoding: ContentEncoding) -> WebPushPayload { - let p256dh = base64::decode_config( + let p256dh = Base64UrlSafeNoPadding::decode_to_vec( "BLMbF9ffKBiWQLCKvTHb6LO8Nb6dcUh6TItC455vu2kElga6PQvUmaFyCdykxY2nOSSL3yKgfbmFLRTUaGv4yV8", - URL_SAFE, + None, ) .unwrap(); - let auth = base64::decode_config("xS03Fi5ErfTNH_l9WHE9Ig", URL_SAFE).unwrap(); + let auth = Base64UrlSafeNoPadding::decode_to_vec("xS03Fi5ErfTNH_l9WHE9Ig", None).unwrap(); let http_ece = HttpEce::new(encoding, &p256dh, &auth, vapid_signature); let content = "Hello, world!".as_bytes(); diff --git a/src/lib.rs b/src/lib.rs index cf2a6720..399f183f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,6 @@ //! //! ```no_run //! # use web_push::*; -//! # use base64::URL_SAFE; //! # use std::fs::File; //! # #[tokio::main] //! # async fn main() -> Result<(), Box> { @@ -17,7 +16,7 @@ //! let p256dh = "key_from_browser_as_base64"; //! let auth = "auth_from_browser_as_base64"; //! -//! //You would likely get this by deserializing a browser `pushSubscription` object. +//! //You would likely get this by deserializing a browser `pushSubscription` object. //! let subscription_info = SubscriptionInfo::new( //! endpoint, //! p256dh, @@ -62,7 +61,6 @@ pub use crate::message::{ }; pub use crate::vapid::builder::PartialVapidSignatureBuilder; pub use crate::vapid::{VapidSignature, VapidSignatureBuilder}; -pub use base64::{Config, BCRYPT, BINHEX, CRYPT, IMAP_MUTF7, STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD}; mod clients; mod error; diff --git a/src/message.rs b/src/message.rs index 8a1ff419..6257047e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,3 +1,4 @@ +use ct_codecs::{Base64UrlSafeNoPadding, Decoder}; use http::uri::Uri; use std::fmt::{Display, Formatter}; @@ -8,9 +9,9 @@ use crate::vapid::VapidSignature; /// Encryption keys from the client. #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] pub struct SubscriptionKeys { - /// The public key. Base64 encoded. + /// The public key. Base64-encoded, URL-safe alphabet, no padding. pub p256dh: String, - /// Authentication secret. Base64 encoded. + /// Authentication secret. Base64-encoded, URL-safe alphabet, no padding. pub auth: String, } @@ -183,8 +184,10 @@ impl<'a> WebPushMessageBuilder<'a> { .transpose()?; if let Some(payload) = self.payload { - let p256dh = base64::decode_config(&self.subscription_info.keys.p256dh, base64::URL_SAFE)?; - let auth = base64::decode_config(&self.subscription_info.keys.auth, base64::URL_SAFE)?; + let p256dh = Base64UrlSafeNoPadding::decode_to_vec(&self.subscription_info.keys.p256dh, None) + .map_err(|_| WebPushError::InvalidCryptoKeys)?; + let auth = Base64UrlSafeNoPadding::decode_to_vec(&self.subscription_info.keys.auth, None) + .map_err(|_| WebPushError::InvalidCryptoKeys)?; let http_ece = HttpEce::new(payload.encoding, &p256dh, &auth, self.vapid_signature); diff --git a/src/vapid/builder.rs b/src/vapid/builder.rs index b86eade6..b5d42b31 100644 --- a/src/vapid/builder.rs +++ b/src/vapid/builder.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::io::Read; +use ct_codecs::Base64UrlSafeNoPadding; use http::uri::Uri; use jwt_simple::prelude::*; use serde_json::Value; @@ -35,7 +36,7 @@ use crate::vapid::{VapidKey, VapidSignature, VapidSigner}; /// openssl ec -in private.pem -text -noout -conv_form uncompressed /// ``` /// -/// ... or a base64 encoded string, which the client should convert into +/// ... or a base64-encoded string, which the client should convert into /// byte form before using: /// /// ```bash,ignore @@ -73,7 +74,6 @@ use crate::vapid::{VapidKey, VapidSignature, VapidSigner}; /// let signature = sig_builder.build().unwrap(); /// # } /// ``` - pub struct VapidSignatureBuilder<'a> { claims: Claims, key: VapidKey, @@ -148,43 +148,40 @@ impl<'a> VapidSignatureBuilder<'a> { }) } - /// Creates a new builder from a raw base64 encoded private key. This isn't the base64 from a key + /// Creates a new builder from a raw base64-encoded private key. This isn't the base64 from a key /// generated by openssl, but rather the literal bytes of the private key itself. This is the kind /// of key given to you by most VAPID key generator sites, and also the kind used in the API of other /// large web push libraries, such as PHP and Node. /// - /// # Config - /// base64 has multiple encodings itself, the most common of which for web push is URL_SAFE_NO_PAD. - /// This function does support other encodings however, if needed. + /// Base64 encoding must use URL-safe alphabet without padding. /// /// # Example /// /// ``` /// # use web_push::VapidSignatureBuilder; /// // Use `from_base64` here if you have a sub - /// let builder = VapidSignatureBuilder::from_base64_no_sub("IQ9Ur0ykXoHS9gzfYX0aBjy9lvdrjx_PFUXmie9YRcY", base64::URL_SAFE_NO_PAD).unwrap(); + /// let builder = VapidSignatureBuilder::from_base64_no_sub("IQ9Ur0ykXoHS9gzfYX0aBjy9lvdrjx_PFUXmie9YRcY").unwrap(); /// ``` pub fn from_base64( encoded: &str, - config: base64::Config, subscription_info: &'a SubscriptionInfo, ) -> Result, WebPushError> { let pr_key = ES256KeyPair::from_bytes( - &base64::decode_config(encoded, config).map_err(|_| WebPushError::InvalidCryptoKeys)?, + &Base64UrlSafeNoPadding::decode_to_vec(encoded, None).map_err(|_| WebPushError::InvalidCryptoKeys)?, ) .map_err(|_| WebPushError::InvalidCryptoKeys)?; Ok(Self::from_ec(pr_key, subscription_info)) } - /// Creates a new builder from a raw base64 encoded private key. This function doesn't take a subscription, + /// Creates a new builder from a raw base64-encoded private key. This function doesn't take a subscription, /// allowing the reuse of one builder for multiple messages by cloning the resulting builder. - pub fn from_base64_no_sub( - encoded: &str, - config: base64::Config, - ) -> Result { + /// + /// Base64 encoding must use URL-safe alphabet without padding. + /// + pub fn from_base64_no_sub(encoded: &str) -> Result { let pr_key = ES256KeyPair::from_bytes( - &base64::decode_config(encoded, config).map_err(|_| WebPushError::InvalidCryptoKeys)?, + &Base64UrlSafeNoPadding::decode_to_vec(encoded, None).map_err(|_| WebPushError::InvalidCryptoKeys)?, ) .map_err(|_| WebPushError::InvalidCryptoKeys)?; @@ -296,7 +293,8 @@ impl PartialVapidSignatureBuilder { mod tests { use std::fs::File; - use ::lazy_static::lazy_static; + use ct_codecs::{Base64UrlSafeNoPadding, Encoder}; + use lazy_static::lazy_static; use crate::message::SubscriptionInfo; use crate::vapid::VapidSignatureBuilder; @@ -328,7 +326,7 @@ mod tests { assert_eq!( "BMo1HqKF6skMZYykrte9duqYwBD08mDQKTunRkJdD3sTJ9E-yyN6sJlPWTpKNhp-y2KeS6oANHF-q3w37bClb7U", - base64::encode_config(&signature.auth_k, base64::URL_SAFE_NO_PAD) + Base64UrlSafeNoPadding::encode_to_string(&signature.auth_k).unwrap() ); assert!(!signature.auth_t.is_empty()); @@ -341,7 +339,7 @@ mod tests { assert_eq!( "BMo1HqKF6skMZYykrte9duqYwBD08mDQKTunRkJdD3sTJ9E-yyN6sJlPWTpKNhp-y2KeS6oANHF-q3w37bClb7U", - base64::encode_config(&signature.auth_k, base64::URL_SAFE_NO_PAD) + Base64UrlSafeNoPadding::encode_to_string(&signature.auth_k).unwrap() ); assert!(!signature.auth_t.is_empty()); @@ -349,13 +347,12 @@ mod tests { #[test] fn test_builder_from_base64() { - let builder = - VapidSignatureBuilder::from_base64(PRIVATE_BASE64, base64::URL_SAFE_NO_PAD, &SUBSCRIPTION_INFO).unwrap(); + let builder = VapidSignatureBuilder::from_base64(PRIVATE_BASE64, &SUBSCRIPTION_INFO).unwrap(); let signature = builder.build().unwrap(); assert_eq!( "BMjQIp55pdbU8pfCBKyXcZjlmER_mXt5LqNrN1hrXbdBS5EnhIbMu3Au-RV53iIpztzNXkGI56BFB1udQ8Bq_H4", - base64::encode_config(&signature.auth_k, base64::URL_SAFE_NO_PAD) + Base64UrlSafeNoPadding::encode_to_string(&signature.auth_k).unwrap() ); assert!(!signature.auth_t.is_empty()); diff --git a/src/vapid/signer.rs b/src/vapid/signer.rs index a155977a..b773ef01 100644 --- a/src/vapid/signer.rs +++ b/src/vapid/signer.rs @@ -10,7 +10,7 @@ use crate::{error::WebPushError, vapid::VapidKey}; /// [VapidSignatureBuilder](struct.VapidSignatureBuilder.html). #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct VapidSignature { - /// The signed JWT, base64 encoded + /// The signed JWT, base64-encoded pub auth_t: String, /// The public key bytes pub auth_k: Vec,