jwt/algorithm/
openssl.rs

1//! OpenSSL support through the openssl crate.
2//! Note that private keys can only be used for signing and that public keys
3//! can only be used for verification.
4//! ## Examples
5//! ```
6//! use jwt::PKeyWithDigest;
7//! use openssl::hash::MessageDigest;
8//! use openssl::pkey::PKey;
9//! let pem = include_bytes!("../../test/rs256-public.pem");
10//! let rs256_public_key = PKeyWithDigest {
11//!     digest: MessageDigest::sha256(),
12//!     key: PKey::public_key_from_pem(pem).unwrap(),
13//! };
14//! ```
15
16use crate::algorithm::{AlgorithmType, SigningAlgorithm, VerifyingAlgorithm};
17use crate::error::Error;
18use crate::SEPARATOR;
19
20use openssl::bn::BigNum;
21use openssl::ecdsa::EcdsaSig;
22use openssl::hash::MessageDigest;
23use openssl::nid::Nid;
24use openssl::pkey::{Id, PKey, Private, Public};
25use openssl::sign::{Signer, Verifier};
26
27/// A wrapper class around [PKey](../../../openssl/pkey/struct.PKey.html) that
28/// associates the key with a
29/// [MessageDigest](../../../openssl/hash/struct.MessageDigest.html).
30pub struct PKeyWithDigest<T> {
31    pub digest: MessageDigest,
32    pub key: PKey<T>,
33}
34
35impl<T> PKeyWithDigest<T> {
36    fn algorithm_type(&self) -> AlgorithmType {
37        match (self.key.id(), self.digest.type_()) {
38            (Id::RSA, Nid::SHA256) => AlgorithmType::Rs256,
39            (Id::RSA, Nid::SHA384) => AlgorithmType::Rs384,
40            (Id::RSA, Nid::SHA512) => AlgorithmType::Rs512,
41            (Id::EC, Nid::SHA256) => AlgorithmType::Es256,
42            (Id::EC, Nid::SHA384) => AlgorithmType::Es384,
43            (Id::EC, Nid::SHA512) => AlgorithmType::Es512,
44            _ => panic!("Invalid algorithm type"),
45        }
46    }
47}
48
49impl SigningAlgorithm for PKeyWithDigest<Private> {
50    fn algorithm_type(&self) -> AlgorithmType {
51        PKeyWithDigest::algorithm_type(self)
52    }
53
54    fn sign(&self, header: &str, claims: &str) -> Result<String, Error> {
55        let mut signer = Signer::new(self.digest.clone(), &self.key)?;
56        signer.update(header.as_bytes())?;
57        signer.update(SEPARATOR.as_bytes())?;
58        signer.update(claims.as_bytes())?;
59        let signer_signature = signer.sign_to_vec()?;
60
61        let signature = if self.key.id() == Id::EC {
62            der_to_jose(&signer_signature)?
63        } else {
64            signer_signature
65        };
66
67        Ok(base64::encode_config(&signature, base64::URL_SAFE_NO_PAD))
68    }
69}
70
71impl VerifyingAlgorithm for PKeyWithDigest<Public> {
72    fn algorithm_type(&self) -> AlgorithmType {
73        PKeyWithDigest::algorithm_type(self)
74    }
75
76    fn verify_bytes(&self, header: &str, claims: &str, signature: &[u8]) -> Result<bool, Error> {
77        let mut verifier = Verifier::new(self.digest.clone(), &self.key)?;
78        verifier.update(header.as_bytes())?;
79        verifier.update(SEPARATOR.as_bytes())?;
80        verifier.update(claims.as_bytes())?;
81
82        let verified = if self.key.id() == Id::EC {
83            let der = jose_to_der(signature)?;
84            verifier.verify(&der)?
85        } else {
86            verifier.verify(signature)?
87        };
88
89        Ok(verified)
90    }
91}
92
93/// OpenSSL by default signs ECDSA in DER, but JOSE expects them in a concatenated (R, S) format
94fn der_to_jose(der: &[u8]) -> Result<Vec<u8>, Error> {
95    let signature = EcdsaSig::from_der(&der)?;
96    let r = signature.r().to_vec();
97    let s = signature.s().to_vec();
98    Ok([r, s].concat())
99}
100
101/// OpenSSL by default verifies ECDSA in DER, but JOSE parses out a concatenated (R, S) format
102fn jose_to_der(jose: &[u8]) -> Result<Vec<u8>, Error> {
103    let (r, s) = jose.split_at(jose.len() / 2);
104    let ecdsa_signature =
105        EcdsaSig::from_private_components(BigNum::from_slice(r)?, BigNum::from_slice(s)?)?;
106    Ok(ecdsa_signature.to_der()?)
107}
108
109#[cfg(test)]
110mod tests {
111    use crate::algorithm::openssl::PKeyWithDigest;
112    use crate::algorithm::AlgorithmType::*;
113    use crate::algorithm::{SigningAlgorithm, VerifyingAlgorithm};
114    use crate::error::Error;
115    use crate::header::PrecomputedAlgorithmOnlyHeader as AlgOnly;
116    use crate::ToBase64;
117
118    use openssl::hash::MessageDigest;
119    use openssl::pkey::PKey;
120
121    // {"sub":"1234567890","name":"John Doe","admin":true}
122    const CLAIMS: &'static str =
123        "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
124
125    const RS256_SIGNATURE: &'static str =
126    "cQsAHF2jHvPGFP5zTD8BgoJrnzEx6JNQCpupebWLFnOc2r_punDDTylI6Ia4JZNkvy2dQP-7W-DEbFQ3oaarHsDndqUgwf9iYlDQxz4Rr2nEZX1FX0-FMEgFPeQpdwveCgjtTYUbVy37ijUySN_rW-xZTrsh_Ug-ica8t-zHRIw";
127
128    #[test]
129    fn rs256_sign() -> Result<(), Error> {
130        let pem = include_bytes!("../../test/rs256-private.pem");
131
132        let algorithm = PKeyWithDigest {
133            digest: MessageDigest::sha256(),
134            key: PKey::private_key_from_pem(pem)?,
135        };
136
137        let result = algorithm.sign(&AlgOnly(Rs256).to_base64()?, CLAIMS)?;
138        assert_eq!(result, RS256_SIGNATURE);
139        Ok(())
140    }
141
142    #[test]
143    fn rs256_verify() -> Result<(), Error> {
144        let pem = include_bytes!("../../test/rs256-public.pem");
145
146        let algorithm = PKeyWithDigest {
147            digest: MessageDigest::sha256(),
148            key: PKey::public_key_from_pem(pem)?,
149        };
150
151        let verification_result =
152            algorithm.verify(&AlgOnly(Rs256).to_base64()?, CLAIMS, RS256_SIGNATURE)?;
153        assert!(verification_result);
154        Ok(())
155    }
156
157    #[test]
158    fn es256() -> Result<(), Error> {
159        let private_pem = include_bytes!("../../test/es256-private.pem");
160        let private_key = PKeyWithDigest {
161            digest: MessageDigest::sha256(),
162            key: PKey::private_key_from_pem(private_pem)?,
163        };
164
165        let signature = private_key.sign(&AlgOnly(Es256).to_base64()?, CLAIMS)?;
166
167        let public_pem = include_bytes!("../../test/es256-public.pem");
168
169        let public_key = PKeyWithDigest {
170            digest: MessageDigest::sha256(),
171            key: PKey::public_key_from_pem(public_pem)?,
172        };
173
174        let verification_result =
175            public_key.verify(&AlgOnly(Es256).to_base64()?, CLAIMS, &*signature)?;
176        assert!(verification_result);
177        Ok(())
178    }
179}