Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/experimental-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ jobs:
"workflow_url": os.environ["WORKFLOW_URL"],
"xcode_version": os.environ["XCODE_VERSION"],
"sqlcipher_source_repository": "https://github.com/sqlcipher/sqlcipher.git",
"sqlcipher_source_tag": "v4.16.0",
"sqlcipher_source_commit": "e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc",
"sqlcipher_source_tag": "v4.17.0",
"sqlcipher_source_commit": "810db22f575ee7cf94ea96a3e91622b5fcece3dc",
"assets": [
"SQLCipher.xcframework.zip",
"SQLCipher.xcframework.sha256",
Expand All @@ -126,8 +126,8 @@ jobs:
- Commit: \`$GITHUB_SHA\`
- Built at (UTC): \`$BUILT_AT\`
- Xcode: \`$XCODE_VERSION\`
- SQLCipher upstream tag: \`v4.16.0\`
- SQLCipher upstream commit: \`e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc\`
- SQLCipher upstream tag: \`v4.17.0\`
- SQLCipher upstream commit: \`810db22f575ee7cf94ea96a3e91622b5fcece3dc\`
- Workflow: [$WORKFLOW_URL]($WORKFLOW_URL)

Assets:
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/stable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
echo "::error::Stable SQLCipher release workflow must run from a tag."
exit 1
fi
if ! printf '%s' "$RELEASE_TAG" | grep -Eq '^sqlcipher-xcframework-v4\.16\.0-cypherair\.[0-9]+$'; then
if ! printf '%s' "$RELEASE_TAG" | grep -Eq '^sqlcipher-xcframework-v4\.17\.0-cypherair\.[0-9]+$'; then
echo "::error::Unexpected stable SQLCipher release tag: $RELEASE_TAG"
exit 1
fi
Expand Down Expand Up @@ -128,8 +128,8 @@ jobs:
"signer_workflow": f"{os.environ['GITHUB_REPOSITORY']}/.github/workflows/stable-release.yml",
"xcode_version": os.environ["XCODE_VERSION"],
"sqlcipher_source_repository": "https://github.com/sqlcipher/sqlcipher.git",
"sqlcipher_source_tag": "v4.16.0",
"sqlcipher_source_commit": "e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc",
"sqlcipher_source_tag": "v4.17.0",
"sqlcipher_source_commit": "810db22f575ee7cf94ea96a3e91622b5fcece3dc",
"assets": [
"SQLCipher.xcframework.zip",
"SQLCipher.xcframework.sha256",
Expand All @@ -153,8 +153,8 @@ jobs:
- Commit: \`$RELEASE_COMMIT\`
- Built at (UTC): \`$BUILT_AT\`
- Xcode: \`$XCODE_VERSION\`
- SQLCipher upstream tag: \`v4.16.0\`
- SQLCipher upstream commit: \`e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc\`
- SQLCipher upstream tag: \`v4.17.0\`
- SQLCipher upstream commit: \`810db22f575ee7cf94ea96a3e91622b5fcece3dc\`
- Workflow: [$WORKFLOW_URL]($WORKFLOW_URL)

Assets:
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This repository builds the CypherAir-owned `SQLCipher.xcframework` for Apple
targets that need `arm64e` device slices.

It is not a fork of SQLCipher core. The build script consumes the upstream
`sqlcipher/sqlcipher` `v4.16.0` tag, verifies the peeled commit, generates the
`sqlcipher/sqlcipher` `v4.17.0` tag, verifies the peeled commit, generates the
SQLCipher amalgamation, and packages a static framework-shaped XCFramework for
CypherAir app integration.

Expand All @@ -13,8 +13,9 @@ CypherAir app integration.
- Status: stable external binary dependency infrastructure for CypherAir, with
separate experiment and drill channels for validation.
- Upstream source: `https://github.com/sqlcipher/sqlcipher`
- Upstream tag: `v4.16.0`
- Expected peeled commit: `e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc`
- Upstream tag: `v4.17.0`
- Expected peeled commit: `810db22f575ee7cf94ea96a3e91622b5fcece3dc`
- SQLite baseline: `3.53.3`
- Package shape: static `SQLCipher.framework` slices inside
`SQLCipher.xcframework`.
- Crypto provider: Apple CommonCrypto / Security framework
Expand Down Expand Up @@ -77,6 +78,10 @@ SQLCipher.framework/
Xcode consume the artifact through the normal Frameworks phase while keeping
CypherAir's static-linking intent.

Validation also executes a macOS encrypted-database round trip, verifies the
SQLCipher 4.17.0 and SQLite 3.53.3 runtime identities, and requires a wrong key
to fail with `SQLITE_NOTADB`.

Run validation again against an existing build:

```bash
Expand All @@ -91,7 +96,7 @@ python3 scripts/validate-sqlcipher-xcframework.py \
## Release Discipline

Stable releases use SSH-signed annotated tags such as
`sqlcipher-xcframework-v4.16.0-cypherair.1`. The stable release workflow only
`sqlcipher-xcframework-v4.17.0-cypherair.1`. The stable release workflow only
publishes from those tags, produces non-prerelease immutable releases, and
verifies both GitHub release integrity and artifact attestations after
publication.
Expand All @@ -108,7 +113,7 @@ Experiment and drill releases remain available for validation work and publish
channel-specific metadata such as `sqlcipher-xcframework-experiment.json`.

Release assets must be immutable. If a stable artifact is wrong, publish a new
semantic release tag such as `sqlcipher-xcframework-v4.16.0-cypherair.2`
semantic release tag such as `sqlcipher-xcframework-v4.17.0-cypherair.2`
instead of replacing existing assets.

## Licensing
Expand Down
11 changes: 6 additions & 5 deletions scripts/build-sqlcipher-xcframework.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

SQLCIPHER_REPOSITORY="${SQLCIPHER_REPOSITORY:-https://github.com/sqlcipher/sqlcipher.git}"
SQLCIPHER_TAG="${SQLCIPHER_TAG:-v4.16.0}"
SQLCIPHER_EXPECTED_COMMIT="${SQLCIPHER_EXPECTED_COMMIT:-e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc}"
SQLCIPHER_TAG="${SQLCIPHER_TAG:-v4.17.0}"
SQLCIPHER_EXPECTED_COMMIT="${SQLCIPHER_EXPECTED_COMMIT:-810db22f575ee7cf94ea96a3e91622b5fcece3dc}"
SQLCIPHER_VERSION="${SQLCIPHER_TAG#v}"
SQLCIPHER_RELEASE_STATUS="${SQLCIPHER_RELEASE_STATUS:-experimental}"

BUILD_DIR="${SQLCIPHER_BUILD_DIR:-$REPO_ROOT/build}"
Expand Down Expand Up @@ -184,7 +185,7 @@ create_framework_bundle() {
cp "$INCLUDE_DIR/module.modulemap" "$framework_dir/Modules/module.modulemap"
cp "$SOURCE_DIR/sqlcipher-resources/PrivacyInfo.xcprivacy" "$framework_dir/PrivacyInfo.xcprivacy"

cat > "$framework_dir/Info.plist" <<'PLIST'
cat > "$framework_dir/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Expand All @@ -202,9 +203,9 @@ create_framework_bundle() {
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.16.0</string>
<string>$SQLCIPHER_VERSION</string>
<key>CFBundleVersion</key>
<string>4.16.0</string>
<string>$SQLCIPHER_VERSION</string>
</dict>
</plist>
PLIST
Expand Down
38 changes: 29 additions & 9 deletions scripts/validate-sqlcipher-xcframework.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
REQUIRED_FRAMEWORK_FILES = ["Info.plist", "Modules/module.modulemap", "PrivacyInfo.xcprivacy"]
COMPILE_OPTIONS = ["SQLITE_HAS_CODEC", "SQLITE_TEMP_STORE=2"]
LINK_FRAMEWORKS = ["Security", "CoreFoundation", "Foundation"]
EXPECTED_SOURCE_REPOSITORY = "https://github.com/sqlcipher/sqlcipher.git"
EXPECTED_SOURCE_TAG = "v4.17.0"
EXPECTED_SOURCE_COMMIT = "810db22f575ee7cf94ea96a3e91622b5fcece3dc"
EXPECTED_SQLCIPHER_VERSION = "4.17.0"
EXPECTED_SQLITE_VERSION = "3.53.3"
CFLAGS = [
"-DNDEBUG",
"-DSQLCIPHER_CRYPTO_CC",
Expand All @@ -68,9 +73,9 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--manifest-out", type=Path)
parser.add_argument("--status", choices=["experimental", "stable"], default="experimental")
parser.add_argument("--source-dir", type=Path)
parser.add_argument("--source-repository", default="https://github.com/sqlcipher/sqlcipher.git")
parser.add_argument("--source-tag", default="v4.16.0")
parser.add_argument("--source-commit", default="e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc")
parser.add_argument("--source-repository", default=EXPECTED_SOURCE_REPOSITORY)
parser.add_argument("--source-tag", default=EXPECTED_SOURCE_TAG)
parser.add_argument("--source-commit", default=EXPECTED_SOURCE_COMMIT)
parser.add_argument("--xcframework-zip", type=Path)
parser.add_argument("--checksum-file", type=Path)
parser.add_argument("--privacy-manifest", type=Path)
Expand Down Expand Up @@ -150,6 +155,11 @@ def validate_xcframework(xcframework: Path) -> list[dict]:
raise ValidationError(f"{identifier}: SQLCipher.framework Info.plist must declare CFBundlePackageType=FMWK")
if framework_info.get("CFBundleExecutable") != "SQLCipher":
raise ValidationError(f"{identifier}: SQLCipher.framework Info.plist must declare CFBundleExecutable=SQLCipher")
for version_key in ("CFBundleShortVersionString", "CFBundleVersion"):
if framework_info.get(version_key) != EXPECTED_SQLCIPHER_VERSION:
raise ValidationError(
f"{identifier}: {version_key} must be {EXPECTED_SQLCIPHER_VERSION}"
)

headers = framework / "Headers"
for header in REQUIRED_HEADERS:
Expand Down Expand Up @@ -258,11 +268,12 @@ def smoke_test(xcframework: Path) -> dict:

if (sqlite3_open(":memory:", &db) != SQLITE_OK) return 10;
if (query_value(db, "PRAGMA cipher_version;", value, sizeof(value)) != SQLITE_OK) return 11;
if (strncmp(value, "4.16.0", 6) != 0 || strstr(value, "community") == NULL) return 12;
if (strncmp(value, "__SQLCIPHER_VERSION__", sizeof("__SQLCIPHER_VERSION__") - 1) != 0 || strstr(value, "community") == NULL) return 12;
sqlite3_close(db);

if (!sqlite3_compileoption_used("SQLITE_HAS_CODEC")) return 13;
if (!sqlite3_compileoption_used("SQLITE_TEMP_STORE=2")) return 14;
if (strcmp(sqlite3_libversion(), "__SQLITE_VERSION__") != 0) return 15;

remove(path);
if (sqlite3_open(path, &db) != SQLITE_OK) return 20;
Expand All @@ -279,14 +290,17 @@ def smoke_test(xcframework: Path) -> dict:

if (sqlite3_open(path, &db) != SQLITE_OK) return 40;
if (exec_sql(db, bad_key) != SQLITE_OK) return 41;
int wrong_key_rc = query_value(db, "SELECT v FROM t;", value, sizeof(value));
int wrong_key_rc = exec_sql(db, "CREATE TABLE should_not_exist(v TEXT);");
sqlite3_close(db);
remove(path);
if (wrong_key_rc == SQLITE_OK) return 42;
if (wrong_key_rc != SQLITE_NOTADB) return 42;

return 0;
}
'''
source = source.replace("__SQLCIPHER_VERSION__", EXPECTED_SQLCIPHER_VERSION).replace(
"__SQLITE_VERSION__", EXPECTED_SQLITE_VERSION
)

with tempfile.TemporaryDirectory() as temp_name:
temp_dir = Path(temp_name)
Expand Down Expand Up @@ -417,10 +431,16 @@ def validate_manifest(path: Path, expected_status: str) -> None:
if payload.get("status") != expected_status:
raise ValidationError(f"manifest status must be {expected_status}")
source = payload.get("source") or {}
if source.get("tag") != "v4.16.0":
raise ValidationError("manifest source tag must be v4.16.0")
if source.get("resolvedCommit") != "e2a6040f2ae5cfff2b3e08eb3320007d93cdf3fc":
if source.get("repository") != EXPECTED_SOURCE_REPOSITORY:
raise ValidationError("manifest source repository is not the pinned SQLCipher repository")
if source.get("tag") != EXPECTED_SOURCE_TAG:
raise ValidationError(f"manifest source tag must be {EXPECTED_SOURCE_TAG}")
if source.get("resolvedCommit") != EXPECTED_SOURCE_COMMIT:
raise ValidationError("manifest source commit is not the pinned SQLCipher commit")
if source.get("versionFile") != EXPECTED_SQLITE_VERSION:
raise ValidationError(
f"manifest SQLite version must be {EXPECTED_SQLITE_VERSION}"
)


def main() -> int:
Expand Down
Loading