#!/usr/bin/env swift // Generic ASC API helper: asc.swift [json-body] import Foundation import CryptoKit func die(_ msg: String) -> Never { FileHandle.standardError.write((msg + "\n").data(using: .utf8)!) exit(1) } func b64url(_ data: Data) -> String { data.base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: "=", with: "") } let env = ProcessInfo.processInfo.environment guard let keyID = env["ASC_KEY_ID"], let issuer = env["ASC_ISSUER_ID"], let keyPath = env["ASC_KEY_PATH"] else { die("missing ASC env") } guard CommandLine.arguments.count >= 3 else { die("usage: asc.swift [body]") } let method = CommandLine.arguments[1].uppercased() let path = CommandLine.arguments[2] let body = CommandLine.arguments.count > 3 ? CommandLine.arguments[3].data(using: .utf8) : nil guard let pem = try? String(contentsOfFile: keyPath, encoding: .utf8), let key = try? P256.Signing.PrivateKey(pemRepresentation: pem) else { die("key error") } let now = Int(Date().timeIntervalSince1970) let header = "{\"alg\":\"ES256\",\"kid\":\"\(keyID)\",\"typ\":\"JWT\"}" let payload = "{\"iss\":\"\(issuer)\",\"iat\":\(now),\"exp\":\(now + 1200),\"aud\":\"appstoreconnect-v1\"}" let signingInput = b64url(Data(header.utf8)) + "." + b64url(Data(payload.utf8)) guard let sig = try? key.signature(for: Data(signingInput.utf8)) else { die("signing failed") } let jwt = signingInput + "." + b64url(sig.rawRepresentation) let base = path.hasPrefix("v2/") ? "https://api.appstoreconnect.apple.com/" : "https://api.appstoreconnect.apple.com/v1/" var req = URLRequest(url: URL(string: base + path)!) req.httpMethod = method req.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization") if let body { req.httpBody = body req.setValue("application/json", forHTTPHeaderField: "Content-Type") } let sem = DispatchSemaphore(value: 0) URLSession.shared.dataTask(with: req) { d, resp, err in if let err { die("network: \(err)") } let status = (resp as? HTTPURLResponse)?.statusCode ?? 0 print("HTTP \(status)") if let d, !d.isEmpty { if let obj = try? JSONSerialization.jsonObject(with: d), let pretty = try? JSONSerialization.data(withJSONObject: obj, options: [.prettyPrinted, .sortedKeys]) { print(String(data: pretty, encoding: .utf8)!) } else { print(String(data: d, encoding: .utf8) ?? "") } } sem.signal() }.resume() sem.wait()