diff --git a/Cargo.toml b/Cargo.toml index 29484afe..f1eb8f34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "web-push" description = "Web push notification client with support for http-ece encryption and VAPID authentication." -version = "0.9.2" +version = "0.9.3" authors = ["Julius de Bruijn ", "Andrew Ealovega "] license = "Apache-2.0" homepage = "https://github.com/pimeys/rust-web-push" @@ -25,10 +25,9 @@ http = "^0.2" serde = "^1.0" serde_json = "^1.0" serde_derive = "^1.0" -jwt-simple = "^0.10.4" +jwt-simple = "0.11.2" ece = "^2.1" -pem = "^0.8.3" -pkcs8 = { version = "^0.7.5", features = ["alloc"] } +pem = "1.1.0" sec1_decode = "^0.1.0" base64 = "^0.13" chrono = "^0.4" diff --git a/src/lib.rs b/src/lib.rs index ee438746..49000d8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ pub use crate::http_ece::ContentEncoding; pub use crate::message::{SubscriptionInfo, SubscriptionKeys, WebPushMessage, WebPushMessageBuilder, WebPushPayload}; 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/vapid/builder.rs b/src/vapid/builder.rs index 85c9d0ac..8050c7a9 100644 --- a/src/vapid/builder.rs +++ b/src/vapid/builder.rs @@ -119,10 +119,13 @@ impl<'a> VapidSignatureBuilder<'a> { let mut der_key: Vec = Vec::new(); pk_der.read_to_end(&mut der_key)?; - let decoded = sec1_decode::parse_der(&der_key).map_err(|_| WebPushError::InvalidCryptoKeys)?; - Ok(Self::from_ec( - ES256KeyPair::from_bytes(&decoded.key).unwrap(), + ES256KeyPair::from_bytes( + &sec1_decode::parse_der(&der_key) + .map_err(|_| WebPushError::InvalidCryptoKeys)? + .key, + ) + .map_err(|_| WebPushError::InvalidCryptoKeys)?, subscription_info, )) } @@ -133,10 +136,60 @@ impl<'a> VapidSignatureBuilder<'a> { let mut der_key: Vec = Vec::new(); pk_der.read_to_end(&mut der_key)?; - let decoded = sec1_decode::parse_der(&der_key).map_err(|_| WebPushError::InvalidCryptoKeys)?; + Ok(PartialVapidSignatureBuilder { + key: VapidKey::new( + ES256KeyPair::from_bytes( + &sec1_decode::parse_der(&der_key) + .map_err(|_| WebPushError::InvalidCryptoKeys)? + .key, + ) + .map_err(|_| WebPushError::InvalidCryptoKeys)?, + ), + }) + } + + /// 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. + /// + /// # 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(); + /// ``` + 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)?, + ) + .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, + /// 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 { + let pr_key = ES256KeyPair::from_bytes( + &base64::decode_config(encoded, config).map_err(|_| WebPushError::InvalidCryptoKeys)?, + ) + .map_err(|_| WebPushError::InvalidCryptoKeys)?; Ok(PartialVapidSignatureBuilder { - key: VapidKey::new(ES256KeyPair::from_bytes(&decoded.key).unwrap()), + key: VapidKey::new(pr_key), }) } @@ -173,8 +226,9 @@ impl<'a> VapidSignatureBuilder<'a> { pub(crate) fn read_pem(mut input: R) -> Result { let mut buffer = String::new(); input.read_to_string(&mut buffer).map_err(|_| WebPushError::IoError)?; + //Parse many PEM in the assumption of extra unneeded sections. - let parsed = pem::parse_many(&buffer); + let parsed = pem::parse_many(&buffer).map_err(|_| WebPushError::InvalidCryptoKeys)?; let found_pkcs8 = parsed.iter().any(|pem| pem.tag == "PRIVATE KEY"); let found_sec1 = parsed.iter().any(|pem| pem.tag == "EC PRIVATE KEY"); @@ -184,10 +238,7 @@ impl<'a> VapidSignatureBuilder<'a> { let key = sec1_decode::parse_pem(buffer.as_bytes()).map_err(|_| WebPushError::InvalidCryptoKeys)?; Ok(ES256KeyPair::from_bytes(&key.key).map_err(|_| WebPushError::InvalidCryptoKeys)?) } else if found_pkcs8 { - let key = - pkcs8::PrivateKeyDocument::from_pem(buffer.as_str()).map_err(|_| WebPushError::InvalidCryptoKeys)?; - Ok(ES256KeyPair::from_bytes(key.private_key_info().private_key) - .map_err(|_| WebPushError::InvalidCryptoKeys)?) + Ok(ES256KeyPair::from_pem(&buffer).map_err(|_| WebPushError::InvalidCryptoKeys)?) } else { Err(WebPushError::MissingCryptoKeys) } @@ -268,9 +319,11 @@ mod tests { ).unwrap(); } + static PRIVATE_BASE64: &str = "IQ9Ur0ykXoHS9gzfYX0aBjy9lvdrjx_PFUXmie9YRcY"; + #[test] fn test_builder_from_pem() { - let builder = VapidSignatureBuilder::from_pem(&*PRIVATE_PEM, &*SUBSCRIPTION_INFO).unwrap(); + let builder = VapidSignatureBuilder::from_pem(&*PRIVATE_PEM, &SUBSCRIPTION_INFO).unwrap(); let signature = builder.build().unwrap(); assert_eq!( @@ -283,7 +336,7 @@ mod tests { #[test] fn test_builder_from_der() { - let builder = VapidSignatureBuilder::from_der(&*PRIVATE_DER, &*SUBSCRIPTION_INFO).unwrap(); + let builder = VapidSignatureBuilder::from_der(&*PRIVATE_DER, &SUBSCRIPTION_INFO).unwrap(); let signature = builder.build().unwrap(); assert_eq!( @@ -293,4 +346,18 @@ mod tests { assert!(!signature.auth_t.is_empty()); } + + #[test] + fn test_builder_from_base64() { + let builder = + VapidSignatureBuilder::from_base64(PRIVATE_BASE64, base64::URL_SAFE_NO_PAD, &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) + ); + + assert!(!signature.auth_t.is_empty()); + } }