app_store_connect/
certs_api.rs1use crate::{AppStoreConnectClient, Result};
8use base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine};
9use rand::rngs::OsRng;
10use rsa::pkcs8::{EncodePrivateKey, LineEnding};
11use rsa::RsaPrivateKey;
12use serde::{Deserialize, Serialize};
13use std::fs::File;
14use std::io::Write;
15use std::path::Path;
16use x509_certificate::{InMemorySigningKeyPair, X509CertificateBuilder};
17
18pub fn generate_signing_certificate(api_key: &Path, ty: CertificateType, pem: &Path) -> Result<()> {
19 let secret = RsaPrivateKey::new(&mut OsRng, 2048)?;
20 let key = InMemorySigningKeyPair::from_pkcs8_der(secret.to_pkcs8_der()?.as_bytes())?;
21 let mut builder = X509CertificateBuilder::default();
22 builder
23 .subject()
24 .append_common_name_utf8_string("Apple Code Signing CSR")
25 .expect("only valid chars");
26 let csr = builder
27 .create_certificate_signing_request(&key)?
28 .encode_pem()?;
29 let cer = AppStoreConnectClient::from_json_path(api_key)?
30 .create_certificate(csr, ty)?
31 .data
32 .attributes
33 .certificate_content;
34 let cer = pem::encode(&pem::Pem::new("CERTIFICATE", STANDARD_ENGINE.decode(cer)?));
35 let mut f = File::create(pem)?;
36 f.write_all(secret.to_pkcs8_pem(LineEnding::CRLF)?.as_bytes())?;
37 f.write_all(cer.as_bytes())?;
38 Ok(())
39}
40
41const APPLE_CERTIFICATE_URL: &str = "https://api.appstoreconnect.apple.com/v1/certificates";
42
43impl AppStoreConnectClient {
44 pub fn create_certificate(
45 &self,
46 csr: String,
47 ty: CertificateType,
48 ) -> Result<CertificateResponse> {
49 let token = self.get_token()?;
50 let body = CertificateCreateRequest {
51 data: CertificateCreateRequestData {
52 attributes: CertificateCreateRequestAttributes {
53 certificate_type: ty.to_string(),
54 csr_content: csr,
55 },
56 r#type: "certificates".into(),
57 },
58 };
59 let req = self
60 .client
61 .post(APPLE_CERTIFICATE_URL)
62 .bearer_auth(token)
63 .header("Accept", "application/json")
64 .header("Content-Type", "application/json")
65 .json(&body);
66 Ok(self.send_request(req)?.json()?)
67 }
68
69 pub fn list_certificates(&self) -> Result<CertificatesResponse> {
70 let token = self.get_token()?;
71 let req = self
72 .client
73 .get(APPLE_CERTIFICATE_URL)
74 .bearer_auth(token)
75 .header("Accept", "application/json");
76 Ok(self.send_request(req)?.json()?)
77 }
78
79 pub fn get_certificate(&self, id: &str) -> Result<CertificateResponse> {
80 let token = self.get_token()?;
81 let req = self
82 .client
83 .get(format!("{APPLE_CERTIFICATE_URL}/{id}"))
84 .bearer_auth(token)
85 .header("Accept", "application/json");
86 Ok(self.send_request(req)?.json()?)
87 }
88
89 pub fn revoke_certificate(&self, id: &str) -> Result<()> {
90 let token = self.get_token()?;
91 let req = self
92 .client
93 .delete(format!("{APPLE_CERTIFICATE_URL}/{id}"))
94 .bearer_auth(token);
95 self.send_request(req)?;
96 Ok(())
97 }
98}
99
100#[derive(Debug, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct CertificateCreateRequest {
103 pub data: CertificateCreateRequestData,
104}
105
106#[derive(Debug, Serialize)]
107#[serde(rename_all = "camelCase")]
108pub struct CertificateCreateRequestData {
109 pub attributes: CertificateCreateRequestAttributes,
110 pub r#type: String,
111}
112
113#[derive(Debug, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct CertificateCreateRequestAttributes {
116 pub certificate_type: String,
117 pub csr_content: String,
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
121pub enum CertificateType {
122 Development,
123 Distribution,
124 DeveloperIdApplication,
125 IosDistribution,
126}
127
128impl std::fmt::Display for CertificateType {
129 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
130 let s = match self {
131 Self::Development => "DEVELOPMENT",
132 Self::Distribution => "DISTRIBUTION",
133 Self::DeveloperIdApplication => "DEVELOPER_ID_APPLICATION",
134 Self::IosDistribution => "IOS_DISTRIBUTION",
135 };
136 write!(f, "{s}")
137 }
138}
139
140#[derive(Debug, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct CertificateResponse {
143 pub data: Certificate,
144}
145
146#[derive(Debug, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct CertificatesResponse {
149 pub data: Vec<Certificate>,
150}
151
152#[derive(Debug, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct Certificate {
155 pub attributes: CertificateAttributes,
156 pub id: String,
157}
158
159#[derive(Debug, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct CertificateAttributes {
162 pub certificate_content: String,
163 pub display_name: String,
164 pub expiration_date: String,
165 pub name: String,
166 pub platform: Option<String>,
167 pub serial_number: String,
168 pub certificate_type: String,
169}