app_store_connect/
api_key.rs1use {
10 crate::{ConnectTokenEncoder, Error, Result},
11 anyhow::Context,
12 base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine},
13 serde::{Deserialize, Serialize},
14 std::{fs::Permissions, io::Write, path::Path},
15};
16
17#[cfg(unix)]
18use std::os::unix::fs::PermissionsExt;
19
20#[cfg(unix)]
21fn set_permissions_private(p: &mut Permissions) {
22 p.set_mode(0o600);
23}
24
25#[cfg(windows)]
26fn set_permissions_private(_: &mut Permissions) {}
27
28#[derive(Clone, Debug, Deserialize, Serialize)]
35pub struct UnifiedApiKey {
36 issuer_id: String,
40
41 key_id: String,
45
46 private_key: String,
48}
49
50impl UnifiedApiKey {
51 pub fn from_ecdsa_pem_path(
56 issuer_id: impl ToString,
57 key_id: impl ToString,
58 path: impl AsRef<Path>,
59 ) -> Result<Self> {
60 let pem_data = std::fs::read(path.as_ref())?;
61
62 let parsed = pem::parse(pem_data).map_err(|_| InvalidPemPrivateKey)?;
63
64 if parsed.tag() != "PRIVATE KEY" {
65 return Err(InvalidPemPrivateKey.into());
66 }
67
68 let private_key = STANDARD_ENGINE.encode(parsed.contents());
69
70 Ok(Self {
71 issuer_id: issuer_id.to_string(),
72 key_id: key_id.to_string(),
73 private_key,
74 })
75 }
76
77 pub fn from_json(data: impl AsRef<[u8]>) -> Result<Self> {
79 Ok(serde_json::from_slice(data.as_ref())?)
80 }
81
82 pub fn from_json_path(path: impl AsRef<Path>) -> Result<Self> {
84 let data = std::fs::read(path.as_ref())?;
85
86 Self::from_json(data)
87 }
88
89 pub fn to_json_string(&self) -> Result<String> {
91 Ok(serde_json::to_string_pretty(&self)?)
92 }
93
94 pub fn write_json_file(&self, path: impl AsRef<Path>) -> Result<()> {
103 let path = path.as_ref();
104
105 if let Some(parent) = path.parent() {
106 std::fs::create_dir_all(parent)?;
107 }
108
109 let data = self.to_json_string()?;
110
111 let mut fh = std::fs::File::create(path)?;
112 let mut permissions = fh.metadata()?.permissions();
113 set_permissions_private(&mut permissions);
114 fh.set_permissions(permissions)?;
115 fh.write_all(data.as_bytes())?;
116
117 Ok(())
118 }
119}
120
121impl TryFrom<UnifiedApiKey> for ConnectTokenEncoder {
122 type Error = anyhow::Error;
123
124 fn try_from(value: UnifiedApiKey) -> Result<Self> {
125 let der = STANDARD_ENGINE
126 .decode(value.private_key)
127 .context("invalid unified api key")?;
128
129 Self::from_ecdsa_der(value.key_id, value.issuer_id, &der)
130 }
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
134#[error("invalid PEM formatted private key")]
135pub struct InvalidPemPrivateKey;