New updates
This commit is contained in:
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env swift
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
let fm = FileManager.default
|
||||
let home = fm.homeDirectoryForCurrentUser
|
||||
let directory = home.appendingPathComponent("Library/Application Support/Rsync Studio Licensing", isDirectory: true)
|
||||
let privateURL = directory.appendingPathComponent("license-private-key.txt")
|
||||
let publicURL = directory.appendingPathComponent("license-public-key.txt")
|
||||
|
||||
try fm.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
if fm.fileExists(atPath: privateURL.path) || fm.fileExists(atPath: publicURL.path) {
|
||||
fputs("Refusing to overwrite an existing licensing keypair at \(directory.path)\n", stderr)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let privateKey = Curve25519.Signing.PrivateKey()
|
||||
let privateText = privateKey.rawRepresentation.base64EncodedString()
|
||||
let publicText = privateKey.publicKey.rawRepresentation.base64EncodedString()
|
||||
try privateText.write(to: privateURL, atomically: true, encoding: .utf8)
|
||||
try publicText.write(to: publicURL, atomically: true, encoding: .utf8)
|
||||
try fm.setAttributes([.posixPermissions: 0o600], ofItemAtPath: privateURL.path)
|
||||
try fm.setAttributes([.posixPermissions: 0o644], ofItemAtPath: publicURL.path)
|
||||
|
||||
print("Created Rsync Studio Ed25519 licensing keypair.")
|
||||
print("PRIVATE (KEEP SECRET): \(privateURL.path)")
|
||||
print("PUBLIC: \(publicURL.path)")
|
||||
print("\nPublic key for RsyncStudioLicensePublicKey:\n\(publicText)")
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env swift
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
struct Payload: Codable {
|
||||
let licenseID: String
|
||||
let customerName: String?
|
||||
let customerEmail: String?
|
||||
let edition: String
|
||||
let installationID: String
|
||||
let issuedAt: Date
|
||||
let expiresAt: Date?
|
||||
let maxMajorVersion: Int?
|
||||
}
|
||||
|
||||
func base64URL(_ data: Data) -> String {
|
||||
data.base64EncodedString()
|
||||
.replacingOccurrences(of: "+", with: "-")
|
||||
.replacingOccurrences(of: "/", with: "_")
|
||||
.replacingOccurrences(of: "=", with: "")
|
||||
}
|
||||
|
||||
let args = CommandLine.arguments
|
||||
func value(_ flag: String) -> String? {
|
||||
guard let index = args.firstIndex(of: flag), index + 1 < args.count else { return nil }
|
||||
return args[index + 1]
|
||||
}
|
||||
|
||||
guard let installationID = value("--installation-id"), UUID(uuidString: installationID) != nil else {
|
||||
fputs("Usage: swift Scripts/license-sign.swift --installation-id UUID [--name NAME] [--email EMAIL] [--edition Professional] [--license-id ID] [--max-major 1] [--expires YYYY-MM-DD]\n", stderr)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
let fm = FileManager.default
|
||||
let defaultPrivate = fm.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/Rsync Studio Licensing/license-private-key.txt")
|
||||
let privateURL = value("--private-key").map { URL(fileURLWithPath: $0) } ?? defaultPrivate
|
||||
let privateText = try String(contentsOf: privateURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard let privateData = Data(base64Encoded: privateText) else { throw NSError(domain: "RsyncStudioLicensing", code: 1, userInfo: [NSLocalizedDescriptionKey: "Private key is not Base64."]) }
|
||||
let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: privateData)
|
||||
|
||||
var expiry: Date?
|
||||
if let text = value("--expires") {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
guard let date = formatter.date(from: text) else {
|
||||
fputs("Invalid --expires date; use YYYY-MM-DD.\n", stderr)
|
||||
exit(2)
|
||||
}
|
||||
expiry = date
|
||||
}
|
||||
|
||||
let payload = Payload(
|
||||
licenseID: value("--license-id") ?? UUID().uuidString.lowercased(),
|
||||
customerName: value("--name"),
|
||||
customerEmail: value("--email"),
|
||||
edition: value("--edition") ?? "Professional",
|
||||
installationID: installationID.lowercased(),
|
||||
issuedAt: Date(),
|
||||
expiresAt: expiry,
|
||||
maxMajorVersion: value("--max-major").flatMap(Int.init) ?? 1
|
||||
)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.sortedKeys]
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
let data = try encoder.encode(payload)
|
||||
let signature = try privateKey.signature(for: data)
|
||||
let token = "RS3.\(base64URL(data)).\(base64URL(signature))"
|
||||
print(token)
|
||||
@@ -16,6 +16,10 @@ done
|
||||
|
||||
"$SCRIPT_DIR/verify-release-config.sh"
|
||||
"$SCRIPT_DIR/security-check.sh"
|
||||
if ! grep -Eq 'INFOPLIST_KEY_RsyncStudioLicensePublicKey = "[A-Za-z0-9+/_=-]{40,}";' "$PROJECT/project.pbxproj"; then
|
||||
echo "RsyncStudioLicensePublicKey is not configured. Generate the licensing keypair and embed the PUBLIC key before a commercial release." >&2
|
||||
exit 1
|
||||
fi
|
||||
plutil -lint "$ROOT/Rsync Studio/Resources/PrivacyInfo.xcprivacy"
|
||||
|
||||
BUILD_DIR="$ROOT/build/preflight"
|
||||
|
||||
@@ -23,6 +23,15 @@ grep -q 'hasBalancedShellSyntax' "$ROOT/Rsync Studio/Services/RsyncCommandBuilde
|
||||
grep -q -- '--files-from' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift"
|
||||
grep -q -- '--log-file' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift"
|
||||
|
||||
grep -q 'final class LicensingManager' "$ROOT/Rsync Studio/Services/LicensingManager.swift"
|
||||
grep -q 'trialLengthDays = 30' "$ROOT/Rsync Studio/Services/LicensingManager.swift"
|
||||
grep -q 'Curve25519.Signing.PublicKey' "$ROOT/Rsync Studio/Services/LicensingManager.swift"
|
||||
grep -q 'RsyncStudioLicensePublicKey' "$PROJECT/project.pbxproj"
|
||||
if find "$ROOT" -type f \( -iname '*private*key*' -o -iname '*.p8' \) ! -path '*/build/*' | grep -q .; then
|
||||
echo "A private signing key appears to be present in the project tree." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Static policy checks passed."
|
||||
echo ""
|
||||
echo "After creating a signed Release app, also run:"
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SERVER="$ROOT/rsync-studio-licensing"
|
||||
[[ -d "$SERVER" ]] || { echo 'Licensing Server directory is missing.' >&2; exit 1; }
|
||||
if command -v php >/dev/null 2>&1; then
|
||||
while IFS= read -r -d '' file; do php -l "$file" >/dev/null; done < <(find "$SERVER" -name '*.php' -type f -print0)
|
||||
echo 'OK: all Stage 4 PHP files pass php -l'
|
||||
else
|
||||
echo 'WARN: PHP is unavailable; skipped php -l checks.'
|
||||
fi
|
||||
bash -n "$SERVER/install-overlay.sh"
|
||||
! grep -RniE --exclude='*.md' --exclude='*.example' '(PRIVATE[_ -]?KEY|PADDLE_WEBHOOK_SECRET|LEMON_SQUEEZY_WEBHOOK_SECRET)[[:space:]]*=[[:space:]]*[A-Za-z0-9+/=_-]{20,}' "$SERVER" >/dev/null || { echo 'Possible committed secret detected.' >&2; exit 1; }
|
||||
grep -q "sodium_crypto_sign_detached" "$SERVER/app/Services/Licensing/LicenseSigner.php"
|
||||
grep -q "hash_equals" "$SERVER/app/Services/Payments/PaddleWebhookVerifier.php"
|
||||
grep -q "hash_equals" "$SERVER/app/Services/Payments/LemonSqueezyWebhookVerifier.php"
|
||||
grep -q "lockForUpdate" "$SERVER/app/Http/Controllers/Api/LicenseActivationController.php"
|
||||
grep -q "license_delivery_outbox" "$SERVER/database/migrations/2026_09_08_200000_create_rsync_studio_licensing_tables.php"
|
||||
echo 'Stage 4 licensing-server static checks passed.'
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LS="$ROOT/rsync-studio-licensing"
|
||||
find "$LS/app" "$LS/config" "$LS/database" "$LS/routes" -name '*.php' -print0 | while IFS= read -r -d '' f; do php -l "$f" >/dev/null; done
|
||||
grep -q "implements MustVerifyEmail" "$LS/app/Models/User.php"
|
||||
grep -q "AdminOnly::class" "$LS/routes/web.stage5.php"
|
||||
grep -q "middleware(\['auth','verified'\])" "$LS/routes/web.stage5.php"
|
||||
grep -q "api: __DIR__.'/../routes/api.php'" "$LS/bootstrap/app.php"
|
||||
grep -q "RsyncStudioLicensingServiceProvider::class" "$LS/bootstrap/providers.php"
|
||||
grep -q "create_rsync_studio_auth_tables" <(find "$LS/database/migrations" -maxdepth 1 -type f -printf '%f\n')
|
||||
echo 'Stage 5 integrated licensing portal static verification passed.'
|
||||
Reference in New Issue
Block a user