Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notarization through the Notary API #593

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
853 changes: 583 additions & 270 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ members = [
'x509-certificate',
]
resolver = "2"

[patch.crates-io]
rpm-rs = { git = "https://github.com/roblabla/rpm-rs", branch = "update-packages" }
10 changes: 7 additions & 3 deletions apple-codesign/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ path = "src/main.rs"

[dependencies]
anyhow = "1.0"
aws-config = "0.13.0"
aws-sdk-s3 = "0.13.0"
aws-smithy-http = "0.43.0"
base64 = "0.13"
bcder = "0.7"
bitflags = "1.2"
Expand All @@ -25,7 +28,7 @@ chrono = "0.4"
der = "0.5"
dialoguer = "0.10"
difference = "2.0"
digest = "0.9"
digest = "0.10"
dirs = "4.0"
duct = "0.13"
elliptic-curve = { version = "0.11", features = ["arithmetic", "pkcs8"] }
Expand All @@ -52,9 +55,9 @@ rasn = "0.6"
regex = "1.5"
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] }
ring = "0.16"
rsa = "0.5"
rsa = "0.6"
scroll = "0.11"
sha2 = "0.9"
sha2 = "0.10"
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand All @@ -65,6 +68,7 @@ spki = { version = "0.5", features = ["pem"] }
subtle = "2.4"
tempfile = "3.3"
thiserror = "1.0"
tokio = { version = "1.19", features = ["rt"] }
tungstenite = { version = "0.17", features = ["rustls-tls-native-roots"] }
uuid = { version = "1.1", features = ["v4"] }
which = "4.2"
Expand Down
227 changes: 227 additions & 0 deletions apple-codesign/src/app_store_connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,156 @@ pub struct MoreInfo {
pub hash: Option<String>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSubmissionRequestNotification {
pub channel: String,
pub target: String,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSubmissionRequest {
pub notifications: Vec<NewSubmissionRequestNotification>,
pub sha256: String,
pub submission_name: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSubmissionResponseDataAttributes {
pub aws_access_key_id: String,
pub aws_secret_access_key: String,
pub aws_session_token: String,
pub bucket: String,
pub object: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSubmissionResponseData {
pub attributes: NewSubmissionResponseDataAttributes,
pub id: String,
pub r#type: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSubmissionResponse {
pub data: NewSubmissionResponseData,
pub meta: Value,
}

const APPLE_NOTARY_SUBMIT_SOFTWARE_URL: &str = "https://appstoreconnect.apple.com/notary/v2/submissions";

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum SubmissionResponseStatus {
Accepted,
#[serde(rename = "In Progress")]
InProgress,
Invalid,
Rejected,
#[serde(other)]
Unknown,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionResponseDataAttributes {
pub created_date: String,
pub name: String,
pub status: SubmissionResponseStatus,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionResponseData {
pub attributes: SubmissionResponseDataAttributes,
pub id: String,
pub r#type: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionResponse {
pub data: SubmissionResponseData,
pub meta: Value,
}

impl SubmissionResponse {
/// Convert the instance into a [Result].
///
/// Will yield [Err] if the notarization/upload was not successful.
pub fn into_result(self) -> Result<Self, AppleCodesignError> {
match self.data.attributes.status {
SubmissionResponseStatus::Accepted => Ok(self),
SubmissionResponseStatus::InProgress => Err(AppleCodesignError::NotarizeIncomplete),
SubmissionResponseStatus::Invalid => Err(AppleCodesignError::NotarizeInvalid),
SubmissionResponseStatus::Rejected => Err(AppleCodesignError::NotarizeRejected(0, "Notarization error".into())),
SubmissionResponseStatus::Unknown => Err(AppleCodesignError::NotarizeInvalid),
}
}
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionLogResponseDataAttributes {
developer_log_url: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionLogResponseData {
pub attributes: SubmissionLogResponseDataAttributes,
pub id: String,
pub r#type: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmissionLogResponse {
pub data: SubmissionLogResponseData,
pub meta: Value,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Issue {
pub architecture: String,
pub code: Option<u64>,
pub doc_url: Option<String>,
pub message: String,
pub path: String,
pub severity: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TicketContent {
pub arch: String,
pub cdhash: String,
pub digest_algorithm: String,
pub path: String,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotarizationLogs {
pub archive_filename: String,
#[serde(default)]
pub issues: Vec<Issue>,
pub job_id: String,
pub log_format_version: u64,
pub sha256: String,
pub status: SubmissionResponseStatus,
pub status_code: u64,
pub status_summary: String,
#[serde(default)]
pub ticket_contents: Vec<TicketContent>,
pub upload_date: String,
}
Comment on lines +332 to +367
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, how did you derive these types? AFAICT they are undocumented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at what data the endpoint was returning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also another thing: I found out by trial and error that the correct S3 region to upload the data to is us-west-2, but this is not documented anywhere... I'm not sure how apple expects us to figure this out.


/// A client for App Store Connect API.
///
/// The client isn't generic. Don't get any ideas.
Expand Down Expand Up @@ -285,4 +435,81 @@ impl AppStoreConnectClient {

Ok(dev_id_response)
}

pub fn create_submission(&self, sha256: &str, submission_name: &str) -> Result<NewSubmissionResponse, AppleCodesignError> {
let token = {
let mut token = self.token.lock().unwrap();

if token.is_none() {
token.replace(self.connect_token.new_token(300)?);
}

token.as_ref().unwrap().clone()
};

let body = NewSubmissionRequest {
notifications: Vec::new(),
sha256: sha256.to_string(),
submission_name: submission_name.to_string(),
};
let req = self.client.post(APPLE_NOTARY_SUBMIT_SOFTWARE_URL)
.bearer_auth(token)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.json(&body);

let response = req.send()?;

let res_data = response.json::<NewSubmissionResponse>()?;

Ok(res_data)
}

pub fn get_submission(&self, submission_id: &str) -> Result<SubmissionResponse, AppleCodesignError> {
let token = {
let mut token = self.token.lock().unwrap();

if token.is_none() {
token.replace(self.connect_token.new_token(300)?);
}

token.as_ref().unwrap().clone()
};

let req = self.client.get(format!("https://appstoreconnect.apple.com/notary/v2/submissions/{}", submission_id))
.bearer_auth(token)
.header("Accept", "application/json");

let response = req.send()?;

let res_data = response.json::<SubmissionResponse>()?;

Ok(res_data)
}

pub fn get_submission_log(&self, submission_id: &str) -> Result<Value, AppleCodesignError> {
let token = {
let mut token = self.token.lock().unwrap();

if token.is_none() {
token.replace(self.connect_token.new_token(300)?);
}

token.as_ref().unwrap().clone()
};

let req = self.client.get(format!("https://appstoreconnect.apple.com/notary/v2/submissions/{}/logs", submission_id))
.bearer_auth(token)
.header("Accept", "application/json");

let response = req.send()?;

let res_data = response.json::<SubmissionLogResponse>()?;

let url = res_data.data.attributes.developer_log_url;

let logs = self.client.get(url).send()?.json::<Value>()?;

Ok(logs)
}
}
2 changes: 1 addition & 1 deletion apple-codesign/src/cryptography.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use {
},
ring::signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair},
rsa::{
algorithms::mgf1_xor, pkcs1::FromRsaPrivateKey, BigUint, PaddingScheme,
algorithms::mgf1_xor, pkcs1::DecodeRsaPrivateKey, BigUint, PaddingScheme,
RsaPrivateKey as RsaConstructedKey,
},
signature::Signer,
Expand Down
9 changes: 9 additions & 0 deletions apple-codesign/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ pub enum AppleCodesignError {
#[error("notarization is incomplete (no status code and message)")]
NotarizeIncomplete,

#[error("notarization package is invalid")]
NotarizeInvalid,

#[error("no log file URL in notarization status")]
NotarizeNoLogUrl,

Expand Down Expand Up @@ -350,4 +353,10 @@ pub enum AppleCodesignError {

#[error("remote signing error: {0}")]
RemoteSign(#[from] RemoteSignError),

#[error("bytestream creation error: {0}")]
AwsByteStream(#[from] aws_smithy_http::byte_stream::Error),

#[error("s3 upload error: {0}")]
AwsS3Error(#[from] aws_sdk_s3::Error),
}
1 change: 0 additions & 1 deletion apple-codesign/src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use {
item::{ItemClass, ItemSearchOptions, Reference, SearchResult},
key::{Algorithm as KeychainAlgorithm, SecKey},
os::macos::{
encrypt_transform::{Builder as EncryptBuilder, Padding},
item::ItemSearchOptionsExt,
keychain::{SecKeychain, SecPreferencesDomain},
},
Expand Down
29 changes: 1 addition & 28 deletions apple-codesign/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1686,32 +1686,6 @@ fn command_extract(args: &ArgMatches) -> Result<(), AppleCodesignError> {
Ok(())
}

fn command_find_transporter() -> Result<(), AppleCodesignError> {
if let Some(path) = crate::notarization::find_transporter_exe() {
println!("{}", path.display())
} else {
indoc::eprintdoc! {"
Apple Transporter not found.

This executable is needed to perform notarization.

Instructions for installing the application are available at
https://help.apple.com/itc/transporteruserguide/#/apdAbeb95d60

We looked in PATH and in common install locations but could not find
transporter.

To force usage of a specific executable, set the {env}
environment variable to the path of the executable to use.
",
env=crate::notarization::TRANSPORTER_PATH_ENV_VARIABLE
};
std::process::exit(1);
}

Ok(())
}

fn command_generate_certificate_signing_request(
args: &ArgMatches,
) -> Result<(), AppleCodesignError> {
Expand Down Expand Up @@ -1946,7 +1920,7 @@ fn command_notarize(args: &ArgMatches) -> Result<(), AppleCodesignError> {
"NotarizationUpload::UploadId should not be returned if we waited successfully"
);
}
crate::notarization::NotarizationUpload::DevIdResponse(_) => {
crate::notarization::NotarizationUpload::NotaryResponse(_) => {
let stapler = crate::stapling::Stapler::new()?;
stapler.staple_path(&path)?;
}
Expand Down Expand Up @@ -2984,7 +2958,6 @@ fn main_impl() -> Result<(), AppleCodesignError> {
Some(("compute-code-hashes", args)) => command_compute_code_hashes(args),
Some(("diff-signatures", args)) => command_diff_signatures(args),
Some(("extract", args)) => command_extract(args),
Some(("find-transporter", _)) => command_find_transporter(),
Some(("generate-certificate-signing-request", args)) => {
command_generate_certificate_signing_request(args)
}
Expand Down
Loading