apple_siwa_client_secret/
lib.rs1use std::{error, fmt, time::Duration};
4
5use chrono::{serde::ts_seconds, DateTime, Duration as ChronoDuration, Utc};
6use jwt::{AlgorithmType, Error as JwtError, Header, PKeyWithDigest, SignWithKey, Token};
7use openssl::{ec::EcKey, error::ErrorStack as OpensslErrorStack, hash::MessageDigest, pkey::PKey};
8use serde::{Deserialize, Serialize};
9
10pub const AUDIENCE: &str = "https://appleid.apple.com";
11pub const EXPIRATION_TIME_DURATION_SECONDS_MAX: u64 = 15777000;
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct Claims {
17 pub iss: String,
18 #[serde(with = "ts_seconds")]
19 pub iat: DateTime<Utc>,
20 #[serde(with = "ts_seconds")]
21 pub exp: DateTime<Utc>,
22 pub aud: String,
23 pub sub: String,
24}
25
26pub fn create(
27 key_id: impl AsRef<str>,
28 p8_auth_key_bytes: impl AsRef<[u8]>,
29 team_id: impl AsRef<str>,
30 client_id: impl AsRef<str>,
31 issued_at: impl Into<Option<DateTime<Utc>>>,
32 expiration_time_dur: impl Into<Option<Duration>>,
33) -> Result<String, CreateError> {
34 let pkey = PKeyWithDigest {
36 digest: MessageDigest::sha256(),
37 key: PKey::from_ec_key(
38 EcKey::private_key_from_pem(p8_auth_key_bytes.as_ref())
39 .map_err(CreateError::MakeEcKeyFailed)?,
40 )
41 .map_err(CreateError::MakePKeyFailed)?,
42 };
43
44 let header = Header {
45 algorithm: AlgorithmType::Es256,
46 key_id: Some(key_id.as_ref().to_owned()),
47 ..Default::default()
48 };
49
50 let issued_at = issued_at.into().unwrap_or_else(Utc::now);
51 let mut expiration_time_dur = expiration_time_dur
52 .into()
53 .unwrap_or_else(|| Duration::from_secs(EXPIRATION_TIME_DURATION_SECONDS_MAX));
54 if expiration_time_dur.as_secs() > EXPIRATION_TIME_DURATION_SECONDS_MAX {
55 expiration_time_dur = Duration::from_secs(EXPIRATION_TIME_DURATION_SECONDS_MAX);
56 }
57 let expiration_time = issued_at + ChronoDuration::seconds(expiration_time_dur.as_secs() as i64);
58
59 let claims = Claims {
60 iss: team_id.as_ref().to_owned(),
61 iat: issued_at,
62 exp: expiration_time,
63 aud: AUDIENCE.to_owned(),
64 sub: client_id.as_ref().to_owned(),
65 };
66
67 let token = Token::new(header, claims)
68 .sign_with_key(&pkey)
69 .map_err(CreateError::TokenSignFailed)?;
70
71 Ok(token.as_str().to_owned())
72}
73
74#[derive(Debug)]
75pub enum CreateError {
76 MakeEcKeyFailed(OpensslErrorStack),
77 MakePKeyFailed(OpensslErrorStack),
78 TokenSignFailed(JwtError),
79}
80impl fmt::Display for CreateError {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{:?}", self)
83 }
84}
85impl error::Error for CreateError {}