Skip to main content

rustfs_crypto/
license_token.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use rsa::{
16    Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey,
17    pkcs8::{DecodePrivateKey, DecodePublicKey},
18    pss::{BlindedSigningKey, Signature, VerifyingKey},
19    sha2::Sha256,
20    signature::{RandomizedSigner, Verifier},
21    traits::PublicKeyParts,
22};
23use serde::{Deserialize, Serialize};
24use std::io::{Error, ErrorKind, Result};
25
26#[derive(Serialize, Deserialize, Debug, Default, Clone)]
27pub struct Token {
28    pub name: String, // Application ID
29    pub expired: u64, // Expiry time (UNIX timestamp)
30}
31
32/// Legacy public-key encryption Token encoder.
33///
34/// Use `sign_license_token` for license issuance so verifiers only need a
35/// public key.
36#[deprecated(note = "use sign_license_token for signed license issuance")]
37pub fn gencode(token: &Token, key: &str) -> Result<String> {
38    let data = serde_json::to_vec(token)?;
39    let mut rng = rand::rng();
40    let public_key = RsaPublicKey::from_public_key_pem(key).map_err(Error::other)?;
41    let encrypted_data = public_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data).map_err(Error::other)?;
42    Ok(base64_simd::URL_SAFE_NO_PAD.encode_to_string(&encrypted_data))
43}
44
45/// Legacy private-key Token decoder.
46///
47/// Use `parse_signed_license_token` or `parse_license_with_public_key` for
48/// license verification so runtime services never need private key material.
49#[deprecated(note = "use parse_signed_license_token or parse_license_with_public_key for signed license verification")]
50pub fn parse(token: &str, key: &str) -> Result<Token> {
51    let encrypted_data = base64_simd::URL_SAFE_NO_PAD
52        .decode_to_vec(token.as_bytes())
53        .map_err(Error::other)?;
54    let private_key = RsaPrivateKey::from_pkcs8_pem(key).map_err(Error::other)?;
55    let decrypted_data = private_key.decrypt(Pkcs1v15Encrypt, &encrypted_data).map_err(Error::other)?;
56    serde_json::from_slice(&decrypted_data).map_err(Error::other)
57}
58
59/// Signs a license token with an RSA private key.
60///
61/// The returned token is base64url(signature || payload), where the signature is
62/// RSASSA-PSS over the JSON payload using SHA-256.
63pub fn sign_license_token(token: &Token, private_key_pem: &str) -> Result<String> {
64    let payload = serde_json::to_vec(token)?;
65    let mut rng = rand::rng();
66    let private_key = RsaPrivateKey::from_pkcs8_pem(private_key_pem).map_err(Error::other)?;
67    let signing_key = BlindedSigningKey::<Sha256>::new(private_key);
68    let signature: Signature = signing_key.try_sign_with_rng(&mut rng, &payload).map_err(Error::other)?;
69    let signature: Box<[u8]> = signature.into();
70
71    let mut signed_payload = Vec::with_capacity(signature.as_ref().len() + payload.len());
72    signed_payload.extend_from_slice(signature.as_ref());
73    signed_payload.extend_from_slice(&payload);
74
75    Ok(base64_simd::URL_SAFE_NO_PAD.encode_to_string(&signed_payload))
76}
77
78/// Verifies and parses a signed license token with an RSA public key.
79pub fn parse_signed_license_token(token: &str, public_key_pem: &str) -> Result<Token> {
80    let signed_payload = base64_simd::URL_SAFE_NO_PAD
81        .decode_to_vec(token.as_bytes())
82        .map_err(Error::other)?;
83    let public_key = RsaPublicKey::from_public_key_pem(public_key_pem).map_err(Error::other)?;
84    let signature_len = public_key.size();
85
86    if signed_payload.len() <= signature_len {
87        return Err(Error::new(ErrorKind::InvalidData, "license token is missing signed payload"));
88    }
89
90    let (signature, payload) = signed_payload.split_at(signature_len);
91    let signature = Signature::try_from(signature).map_err(Error::other)?;
92    let verifying_key = VerifyingKey::<Sha256>::new(public_key);
93    verifying_key.verify(payload, &signature).map_err(Error::other)?;
94
95    serde_json::from_slice(payload).map_err(Error::other)
96}
97
98pub fn parse_license_with_public_key(license: &str, public_key: &str) -> Result<Token> {
99    parse_signed_license_token(license, public_key)
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use rsa::{
106        RsaPrivateKey,
107        pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
108    };
109    use std::time::{SystemTime, UNIX_EPOCH};
110
111    #[test]
112    fn test_sign_license_token_and_parse_signed_license_token() {
113        let mut rng = rand::rng();
114        let bits = 2048;
115        let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
116        let public_key = RsaPublicKey::from(&private_key);
117
118        let private_key_pem = private_key
119            .to_pkcs8_pem(LineEnding::LF)
120            .expect("failed to encode private key pem");
121        let public_key_pem = public_key
122            .to_public_key_pem(LineEnding::LF)
123            .expect("failed to encode public key pem");
124
125        let token = Token {
126            name: "test_app".to_string(),
127            expired: SystemTime::now()
128                .duration_since(UNIX_EPOCH)
129                .expect("system clock before unix epoch")
130                .as_secs()
131                + 3600, // 1 hour from now
132        };
133
134        let encoded = sign_license_token(&token, &private_key_pem).expect("Failed to encode token");
135
136        let decoded = parse_signed_license_token(&encoded, &public_key_pem).expect("Failed to decode token");
137
138        assert_eq!(token.name, decoded.name);
139        assert_eq!(token.expired, decoded.expired);
140    }
141
142    #[test]
143    #[allow(deprecated)]
144    fn test_legacy_gencode_and_parse_roundtrip() {
145        let mut rng = rand::rng();
146        let bits = 2048;
147        let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
148        let public_key = RsaPublicKey::from(&private_key);
149
150        let private_key_pem = private_key
151            .to_pkcs8_pem(LineEnding::LF)
152            .expect("failed to encode private key pem");
153        let public_key_pem = public_key
154            .to_public_key_pem(LineEnding::LF)
155            .expect("failed to encode public key pem");
156
157        let token = Token {
158            name: "test_app".to_string(),
159            expired: SystemTime::now()
160                .duration_since(UNIX_EPOCH)
161                .expect("system clock before unix epoch")
162                .as_secs()
163                + 3600,
164        };
165
166        let encoded = gencode(&token, &public_key_pem).expect("Failed to encode token");
167        let decoded = parse(&encoded, &private_key_pem).expect("Failed to decode token");
168
169        assert_eq!(token.name, decoded.name);
170        assert_eq!(token.expired, decoded.expired);
171    }
172
173    #[test]
174    fn test_parse_signed_license_token_rejects_tampered_payload() {
175        let mut rng = rand::rng();
176        let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("Failed to generate private key");
177        let public_key = RsaPublicKey::from(&private_key);
178        let private_key_pem = private_key
179            .to_pkcs8_pem(LineEnding::LF)
180            .expect("failed to encode private key pem");
181        let public_key_pem = public_key
182            .to_public_key_pem(LineEnding::LF)
183            .expect("failed to encode public key pem");
184        let token = Token {
185            name: "test_app".to_string(),
186            expired: SystemTime::now()
187                .duration_since(UNIX_EPOCH)
188                .expect("system clock before unix epoch")
189                .as_secs()
190                + 3600,
191        };
192
193        let encoded = sign_license_token(&token, &private_key_pem).expect("Failed to encode token");
194        let mut signed_payload = base64_simd::URL_SAFE_NO_PAD
195            .decode_to_vec(encoded.as_bytes())
196            .expect("Failed to decode signed payload");
197        let last_byte = signed_payload.last_mut().expect("Signed payload should not be empty");
198        *last_byte ^= 0x01;
199        let tampered = base64_simd::URL_SAFE_NO_PAD.encode_to_string(&signed_payload);
200
201        let result = parse_signed_license_token(&tampered, &public_key_pem);
202
203        assert!(result.is_err());
204    }
205
206    #[test]
207    fn test_parse_signed_license_token_rejects_invalid_token() {
208        let mut rng = rand::rng();
209        let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("Failed to generate private key");
210        let public_key = RsaPublicKey::from(&private_key);
211        let public_key_pem = public_key
212            .to_public_key_pem(LineEnding::LF)
213            .expect("failed to encode public key pem");
214
215        let invalid_token = "invalid_base64_token";
216        let result = parse_signed_license_token(invalid_token, &public_key_pem);
217
218        assert!(result.is_err());
219    }
220
221    #[test]
222    fn test_sign_license_token_with_invalid_signing_key() {
223        let token = Token {
224            name: "test_app".to_string(),
225            expired: SystemTime::now()
226                .duration_since(UNIX_EPOCH)
227                .expect("system clock before unix epoch")
228                .as_secs()
229                + 3600, // 1 hour from now
230        };
231
232        let invalid_key = "invalid_private_key";
233        let result = sign_license_token(&token, invalid_key);
234
235        assert!(result.is_err());
236    }
237}