container = $container; } public function createIndexV1Array() { /** @var EntityManager $em */ $em = $this->container->get(EntityManager::class); /** @var App[] $apps */ $apps = $em ->getRepository(App::class) ->findBy([ 'status' => App::STATUS_APPROVED ]); $appObjects = []; $packageObjects = []; /** @var App $app */ foreach ($apps as $app) { $appObjects[] = [ "authorEmail" => "example@example.com", "authorName" => "example", "authorWebSite" => "https://example.org", "categories" => [ 'example' ], "suggestedVersionCode" => $app->getReleases()->first()->getVersionCode(), "name" => $app->getLabel(), "added" => 0, "packageName" => $app->getName(), "lastUpdated" => 0, "license" => "no" ]; $packages = []; foreach ($app->getReleases() as $release) { $packages[] = [ "added" => 0, "apkName" => "/apk/" . $app->getName() . "/" . $release->getVersionCode() . ".apk", "hash" => hash('sha256', $release->getApk()), "hashType" => "sha256", "minSdkVersion" => 4, "packageName" => $app->getName(), "sig" => "deadbeef", "signer" => "deadbeef", "size" => filesize($release->getApk()), "targetSdkVersion" => 28, "versionCode" => $release->getVersionCode(), "versionName" => $release->getVersionName() ]; } $packageObjects[$app->getName()] = $packages; } return [ 'repo' => [ 'timestamp' => time() * 1000, 'version' => 21, 'maxage' => 14, 'name' => $this->container->get('app.name'), 'description' => $this->container->get('app.description'), 'address' => "https://" . $this->container->get('app.domain') . '/repo' ], 'requests' => [ 'install' => [], 'uninstall' => [], ], "apps" => $appObjects, "packages" => $packageObjects ]; } /** * Create's a signed jar with a single file. * * A jar is a zip file. * Contains the following files: * * META-INF/MANIFEST.MF * Contains manifest header * Contains header (and hashes) for files * * META-INF/1.SF * Contains hash of full manifest, manifest header and file header * * META-INF/1.RSA * PKCS7 Signature of META-INF/1.SF * * @param string $file The filename of the file to add in the jar * @param string $contents The contents of the file to add in the jar * @return string */ public function createSigned($file, $contents) { $zip = new ZipArchive(); $zipPath = tempnam(sys_get_temp_dir(), 'zip'); if ($zipPath === false) { throw new RuntimeException("Failed to create temp file"); } $fileDigest = hash('sha256', $contents, true); $zip->open($zipPath, ZipArchive::CREATE); $fileHeader = 'Name: ' . $file . "\n"; $fileHeader .= 'SHA-256-Digest: ' . base64_encode($fileDigest) . "\n\n"; $fileHeaderDigest = hash('sha256', $fileHeader, true); $manifest = "Manifest-Version: 1.0\n"; $manifest .= "Created-By: CubiStore\n\n"; $manifestHeaderDigest = hash('sha256', $manifest, true); $manifest .= $fileHeader; $manifestDigest = hash('sha256', $manifest, true); $fileManifest = "Signature-Version: 1.0\n"; $fileManifest .= "SHA-256-Digest-Manifest-Main-Attributes: " . base64_encode($manifestHeaderDigest) . "\n"; $fileManifest .= "SHA-256-Digest-Manifest: " . base64_encode($manifestDigest) . "\n"; $fileManifest .= "Created-By: CubiStore\n\n"; $fileManifest .= "Name: " . $file . "\n"; $fileManifest .= "SHA-256-Digest: " . base64_encode($fileHeaderDigest) . "\n\n"; $zip->addFromString('META-INF/MANIFEST.MF', $manifest); $zip->addFromString('META-INF/1.SF', $fileManifest); $in = tempnam(sys_get_temp_dir(), 'repo'); if ($in === false) { throw new RuntimeException("Failed to create temp file"); } $out = tempnam(sys_get_temp_dir(), 'repo'); if ($out === false) { throw new RuntimeException("Failed to create temp file"); } file_put_contents($in, $fileManifest); $success = openssl_pkcs7_sign( $in, $out, 'file://' . __DIR__ . '/../../certs/cert.pem', 'file://' . __DIR__ . '/../../certs/key.pem', [], PKCS7_BINARY | PKCS7_NOATTR ); if (!$success) { $error = ''; while ($line = openssl_error_string()) { $error .= $line . "\n"; } throw new RuntimeException($error); } unlink($in); $signed = file_get_contents($out); unlink($out); if ($signed === false) { throw new RuntimeException("Signature wasn't created"); } $contentOffset = strpos($signed, "\n\n"); if ($contentOffset === false) { throw new RuntimeException("Signature is not as expected"); } $content = substr($signed, $contentOffset); $base64 = str_replace("\n", "", $content); $der = base64_decode($base64); if ($der === false) { throw new RuntimeException("Failed to decode DER from signature"); } $zip->addFromString('META-INF/1.RSA', $der); $zip->addFromString($file, $contents); $zip->close(); $zipContent = file_get_contents($zipPath); unlink($zipPath); if ($zipContent === false) { return new RuntimeException("Failed to read resulting jar"); } return $zipContent; } }