test: record partial native bridge import control
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using System.Diagnostics;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Configuration;
|
||||
using Opc.Ua.Security.Certificates;
|
||||
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("macos")]
|
||||
internal static class ImportSeed
|
||||
{
|
||||
const string Uri = "urn:localhost:NativeRetention.Control";
|
||||
static string Hash(Certificate cert) => Convert.ToHexString(SHA256.HashData(cert.RawData)).ToLowerInvariant();
|
||||
static void Require(bool yes, string message) { if (!yes) throw new InvalidOperationException(message); }
|
||||
static ApplicationConfiguration Configuration(string pki, ITelemetryContext telemetry) => new(telemetry)
|
||||
{
|
||||
ApplicationName = "NativeRetention.Control", ApplicationUri = Uri, ApplicationType = ApplicationType.Client,
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory,
|
||||
StorePath = Path.Combine(pki, "own"), SubjectName = "CN=NativeRetention.Control" },
|
||||
TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "trusted") },
|
||||
TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "issuers") },
|
||||
RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "rejected") },
|
||||
AutoAcceptUntrustedCertificates = false,
|
||||
},
|
||||
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 15000 },
|
||||
};
|
||||
static async Task<ApplicationInstance> Open(string pki, ITelemetryContext telemetry, CancellationToken ct)
|
||||
{
|
||||
var app = new ApplicationInstance(telemetry) { ApplicationType = ApplicationType.Client,
|
||||
ApplicationName = "NativeRetention.Control", ApplicationConfiguration = Configuration(pki, telemetry) };
|
||||
try
|
||||
{
|
||||
await app.ApplicationConfiguration!.ValidateAsync(ApplicationType.Client, ct);
|
||||
Require(await app.CheckApplicationInstanceCertificatesAsync(true, null, ct), "Normal application certificate check failed");
|
||||
return app;
|
||||
}
|
||||
catch { await app.DisposeAsync(); throw; }
|
||||
}
|
||||
static bool ManagerDisposed(ICertificateManager manager)
|
||||
{
|
||||
Require(manager.GetType().FullName == "Opc.Ua.CertificateManager" && manager.GetType().Assembly.GetName().Version?.ToString() == "2.0.1.0", "Exact SDK diagnostic layout changed");
|
||||
var field = manager.GetType().GetField("m_disposed", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||||
Require(field?.FieldType == typeof(bool), "Exact SDK disposal bool missing");
|
||||
return (bool)field!.GetValue(manager)!;
|
||||
}
|
||||
static void PrivateFile(string path, byte[] bytes)
|
||||
{
|
||||
using var stream = new FileStream(path, new FileStreamOptions { Mode = FileMode.CreateNew,
|
||||
Access = FileAccess.Write, UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite });
|
||||
stream.Write(bytes);
|
||||
}
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
ApplicationInstance? app = null;
|
||||
try
|
||||
{
|
||||
Require(OperatingSystem.IsMacOS() && Environment.Version.ToString() == "10.0.14", "Expected matched macOS/.NET10.0.14 seed");
|
||||
var root = Path.GetFullPath(args.Single());
|
||||
Require(Directory.Exists(root) && (File.GetUnixFileMode(root) & (UnixFileMode)63) == 0, "Expected private fresh seed directory");
|
||||
app = await Open(Path.Combine(root, "pki"), DefaultTelemetry.Create(_ => { }), deadline.Token);
|
||||
var manager = app.ApplicationConfiguration!.CertificateManager;
|
||||
string leaf;
|
||||
using (var entry = manager.AcquireApplicationCertificateBySecurityPolicy(SecurityPolicies.Basic256Sha256))
|
||||
{
|
||||
Require(entry is not null && entry.Certificate.HasPrivateKey, "Expected SDK-generated private certificate");
|
||||
var certificate = entry!.Certificate; leaf = Hash(certificate);
|
||||
using var rsa = certificate.GetRSAPrivateKey(); using var publicKey = certificate.GetRSAPublicKey();
|
||||
Require(rsa is not null && publicKey is not null, "Expected normal SDK RSA certificate");
|
||||
var challenge = "native-import-seed-diagnostic"u8.ToArray();
|
||||
var signature = rsa!.SignData(challenge, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
Require(publicKey!.VerifyData(challenge, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), "Seed key mismatch");
|
||||
var privateKey = rsa.ExportRSAPrivateKey();
|
||||
try { PrivateFile(Path.Combine(root, "leaf.der"), certificate.RawData); PrivateFile(Path.Combine(root, "private.pkcs1"), privateKey); }
|
||||
finally { CryptographicOperations.ZeroMemory(privateKey); }
|
||||
}
|
||||
await app.DisposeAsync().AsTask().WaitAsync(deadline.Token); app = null;
|
||||
Require(ManagerDisposed(manager) && Certificate.InstancesLeaked == 0, "Seed SDK ownership remains");
|
||||
Console.WriteLine(JsonSerializer.Serialize(new { kind = "seed", leafSha256 = leaf, runtime = Environment.Version.ToString(),
|
||||
sdkVersion = typeof(Certificate).Assembly.GetName().Version!.ToString(), managerDisposed = true,
|
||||
certificateCoresOutstanding = Certificate.InstancesLeaked, keyFormat = "PKCS1-private-RSA", privateFileMode = "0600",
|
||||
cryptographyRuntime = new { informationalVersion = typeof(X509Certificate2).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion,
|
||||
sha256 = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(typeof(X509Certificate2).Assembly.Location))).ToLowerInvariant() } }));
|
||||
return 0;
|
||||
}
|
||||
catch(Exception error) { Console.WriteLine(JsonSerializer.Serialize(new {kind="failed", errorType=error.GetType().FullName, message=error.Message}));return 1; }
|
||||
finally { if(app is not null) await app.DisposeAsync(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Installed native bridge diagnostic — attempt A stopped at its ownership guard
|
||||
|
||||
The approved fixed-200 diagnostic did **not complete**. One measured native
|
||||
child started; it stopped during its first operation because the harness required
|
||||
an identity keychain association that the installed .NET wrapper allows to be
|
||||
absent. This is a diagnostic model error. There is no repeated native retention
|
||||
result, no production mitigation or resource-budget decision, and no change to
|
||||
#799's status.
|
||||
|
||||
The separate SDK 2.0.1 seed completed with the expected RSA leaf/key pair,
|
||||
observed application manager disposal, and private fixture files. The measured C
|
||||
child loaded the pinned native bridge, checked its export image and rejected CLR
|
||||
and SDK images. The C build succeeded with six unsuppressed deprecation warnings
|
||||
for the exercised public keychain APIs; the seed build reported zero warnings
|
||||
and errors. This was macOS **26.5.2** build **25F84** arm64; installed managed
|
||||
cryptography was .NET **10.0.14**. [The inspected design](NATIVE_IMPORT_DESIGN.md)
|
||||
records the exact native/managed binary paths and ABI.
|
||||
|
||||
| Observation | Recorded result |
|
||||
|---|---|
|
||||
| Before snapshot | AppleDatabase **1 / 768 shallow bytes**; Mach RSS **7,815,168 bytes**; 3 numeric FDs |
|
||||
| DER import, modern RSA creation, private keychain create/lock, external representation and legacy ephemeral import | All reached bridge calls succeeded |
|
||||
| Move to private keychain | Bridge return **1**, OSStatus **0**; post-move certificate keychain copy returned a verified private association |
|
||||
| Post-move identity keychain copy | Bridge-normalized OSStatus **0**, **no returned keychain reference** |
|
||||
| Stop | Before identity certificate copy; its uncalled status is the `INT32_MIN` sentinel, not a native OSStatus |
|
||||
| Error-path cleanup | **9** owned CF outputs / **9** releases; logical owners **0**; verified private keychain native delete OSStatus **0**; no fallback cleanup |
|
||||
| Process and fixture | Native exit **1**, reported elapsed **1.172310 s**; both builds, seed and native process confirmed reaped; no remaining owned keychain files before collector cleanup; private task files overwritten and unlinked after reaping; root removed |
|
||||
|
||||
Only four JSONL rows exist: before fingerprint, before checkpoint, partial
|
||||
operation 1, failure. No first/repeated/idle snapshot or after fingerprint was
|
||||
collected. The before-only default/search query succeeded; equality across the
|
||||
attempt was **not measured**. The measured child made no setter or restore call.
|
||||
No estimate of native heap growth can be drawn from its single before sample.
|
||||
|
||||
## Why the guard did not match the installed wrapper
|
||||
|
||||
`SafeTemporaryKeychainHandle.TrackItem` obtains a copied keychain and returns
|
||||
without adding a logical owner when the copied handle is invalid. The installed
|
||||
native bridge `AppleCryptoNative_SecKeychainItemCopyKeychain` at `0x8a04` normalizes
|
||||
`errSecNoSuchKeychain` (`-25294`) and `errSecInvalidItemRef` (`-25304`) to success;
|
||||
the receipt records the bridge result, not the underlying raw Apple status. The
|
||||
installed move wrapper validates the move result and identity handle; it does
|
||||
not require the identity's `TrackItem` to add an owner. Attempt A incorrectly
|
||||
made that association mandatory before identity certificate copy.
|
||||
|
||||
Any separately reviewed follow-up should model ownership from actual validated
|
||||
associations: keep all nine expected native copy calls and their statuses; add a
|
||||
logical owner only when the returned keychain matches the generated private
|
||||
reference/path; record no association when it is null. Reject a disappearing
|
||||
association during Untrack of a previously tracked owner. Record the actual
|
||||
zero-owner deletion stage and which item/copy references remain alive, rather
|
||||
than requiring deletion at identity Untrack. A single partial attempt cannot
|
||||
predict the copied certificate's association or a fixed number of non-null
|
||||
copies. This correction concerns the diagnostic only, not production SDK
|
||||
ownership or certificate selection.
|
||||
|
||||
Selected receipts and verified raw/projection hashes are in
|
||||
[`evidence/2026-11-10-native-import-attempt-a`](evidence/2026-11-10-native-import-attempt-a/verification.json).
|
||||
The unchanged raw directory is `/tmp/opcua2-native-import-20261110a`. It contains
|
||||
raw native snapshots but no retained private fixture. The native executable
|
||||
SHA-256 is `e5c5caf2a8f882f82b738396156b378fd157ebd1a825d462449cd2a0497830fd`;
|
||||
the seed DLL SHA-256 is
|
||||
`1fa7de542aee11acab18dcc8d4096b21fcbc16e4edc1fca2243159dd1176adc0`.
|
||||
This was a single serialized execution. No retry or counterfactual arm ran.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Native bridge import control — inspected design
|
||||
|
||||
Status: Astra approved the refined ABI/ownership design for implementation. The
|
||||
C child, separate seed and collector are implemented and submitted for actual-code
|
||||
review. The original assumptions below are retained to explain the exact attempt.
|
||||
[Attempt A](NATIVE_IMPORT_ATTEMPT.md) stopped at its first partial operation: the
|
||||
installed wrapper permits null item associations, contrary to the original fixed
|
||||
identity-association expectation. It produced no repeated retention result. The coordinator confirmed a
|
||||
single staged native-bridge control; a handwritten Apple import reproduction is
|
||||
deferred until the result shows whether it is useful. This is not a production
|
||||
routing/crypto change, a mitigation or an acceptance decision.
|
||||
|
||||
## Installed binaries and retained inspection
|
||||
|
||||
Installed managed crypto:
|
||||
`/opt/homebrew/Cellar/dotnet/10.0.403/libexec/shared/Microsoft.NETCore.App/10.0.14/System.Security.Cryptography.dll`.
|
||||
Informational version `10.0.14+0427b9abbead3fa64c6655eb4290c537397131fa`;
|
||||
SHA-256 `f4957d7218a004a27bf8964e45147df6a88092c6e7344582082840d62c6c579a`.
|
||||
|
||||
Installed native bridge:
|
||||
`/opt/homebrew/Cellar/dotnet/10.0.403/libexec/shared/Microsoft.NETCore.App/10.0.14/libSystem.Security.Cryptography.Native.Apple.dylib`.
|
||||
SHA-256 `0333994e217a3d111d36d7f31460eea69a286f9fc819e2d7c746df13d2aec46c`.
|
||||
Only this exact, unmodified dylib would be loaded by absolute path; required
|
||||
exports and actual loaded-image identity must match. No SDK/CLR may be loaded in
|
||||
the measured C child. Its native bridge dependency is explicitly part of this
|
||||
experiment, unlike the preceding plain-Apple negative control.
|
||||
|
||||
Retained actual-binary inspection files (read-only ILSpy 10.1.0.8386 / otool):
|
||||
|
||||
| File | Relevant locations |
|
||||
|---|---|
|
||||
| `/tmp/opcua2-native-runtime-loader-decompiled.cs` | 836–870 load certs/keys/match, initialize import state and FromCertAndKey; 1555–1581 exportable branch; 1611–1640 keychain initialization and GetPrivateKey |
|
||||
| `/tmp/opcua2-native-runtime-pkcs12key-decompiled.cs` | AsymmetricAlgorithmPkcs12PrivateKey imports PKCS8 into its RSA algorithm |
|
||||
| `/tmp/opcua2-native-runtime-pkcs12key-base-decompiled.cs` | Dispose calls the algorithm's Dispose |
|
||||
| `/tmp/opcua2-native-runtime-rsa-decompiled.cs` | 95–108 ImportParameters/CreateDataKey/CopyPublicKey; 431–458 PKCS1 serialization to CreateDataKey; export uses native external representation |
|
||||
| `/tmp/opcua2-native-runtime-applepal-decompiled.cs` | 339–349 identity constructor/certificate copy and disposal; 555–564 MoveToKeychain |
|
||||
| `/tmp/opcua2-native-runtime-appleinterop-decompiled.cs` | PAL RSA enum 544–549; CreateDataKey/external representation/public copy 1982–2127; keychain create/never-lock 3054/3209/3481; legacy key import 3563/3596; cert import 3697/3944; move 3878/4036; identity cert copy 2629/4087 |
|
||||
| `/tmp/opcua2-native-runtime-certhandle-decompiled.cs` and `identityhandle-decompiled.cs` | Both actual types inherit SafeKeychainItemHandle with no release override |
|
||||
| `/tmp/opcua2-native-runtime-keychainitem-decompiled.cs` | SafeKeychainItemHandle release: UntrackItem copy/release, then CFRelease; certificate and identity inherit this class |
|
||||
| `/tmp/opcua2-native-runtime-keypair-decompiled.cs` | Actual SecKeyPair Dispose: modern private-key owner before modern public-key owner |
|
||||
| `/tmp/opcua2-native-runtime-keyhandle-decompiled.cs` | SafeSecKeyRefHandle release: straight CFRelease, then optional managed parent release; no tracking copy |
|
||||
| `/tmp/opcua2-native-runtime-keychain-decompiled.cs` | TrackItem/UntrackItem each copy/release a keychain; matching dictionary only adds/releases managed SafeHandle ownership |
|
||||
| `/tmp/opcua2-native-runtime-x509contenttype-decompiled.cs` | Actual public Cert enum value 1; Pfx/Pkcs12 value 3 |
|
||||
| `/tmp/opcua2-native-runtime-apple-disassembly.txt` | `_AppleCryptoNative_SecKeyImportEphemeral`, `_AddKeyToKeychain`, `_AppleCryptoNative_X509MoveToKeychain`, all required exports |
|
||||
|
||||
The installed native call sites, not merely the public runtime tag, show:
|
||||
|
||||
- `SecKeyImportEphemeral` at `0x92e8` calls `SecItemImport` at `0x9390`,
|
||||
followed by CF ownership cleanup.
|
||||
- `AddKeyToKeychain` calls `SecItemExport` at `0xa0a8` and `SecItemImport` at
|
||||
`0xa11c`, then retains an optional returned key and releases local containers.
|
||||
- `X509MoveToKeychain` at `0xa180` calls AddKeyToKeychain at `0xa240`,
|
||||
`SecCertificateAddToKeychain` at `0xa278`, and a private-search-list identity
|
||||
`SecItemCopyMatching` at `0xa388`, with CF cleanup and one returned identity.
|
||||
|
||||
Public `v10.0.14` native headers/source are explanatory cross-checks, not asserted
|
||||
as the exact installed source commit. In particular this **Exportable** path is
|
||||
managed PKCS12 decode / RSA materialization → legacy key import → move-to-keychain.
|
||||
A guessed `SecPKCS12Import` control would exercise a different path.
|
||||
|
||||
## ABI and returned ownership map
|
||||
|
||||
All bridge integer return values below are signed 32-bit. The installed generated
|
||||
managed keychain-create declaration takes signed `int utf8PassphraseLength`;
|
||||
the explanatory public native header takes uint32. The control uses the same
|
||||
fixed positive bounded value 129, whose ABI representation is identical. Other
|
||||
byte lengths are signed 32-bit. Native
|
||||
pointers are opaque CF references and must never be printed. Every out location
|
||||
starts null/zero; each non-null owned output must be released once on success or
|
||||
failure. Borrowed CFData byte pointers are valid only while their owned CFData is
|
||||
held and are never separately freed. CFError is an owned output, recorded only
|
||||
by code and released once, without description/key logging.
|
||||
|
||||
| Exact export | Argument/return contract |
|
||||
|---|---|
|
||||
| `AppleCryptoNative_X509ImportCertificate` | `(uint8_t*, int32 len, int32 contentType=1, CFStringRef NULL, SecKeychainRef NULL, int32 exportable=1, SecCertificateRef*, SecIdentityRef*, int32 OSStatus*)`; bridge return 1 and OSStatus 0 required; DER must yield an owned cert and no identity |
|
||||
| `AppleCryptoNative_SecKeyCreateWithData` | `(uint8_t*, int32 len, uint32 PAL_RSA=2, int32 isPublic=0, SecKeyRef*, CFErrorRef*)`; bridge return 1, owned modern RSA key |
|
||||
| `AppleCryptoNative_SecKeyCopyPublicKey` | `(SecKeyRef) -> SecKeyRef`; owned public-key copy, non-null required |
|
||||
| `AppleCryptoNative_SecKeyCopyExternalRepresentation` | `(SecKeyRef, CFDataRef*, CFErrorRef*)`; bridge return 1, owned PKCS1 private-key data, no key bytes logged |
|
||||
| `AppleCryptoNative_SecKeychainCreate` | `(const char*, int32 passwordLength=129 in installed managed ABI, const uint8_t*, SecKeychainRef*)`; OSStatus 0, owned fresh private keychain; same native false prompt flag |
|
||||
| `AppleCryptoNative_SetKeychainNeverLock` | `(SecKeychainRef) -> OSStatus`; 0 required; affects only the generated private keychain, matching installed import state |
|
||||
| `AppleCryptoNative_SecKeyImportEphemeral` | `(uint8_t*, int32 len, int32 isPrivate=1, SecKeyRef*, int32 OSStatus*)`; bridge return 1 / OSStatus 0, owned legacy private key |
|
||||
| `AppleCryptoNative_X509MoveToKeychain` | `(SecCertificateRef, SecKeychainRef, SecKeyRef, SecIdentityRef*, int32 OSStatus*)`; bridge return 1 / OSStatus 0 and owned non-null identity required; native helper scopes its identity query to target keychain and supplied cert |
|
||||
| `AppleCryptoNative_X509CopyCertFromIdentity` | `(SecIdentityRef, SecCertificateRef*) -> OSStatus`; 0 required, owned certificate copy |
|
||||
| `AppleCryptoNative_SecKeychainItemCopyKeychain` | `(CFTypeRef item, SecKeychainRef*) -> int32`; bridge-normalized OSStatus 0 required; non-null returned keychain is owned and CFReleased once. The installed bridge normalizes native -25304/-25294 to 0; null output is valid for an unassociated item. Preserve normalized status and output-present flag, not raw underlying status. |
|
||||
| `AppleCryptoNative_SecKeychainDelete` | `(SecKeychainRef) -> OSStatus`; recorded separately from subsequent CFRelease; only a returned keychain verified against this generated private path may be deleted |
|
||||
|
||||
## Matching native interactions from managed wrapper tracking
|
||||
|
||||
The child will include the bridge-normalized `SecKeychainItemCopyKeychain` calls
|
||||
made by installed wrappers, not merely the primary import/move exports. Every
|
||||
non-null copied keychain is released once. The map is:
|
||||
|
||||
1. After DER import, TrackItem(cert): copy/release its associated keychain.
|
||||
The null identity output is skipped, as the managed TrackItem does.
|
||||
2. Around MoveToKeychain: acquire the outer cert-keychain copy and keep it alive
|
||||
across the entire move wrapper; separately copy/release cert's old keychain
|
||||
for UntrackItem; call Move; copy/release cert's new keychain and identity's
|
||||
keychain for the two TrackItem calls; then release the outer copy.
|
||||
3. After identity certificate copy: copy/release copied cert's keychain for
|
||||
TrackItem.
|
||||
4. Releasing initial cert, copied cert and identity: copy/release each item's
|
||||
keychain first (UntrackItem), then CFRelease the item. This preserves the
|
||||
installed PAL order: initial cert after the replacement PAL is constructed,
|
||||
then replacement cert before identity at disposal.
|
||||
5. Modern public/private and legacy private `SafeSecKeyRefHandle` release maps
|
||||
directly to CFRelease, with no keychain tracking native call. Its optional
|
||||
managed parent lifetime is not used by the inspected GetPrivateKey path.
|
||||
|
||||
No managed dictionary is recreated. A small logical owner counter preserves the
|
||||
known deterministic ownership/deletion order. It starts at one import-state
|
||||
owner. Successful TrackItem of an item associated with the generated keychain
|
||||
adds one; UntrackItem subtracts one. These logical increments are not CF retains.
|
||||
After the replacement PAL is constructed and the original cert is disposed,
|
||||
ImportState.Dispose relinquishes its initial logical ownership. The counter then
|
||||
contains only copied-cert and identity owners. Their Untrack calls subtract one
|
||||
before releasing each copied-keychain reference. The final identity Untrack
|
||||
reaches zero: it calls SecKeychainDelete and releases the initial native keychain
|
||||
owner **while the copied keychain ref and identity are still alive**, then
|
||||
releases the copied keychain ref, then CFReleases the identity. This matches the
|
||||
installed SafeHandle delayed-release ordering; deliberately postponing deletion
|
||||
to after all item CFReleases would be a different reduced sequence. The legacy
|
||||
private key is released explicitly after this sequence (the managed helper lacks
|
||||
an explicit local using scope); exact managed finalizer timing is not emulated.
|
||||
The installed bridge at `0x8a04` checks item type, calls the public Apple copy API,
|
||||
and normalizes -25304/-25294 to success. The control calls that pinned export
|
||||
rather than interpreting a raw public return differently. Any other returned
|
||||
status fails the operation but is retained in the receipt.
|
||||
|
||||
## Proposed one-child sequence and boundaries
|
||||
|
||||
Prepare one fresh normal SDK-generated RSA fixture in a **separate seed process**,
|
||||
not in the measured child. Export its public DER certificate and RSA PKCS1 private
|
||||
key through public APIs into a private directory with files mode 0600. Preserve
|
||||
only public identity/source/binary hashes, never private bytes/passwords. The seed
|
||||
and measured child must be reaped before their key material is overwritten and
|
||||
unlinked; cleanup is confined to their fresh owned roots. No existing PKI is used.
|
||||
|
||||
One plain C child would then repeat exactly 200 times, without count/retry
|
||||
extension:
|
||||
|
||||
1. Import public DER through the pinned bridge. Materialize the modern RSA key
|
||||
from PKCS1 through CreateWithData and obtain the owned public-key copy, matching
|
||||
the inspected RSA loader prelude without reproducing managed ASN1/PFX matching.
|
||||
2. Create one generated unrelated private keychain path and set its never-lock
|
||||
behavior through the pinned bridge. Use generated password bytes and no prompt;
|
||||
verify its returned path is exactly the generated private filename/backing file.
|
||||
3. Export modern private-key representation, import the legacy ephemeral key,
|
||||
then release exported CFData and modern RSA private/public owners in that order, as the
|
||||
managed Pkcs12Key is disposed before MoveToKeychain.
|
||||
4. Move certificate/key to that private keychain; require a typed identity.
|
||||
Copy its certificate as the AppleCertificatePal identity constructor does;
|
||||
release the original certificate. At first/last only, check returned public
|
||||
certificate identity against the input; no secret/private-key logging.
|
||||
5. Relinquish the initial import-state logical owner after original cert
|
||||
disposal. Release copied cert (Untrack/copy-release then cert CFRelease), then
|
||||
identity. Its final Untrack deletes/releases the verified initial keychain
|
||||
owner before releasing that Untrack copy and identity, as mapped above.
|
||||
Release the explicitly held legacy key. Preserve every bridge
|
||||
return, OSStatus, CFError code, owned-output/release count and failure path.
|
||||
|
||||
The modern/legacy distinction is explicit. The installed managed helper's local
|
||||
legacy SafeSecKeyRef has no explicit using scope in FromCertAndKey; the native
|
||||
control uses balanced explicit CF owners and does **not** claim that this is a
|
||||
proven GC bug, or exactly emulate managed finalizer timing.
|
||||
|
||||
Use the already accepted before/first/200/five-second-idle checkpoints, raw native
|
||||
heap inventory, Mach RSS, total/owned-keychain FDs and backing-file counts, and
|
||||
read-only successful default/search-list fingerprint equality. No list setter or
|
||||
restore call. Keep fixed parent/process/probe/reap bounds, complete JSONL parsing,
|
||||
private generated paths, fail-closed ABI/hash/image checks and child reaping.
|
||||
|
||||
A positive result would show that managed caller participation is not necessary
|
||||
for this observed import-path retention, while retaining the exact installed
|
||||
native bridge code. It would **not** distinguish a bridge-internal native owner
|
||||
from Apple caching; a later independently owned public-Apple sequence could be
|
||||
justified then. A flat result would not by itself prove a managed bug: omitted
|
||||
managed parsing/matching, registry bookkeeping or lifetime differences remain. No
|
||||
production implementation or additional experiment is approved by this design.
|
||||
@@ -0,0 +1,264 @@
|
||||
// Diagnostic only: pinned installed native bridge import; no measured managed caller or PFX parser.
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dlfcn.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define REPETITIONS 200
|
||||
static char root[PATH_MAX];
|
||||
static struct timespec started;
|
||||
static unsigned created_refs, released_refs, query_releases;
|
||||
static double elapsed(void) {
|
||||
struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
return (double)(now.tv_sec-started.tv_sec)+(double)(now.tv_nsec-started.tv_nsec)/1e9;
|
||||
}
|
||||
static void die(const char *message) {
|
||||
fprintf(stdout,"{\"kind\":\"failed\",\"message\":\"%s\",\"createdRefs\":%u,\"releasedRefs\":%u,\"elapsedSeconds\":%.6f}\n",message,created_refs,released_refs,elapsed());
|
||||
fflush(stdout); exit(1);
|
||||
}
|
||||
static void hex(const unsigned char *bytes, size_t length, char *out) {
|
||||
const char *alphabet="0123456789abcdef";
|
||||
for(size_t i=0;i<length;i++){out[i*2]=alphabet[bytes[i]>>4];out[i*2+1]=alphabet[bytes[i]&15];}
|
||||
out[length*2]=0;
|
||||
}
|
||||
static void wipe(void *memory, size_t length) {
|
||||
volatile unsigned char *bytes=(volatile unsigned char *)memory;
|
||||
while(length--)*bytes++=0;
|
||||
}
|
||||
static OSStatus keychain_path(SecKeychainRef keychain, char *path) {
|
||||
if(!keychain||CFGetTypeID(keychain)!=SecKeychainGetTypeID())return errSecParam;
|
||||
UInt32 length=PATH_MAX-1;
|
||||
OSStatus status=SecKeychainGetPath(keychain,&length,path);
|
||||
if(status==errSecSuccess && length<PATH_MAX)path[length]=0;
|
||||
return status;
|
||||
}
|
||||
struct fingerprint { char default_sha[65], search_sha[65]; CFIndex search_count; };
|
||||
static struct fingerprint fingerprint(const char *stage) {
|
||||
SecKeychainRef default_keychain=NULL;
|
||||
CFArrayRef search=NULL;
|
||||
struct fingerprint out; memset(&out,0,sizeof(out));
|
||||
char path[PATH_MAX]; memset(path,0,sizeof(path));
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
OSStatus default_status=SecKeychainCopyDefault(&default_keychain);
|
||||
OSStatus default_path_status=default_keychain?keychain_path(default_keychain,path):errSecInvalidKeychain;
|
||||
if(default_status==errSecSuccess && default_path_status==errSecSuccess){CC_SHA256(path,(CC_LONG)strlen(path),digest);hex(digest,sizeof(digest),out.default_sha);}
|
||||
if(default_keychain){CFRelease(default_keychain);query_releases++;default_keychain=NULL;}
|
||||
OSStatus search_status=SecKeychainCopySearchList(&search);
|
||||
OSStatus search_path_status=errSecSuccess;
|
||||
CC_SHA256_CTX hash; CC_SHA256_Init(&hash);
|
||||
if(!search||CFGetTypeID(search)!=CFArrayGetTypeID())search_path_status=errSecParam;
|
||||
if(search_status==errSecSuccess && search && search_path_status==errSecSuccess){
|
||||
out.search_count=CFArrayGetCount(search);
|
||||
uint64_t count=(uint64_t)out.search_count; CC_SHA256_Update(&hash,&count,sizeof(count));
|
||||
for(CFIndex i=0;i<out.search_count;i++){
|
||||
// CFArray values are borrowed. Releasing the owned array balances its element references.
|
||||
SecKeychainRef borrowed=(SecKeychainRef)CFArrayGetValueAtIndex(search,i);
|
||||
memset(path,0,sizeof(path)); OSStatus status=keychain_path(borrowed,path);
|
||||
if(status!=errSecSuccess){search_path_status=status;break;}
|
||||
uint32_t length=(uint32_t)strlen(path);CC_SHA256_Update(&hash,&length,sizeof(length));CC_SHA256_Update(&hash,path,length);
|
||||
}
|
||||
}
|
||||
CC_SHA256_Final(digest,&hash);hex(digest,sizeof(digest),out.search_sha);
|
||||
if(search){CFRelease(search);query_releases++;search=NULL;}
|
||||
fprintf(stdout,"{\"kind\":\"fingerprint\",\"stage\":\"%s\",\"defaultStatus\":%d,\"defaultPathStatus\":%d,\"searchStatus\":%d,\"searchPathStatus\":%d,\"defaultSha256\":\"%s\",\"searchSha256\":\"%s\",\"searchCount\":%ld,\"ownedQueryReferencesReleased\":%u}\n",stage,(int)default_status,(int)default_path_status,(int)search_status,(int)search_path_status,out.default_sha,out.search_sha,(long)out.search_count,query_releases);fflush(stdout);
|
||||
if(default_status || default_path_status || search_status || search_path_status)die("Read-only keychain fingerprint failed");
|
||||
return out;
|
||||
}
|
||||
static void checkpoint(const char *stage, unsigned operations) {
|
||||
struct mach_task_basic_info info; mach_msg_type_number_t count=MACH_TASK_BASIC_INFO_COUNT;
|
||||
kern_return_t status=task_info(mach_task_self(),MACH_TASK_BASIC_INFO,(task_info_t)&info,&count);
|
||||
if(status!=KERN_SUCCESS)die("task_info failed");
|
||||
struct timespec wall;clock_gettime(CLOCK_REALTIME,&wall);struct tm utc;gmtime_r(&wall.tv_sec,&utc);
|
||||
char stamp[40];strftime(stamp,sizeof(stamp),"%Y-%m-%dT%H:%M:%S",&utc);
|
||||
fprintf(stdout,"{\"kind\":\"checkpoint\",\"mode\":\"native-bridge-import\",\"stage\":\"%s\",\"operations\":%u,\"processId\":%d,\"utc\":\"%s.%03ldZ\",\"elapsedSeconds\":%.6f,\"rssBytes\":%llu,\"createdRefs\":%u,\"releasedRefs\":%u,\"managedRuntimePresent\":false}\n",stage,operations,getpid(),stamp,wall.tv_nsec/1000000,elapsed(),(unsigned long long)info.resident_size,created_refs,released_refs);fflush(stdout);
|
||||
char next[PATH_MAX];int n=snprintf(next,sizeof(next),"%s/continue-%s",root,stage);if(n<0 || (size_t)n>=sizeof(next))die("Checkpoint path overflow");
|
||||
while(access(next,F_OK)!=0){if(elapsed()>180)die("Checkpoint deadline exceeded");struct timespec pause={0,25000000};nanosleep(&pause,NULL);}
|
||||
}
|
||||
static int absent(const char *path){struct stat state;if(lstat(path,&state)==0)return 0;return errno==ENOENT;}
|
||||
|
||||
#define BRIDGE_SHA "0333994e217a3d111d36d7f31460eea69a286f9fc819e2d7c746df13d2aec46c"
|
||||
static int32_t (*import_cert)(const uint8_t*,int32_t,int32_t,CFStringRef,SecKeychainRef,int32_t,SecCertificateRef*,SecIdentityRef*,int32_t*);
|
||||
static int32_t (*create_key)(const uint8_t*,int32_t,uint32_t,int32_t,SecKeyRef*,CFErrorRef*);
|
||||
static SecKeyRef (*copy_public)(SecKeyRef);
|
||||
static int32_t (*external_key)(SecKeyRef,CFDataRef*,CFErrorRef*);
|
||||
static int32_t (*create_chain)(const char*,int32_t,const uint8_t*,SecKeychainRef*);
|
||||
static int32_t (*never_lock)(SecKeychainRef);
|
||||
static int32_t (*import_key)(const uint8_t*,int32_t,int32_t,SecKeyRef*,int32_t*);
|
||||
static int32_t (*move_key)(SecCertificateRef,SecKeychainRef,SecKeyRef,SecIdentityRef*,int32_t*);
|
||||
static int32_t (*copy_cert)(SecIdentityRef,SecCertificateRef*);
|
||||
static int32_t (*copy_chain)(CFTypeRef,SecKeychainRef*);
|
||||
static int32_t (*delete_chain)(SecKeychainRef);
|
||||
static unsigned all_created,all_released;
|
||||
static void owned(CFTypeRef value){if(value)all_created++;}
|
||||
static void drop(CFTypeRef *value){if(*value){CFRelease(*value);*value=NULL;all_released++;}}
|
||||
#define DROP(value) drop((CFTypeRef*)&(value))
|
||||
static void image_guard(void){
|
||||
unsigned bridge_images=0;
|
||||
for(uint32_t i=0;i<_dyld_image_count();i++){
|
||||
const char *name=_dyld_get_image_name(i);
|
||||
if(strstr(name,"libcoreclr")||strstr(name,"libhostfxr")||strstr(name,"Opc.Ua"))die("Measured child loaded a managed runtime or SDK");
|
||||
if(strstr(name,"libSystem.Security.Cryptography.Native.Apple.dylib"))bridge_images++;
|
||||
}
|
||||
if(bridge_images!=1)die("Expected exactly one pinned native bridge image");
|
||||
}
|
||||
static void load_bridge(const char *path){
|
||||
FILE *file=fopen(path,"rb");if(!file)die("Pinned bridge could not be read");
|
||||
CC_SHA256_CTX hash;CC_SHA256_Init(&hash);unsigned char bytes[8192],digest[32];size_t size;
|
||||
while((size=fread(bytes,1,sizeof(bytes),file)))CC_SHA256_Update(&hash,bytes,(CC_LONG)size);
|
||||
int bad=ferror(file);fclose(file);if(bad)die("Pinned bridge read failed");CC_SHA256_Final(digest,&hash);
|
||||
char actual[65];hex(digest,sizeof(digest),actual);if(strcmp(actual,BRIDGE_SHA))die("Pinned bridge hash changed");
|
||||
void *library=dlopen(path,RTLD_NOW|RTLD_LOCAL);if(!library)die("Pinned bridge load failed");
|
||||
#define LOAD(variable,symbol) do { *(void**)(&variable)=dlsym(library,symbol); if(!variable)die("Pinned bridge export missing"); Dl_info info;char loaded[PATH_MAX],expected[PATH_MAX];if(!dladdr((void*)variable,&info)||!realpath(info.dli_fname,loaded)||!realpath(path,expected)||strcmp(loaded,expected))die("Bridge export image identity changed"); } while(0)
|
||||
LOAD(import_cert,"AppleCryptoNative_X509ImportCertificate");LOAD(create_key,"AppleCryptoNative_SecKeyCreateWithData");
|
||||
LOAD(copy_public,"AppleCryptoNative_SecKeyCopyPublicKey");LOAD(external_key,"AppleCryptoNative_SecKeyCopyExternalRepresentation");
|
||||
LOAD(create_chain,"AppleCryptoNative_SecKeychainCreate");LOAD(never_lock,"AppleCryptoNative_SetKeychainNeverLock");
|
||||
LOAD(import_key,"AppleCryptoNative_SecKeyImportEphemeral");LOAD(move_key,"AppleCryptoNative_X509MoveToKeychain");
|
||||
LOAD(copy_cert,"AppleCryptoNative_X509CopyCertFromIdentity");LOAD(copy_chain,"AppleCryptoNative_SecKeychainItemCopyKeychain");
|
||||
LOAD(delete_chain,"AppleCryptoNative_SecKeychainDelete");
|
||||
#undef LOAD
|
||||
image_guard();
|
||||
}
|
||||
struct copy_result {const char *stage;int32_t status;int present,associated;};
|
||||
struct operation_state {
|
||||
SecCertificateRef cert,copied;SecIdentityRef identity;SecKeyRef modern,public_key,legacy;
|
||||
CFDataRef external;SecKeychainRef keychain,keychain_identity,outer;CFErrorRef error;
|
||||
char path[PATH_MAX],backing[PATH_MAX];
|
||||
unsigned logical_owners;int initial_owner,cert_tracked,copied_tracked,identity_tracked;
|
||||
int failed,path_verified,deleted,delete_during_identity_untrack,fallback_cleanup;
|
||||
int32_t cert_return,cert_status,modern_return,create_status,lock_status,external_return,key_return,key_status,move_return,move_status,copy_cert_status,delete_status,path_status;
|
||||
CFIndex modern_error,external_error;int modern_error_present,external_error_present;unsigned copy_count;
|
||||
struct copy_result copies[16];
|
||||
};
|
||||
static int private_path(struct operation_state *s,SecKeychainRef keychain){
|
||||
if(!keychain||CFGetTypeID(keychain)!=SecKeychainGetTypeID())return 0;
|
||||
char actual[PATH_MAX];memset(actual,0,sizeof(actual));int32_t status=keychain_path(keychain,actual);
|
||||
return status==0 && (!strcmp(actual,s->path)||!strcmp(actual,s->backing));
|
||||
}
|
||||
static void delete_owner(struct operation_state *s,const char *stage){
|
||||
if(!s->keychain)return;
|
||||
if(s->deleted){s->failed=1;return;}
|
||||
s->path_verified=private_path(s,s->keychain);
|
||||
if(s->path_verified){s->delete_status=delete_chain(s->keychain);s->deleted=1;}
|
||||
if(!s->path_verified||s->delete_status)s->failed=1;
|
||||
s->delete_during_identity_untrack=!strcmp(stage,"release-identity")&&s->identity!=NULL&&s->copied==NULL;
|
||||
DROP(s->keychain);released_refs++;
|
||||
}
|
||||
static void relinquish(struct operation_state *s,const char *stage){
|
||||
if(!s->logical_owners){s->failed=1;return;}
|
||||
if(--s->logical_owners==0)delete_owner(s,stage);
|
||||
}
|
||||
static SecKeychainRef copied_chain(struct operation_state *s,CFTypeRef item,const char *stage,int *associated){
|
||||
*associated=0;if(!item)return NULL;
|
||||
CFTypeID type=CFGetTypeID(item);if(type!=SecCertificateGetTypeID()&&type!=SecIdentityGetTypeID()){s->failed=1;return NULL;}
|
||||
SecKeychainRef result=NULL;int32_t status=copy_chain(item,&result);owned(result);
|
||||
int type_ok=!result||CFGetTypeID(result)==SecKeychainGetTypeID();
|
||||
int belongs=result&&type_ok&&s->keychain_identity==result&&private_path(s,result);
|
||||
if(s->copy_count<16)s->copies[s->copy_count++]=(struct copy_result){stage,status,result!=NULL,belongs};else s->failed=1;
|
||||
if(status || !type_ok || (result&&!belongs))s->failed=1;
|
||||
*associated=status==0&&belongs;return result;
|
||||
}
|
||||
static void track(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
|
||||
int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
|
||||
if(associated){if(*tracked||!s->keychain)s->failed=1;else{s->logical_owners++;*tracked=1;}}
|
||||
DROP(copy);
|
||||
}
|
||||
static void untrack(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
|
||||
if(!item)return;
|
||||
int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
|
||||
if(*tracked){if(associated){*tracked=0;relinquish(s,stage);}else s->failed=1;}
|
||||
else if(associated)s->failed=1;
|
||||
// Final logical-owner release above precedes this copy's CFRelease.
|
||||
DROP(copy);
|
||||
}
|
||||
static int leaf_equal(SecCertificateRef cert,const unsigned char *der,size_t length){
|
||||
CFDataRef data=SecCertificateCopyData(cert);owned(data);
|
||||
int equal=data&&CFGetTypeID(data)==CFDataGetTypeID()&&CFDataGetLength(data)==(CFIndex)length&&!memcmp(CFDataGetBytePtr(data),der,length);
|
||||
DROP(data);return equal;
|
||||
}
|
||||
static void import_operation(unsigned iteration,const unsigned char *der,size_t der_length,const unsigned char *key,size_t key_length){
|
||||
struct operation_state s;memset(&s,0,sizeof(s));
|
||||
s.cert_return=s.cert_status=s.modern_return=s.create_status=s.lock_status=s.external_return=s.key_return=s.key_status=s.move_return=s.move_status=s.copy_cert_status=s.delete_status=s.path_status=INT32_MIN;
|
||||
unsigned before_created=all_created,before_released=all_released;
|
||||
unsigned char random[144],password[129];char suffix[33];
|
||||
if(SecRandomCopyBytes(kSecRandomDefault,sizeof(random),random))die("Secure random failed");
|
||||
hex(random,16,suffix);const unsigned char choices[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@";
|
||||
for(unsigned i=0;i<128;i++)password[i]=choices[random[16+i]&63];password[128]=0;wipe(random,sizeof(random));
|
||||
int n=snprintf(s.path,sizeof(s.path),"%s/tmp/native-import-%s.keychain",root,suffix);
|
||||
if(n<0||(size_t)n>=sizeof(s.path))die("Private fixture path overflow");
|
||||
n=snprintf(s.backing,sizeof(s.backing),"%s-db",s.path);
|
||||
if(n<0||(size_t)n>=sizeof(s.backing)||!absent(s.path)||!absent(s.backing))die("Private fixture path already exists or overflows");
|
||||
s.cert_return=import_cert(der,(int32_t)der_length,1,NULL,NULL,1,&s.cert,&s.identity,&s.cert_status);owned(s.cert);owned(s.identity);
|
||||
track(&s,s.cert,"import-cert-track",&s.cert_tracked);
|
||||
if(s.identity)track(&s,s.identity,"unexpected-import-identity-track",&s.identity_tracked);
|
||||
if(s.cert_return!=1||s.cert_status||!s.cert||CFGetTypeID(s.cert)!=SecCertificateGetTypeID()||s.identity||s.failed){s.failed=1;goto cleanup;}
|
||||
s.modern_return=create_key(key,(int32_t)key_length,2,0,&s.modern,&s.error);owned(s.modern);owned(s.error);
|
||||
s.modern_error_present=s.error!=NULL;if(s.error){if(CFGetTypeID(s.error)==CFErrorGetTypeID())s.modern_error=CFErrorGetCode(s.error);else s.failed=1;}DROP(s.error);
|
||||
if(s.modern_return!=1||!s.modern||CFGetTypeID(s.modern)!=SecKeyGetTypeID()||s.modern_error_present||s.failed){s.failed=1;goto cleanup;}
|
||||
s.public_key=copy_public(s.modern);owned(s.public_key);if(!s.public_key||CFGetTypeID(s.public_key)!=SecKeyGetTypeID()){s.failed=1;goto cleanup;}
|
||||
s.create_status=create_chain(s.path,129,password,&s.keychain);owned(s.keychain);wipe(password,sizeof(password));
|
||||
if(s.keychain){created_refs++;s.keychain_identity=s.keychain;s.initial_owner=1;s.logical_owners=1;char actual[PATH_MAX];s.path_status=keychain_path(s.keychain,actual);s.path_verified=private_path(&s,s.keychain);}
|
||||
if(s.create_status||!s.keychain||s.path_status||!s.path_verified){s.failed=1;goto cleanup;}
|
||||
s.lock_status=never_lock(s.keychain);if(s.lock_status){s.failed=1;goto cleanup;}
|
||||
s.external_return=external_key(s.modern,&s.external,&s.error);owned(s.external);owned(s.error);
|
||||
s.external_error_present=s.error!=NULL;if(s.error){if(CFGetTypeID(s.error)==CFErrorGetTypeID())s.external_error=CFErrorGetCode(s.error);else s.failed=1;}DROP(s.error);
|
||||
if(s.external_return!=1||!s.external||CFGetTypeID(s.external)!=CFDataGetTypeID()||s.external_error_present||s.failed||CFDataGetLength(s.external)<=0||CFDataGetLength(s.external)>65536){s.failed=1;goto cleanup;}
|
||||
s.key_return=import_key(CFDataGetBytePtr(s.external),(int32_t)CFDataGetLength(s.external),1,&s.legacy,&s.key_status);owned(s.legacy);
|
||||
DROP(s.external);DROP(s.modern);DROP(s.public_key); // Installed SecKeyPair releases private before public.
|
||||
if(s.key_return!=1||s.key_status||!s.legacy||CFGetTypeID(s.legacy)!=SecKeyGetTypeID()){s.failed=1;goto cleanup;}
|
||||
int associated=0;s.outer=copied_chain(&s,s.cert,"move-outer-copy",&associated);
|
||||
untrack(&s,s.cert,"move-cert-untrack",&s.cert_tracked);
|
||||
if(s.failed)goto cleanup;
|
||||
s.move_return=move_key(s.cert,s.keychain,s.legacy,&s.identity,&s.move_status);owned(s.identity);
|
||||
track(&s,s.cert,"move-cert-track",&s.cert_tracked);track(&s,s.identity,"move-identity-track",&s.identity_tracked);DROP(s.outer);
|
||||
if(s.move_return!=1||s.move_status||!s.identity||CFGetTypeID(s.identity)!=SecIdentityGetTypeID()||!s.cert_tracked||!s.identity_tracked||s.failed){s.failed=1;goto cleanup;}
|
||||
s.copy_cert_status=copy_cert(s.identity,&s.copied);owned(s.copied);track(&s,s.copied,"identity-cert-track",&s.copied_tracked);
|
||||
if(s.copy_cert_status||!s.copied||CFGetTypeID(s.copied)!=SecCertificateGetTypeID()||!s.copied_tracked||s.failed){s.failed=1;goto cleanup;}
|
||||
if((iteration==1||iteration==REPETITIONS)&&!leaf_equal(s.copied,der,der_length)){s.failed=1;goto cleanup;}
|
||||
cleanup:
|
||||
wipe(password,sizeof(password));DROP(s.error);DROP(s.external);DROP(s.modern);DROP(s.public_key);DROP(s.outer);
|
||||
untrack(&s,s.cert,"release-original-cert",&s.cert_tracked);DROP(s.cert);
|
||||
if(s.initial_owner){s.initial_owner=0;relinquish(&s,"release-import-state");}
|
||||
untrack(&s,s.copied,"release-copied-cert",&s.copied_tracked);DROP(s.copied);
|
||||
untrack(&s,s.identity,"release-identity",&s.identity_tracked);DROP(s.identity);
|
||||
DROP(s.legacy); // Explicit lifetime; managed helper does not scope this local with using.
|
||||
if(s.keychain){s.failed=1;s.fallback_cleanup=1;delete_owner(&s,"failure-fallback-after-items");}
|
||||
unsigned made=all_created-before_created,released=all_released-before_released;
|
||||
if(made!=released||s.logical_owners||s.cert_tracked||s.copied_tracked||s.identity_tracked)s.failed=1;
|
||||
if(!s.failed&&(s.copy_count!=9||!s.delete_during_identity_untrack||!s.deleted))s.failed=1;
|
||||
fprintf(stdout,"{\"kind\":\"operation\",\"iteration\":%u,\"certReturn\":%d,\"certStatus\":%d,\"modernReturn\":%d,\"modernErrorCode\":%ld,\"modernErrorPresent\":%s,\"createStatus\":%d,\"pathStatus\":%d,\"neverLockStatus\":%d,\"externalReturn\":%d,\"externalErrorCode\":%ld,\"externalErrorPresent\":%s,\"legacyReturn\":%d,\"legacyStatus\":%d,\"moveReturn\":%d,\"moveStatus\":%d,\"copyCertStatus\":%d,\"deleteStatus\":%d,\"privatePathVerified\":%s,\"deleteDuringFinalIdentityUntrack\":%s,\"fallbackCleanup\":%s,\"logicalOwnersRemaining\":%u,\"ownedOutputs\":%u,\"releasedOutputs\":%u,\"failed\":%s,\"copies\":[",iteration,s.cert_return,s.cert_status,s.modern_return,(long)s.modern_error,s.modern_error_present?"true":"false",s.create_status,s.path_status,s.lock_status,s.external_return,(long)s.external_error,s.external_error_present?"true":"false",s.key_return,s.key_status,s.move_return,s.move_status,s.copy_cert_status,s.delete_status,s.path_verified?"true":"false",s.delete_during_identity_untrack?"true":"false",s.fallback_cleanup?"true":"false",s.logical_owners,made,released,s.failed?"true":"false");
|
||||
for(unsigned i=0;i<s.copy_count;i++){struct copy_result c=s.copies[i];fprintf(stdout,"%s{\"stage\":\"%s\",\"status\":%d,\"returnedRef\":%s,\"privateAssociation\":%s}",i?",":"",c.stage,c.status,c.present?"true":"false",c.associated?"true":"false");}
|
||||
fprintf(stdout,"]}\n");fflush(stdout);if(s.failed)die("Native bridge import operation failed after ownership cleanup");
|
||||
}
|
||||
static unsigned char *read_fixture(const char *name,size_t *length){
|
||||
char path[PATH_MAX];snprintf(path,sizeof(path),"%s/seed/%s",root,name);struct stat state;
|
||||
if(lstat(path,&state)||!S_ISREG(state.st_mode)||(state.st_mode&077)||state.st_nlink!=1||state.st_size<1||state.st_size>65536)die("Expected private bounded regular fixture");
|
||||
FILE *file=fopen(path,"rb");if(!file)die("Fixture read failed");*length=(size_t)state.st_size;
|
||||
unsigned char *bytes=malloc(*length);if(!bytes){fclose(file);die("Fixture allocation failed");}
|
||||
int bad=fread(bytes,1,*length,file)!=*length||ferror(file);fclose(file);if(bad){wipe(bytes,*length);free(bytes);die("Fixture read incomplete");}return bytes;
|
||||
}
|
||||
int main(int argc,char **argv){
|
||||
clock_gettime(CLOCK_MONOTONIC,&started);
|
||||
if(argc!=3||!realpath(argv[1],root))die("Expected private fixture root and pinned bridge path");
|
||||
struct stat state;if(stat(root,&state)||!S_ISDIR(state.st_mode)||(state.st_mode&077))die("Fixture root must be mode0700");
|
||||
load_bridge(argv[2]);size_t der_length,key_length;
|
||||
unsigned char *der=read_fixture("leaf.der",&der_length),*key=read_fixture("private.pkcs1",&key_length);
|
||||
struct fingerprint before=fingerprint("before");checkpoint("before",0);
|
||||
for(unsigned i=1;i<=REPETITIONS;i++){import_operation(i,der,der_length,key,key_length);if(i==1)checkpoint("first",i);}
|
||||
checkpoint("repeated",REPETITIONS);struct timespec idle={5,0};while(nanosleep(&idle,&idle)){if(errno!=EINTR)die("Idle delay failed");}
|
||||
struct fingerprint after=fingerprint("after");image_guard();checkpoint("idle",REPETITIONS);
|
||||
wipe(key,key_length);free(key);free(der);
|
||||
if(strcmp(before.default_sha,after.default_sha)||strcmp(before.search_sha,after.search_sha)||before.search_count!=after.search_count||created_refs!=REPETITIONS||released_refs!=REPETITIONS||all_created!=all_released)die("Fingerprint or ownership guard failed");
|
||||
fprintf(stdout,"{\"kind\":\"complete\",\"operations\":200,\"createdRefs\":%u,\"releasedRefs\":%u,\"ownedOutputs\":%u,\"releasedOutputs\":%u,\"readOnlyDefaultAndSearchListUnchanged\":true,\"managedRuntimePresent\":false,\"bridgeSha256\":\"%s\",\"elapsedSeconds\":%.6f,\"fullSoakGate\":false}\n",created_refs,released_refs,all_created,all_released,BRIDGE_SHA,elapsed());fflush(stdout);return 0;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"remainingOwnedKeychainFiles": 0
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"allLaunchedChildrenConfirmedReaped": true,
|
||||
"reapOutcomes": [
|
||||
{
|
||||
"processId": 65330,
|
||||
"confirmedReaped": true,
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"processId": 65334,
|
||||
"confirmedReaped": true,
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"processId": 65338,
|
||||
"confirmedReaped": true,
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"processId": 65339,
|
||||
"confirmedReaped": true,
|
||||
"exitCode": 1
|
||||
}
|
||||
],
|
||||
"privateMaterialOverwrittenThenUnlinkedAfterChildReaping": true,
|
||||
"physicalSecureErasureClaimed": false,
|
||||
"status": "passed",
|
||||
"ownedFixtureRootRemoved": true
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:41:42: warning: 'SecKeychainGetTypeID' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
41 | if(!keychain||CFGetTypeID(keychain)!=SecKeychainGetTypeID())return errSecParam;
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:270:10: note: 'SecKeychainGetTypeID' has been explicitly marked deprecated here
|
||||
270 | CFTypeID SecKeychainGetTypeID(void)
|
||||
| ^
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:43:21: warning: 'SecKeychainGetPath' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
43 | OSStatus status=SecKeychainGetPath(keychain,&length,path);
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:474:10: note: 'SecKeychainGetPath' has been explicitly marked deprecated here
|
||||
474 | OSStatus SecKeychainGetPath(SecKeychainRef __nullable keychain, UInt32 *ioPathLength, char *pathName)
|
||||
| ^
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:54:29: warning: 'SecKeychainCopyDefault' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
54 | OSStatus default_status=SecKeychainCopyDefault(&default_keychain);
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:384:10: note: 'SecKeychainCopyDefault' has been explicitly marked deprecated here
|
||||
384 | OSStatus SecKeychainCopyDefault(SecKeychainRef * __nonnull CF_RETURNS_RETAINED keychain)
|
||||
| ^
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:58:28: warning: 'SecKeychainCopySearchList' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
58 | OSStatus search_status=SecKeychainCopySearchList(&search);
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:404:10: note: 'SecKeychainCopySearchList' has been explicitly marked deprecated here
|
||||
404 | OSStatus SecKeychainCopySearchList(CFArrayRef * __nonnull CF_RETURNS_RETAINED searchList)
|
||||
| ^
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:145:42: warning: 'SecKeychainGetTypeID' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
145 | if(!keychain||CFGetTypeID(keychain)!=SecKeychainGetTypeID())return 0;
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:270:10: note: 'SecKeychainGetTypeID' has been explicitly marked deprecated here
|
||||
270 | CFTypeID SecKeychainGetTypeID(void)
|
||||
| ^
|
||||
<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c:166:47: warning: 'SecKeychainGetTypeID' is deprecated: first deprecated in macOS 10.10 - SecKeychain is deprecated [-Wdeprecated-declarations]
|
||||
166 | int type_ok=!result||CFGetTypeID(result)==SecKeychainGetTypeID();
|
||||
| ^
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Security.framework/Headers/SecKeychain.h:270:10: note: 'SecKeychainGetTypeID' has been explicitly marked deprecated here
|
||||
270 | CFTypeID SecKeychainGetTypeID(void)
|
||||
| ^
|
||||
6 warnings generated.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"sourcesSha256": {
|
||||
"NativeImportControl.c": "d3f8d3ed0a4e25098bc42c3bb8dca8650f2557ee8c180b61877814609ced73df",
|
||||
"ImportSeed.cs": "feaac1c93bdb0f4e59bd0a9fb213d6de53cb89ece7393356c9d12d318b395c1b",
|
||||
"native_import.py": "2a2e8b34d351e134e91df16569437893b9bf202966ee98267a81b0030a22111d",
|
||||
"pure_apple.py": "1b2148f1c20578529355379ab94b439931d7241608a5d5effce8fc86f4af7bc6"
|
||||
},
|
||||
"nativeBridgeSha256": "0333994e217a3d111d36d7f31460eea69a286f9fc819e2d7c746df13d2aec46c",
|
||||
"managedCryptographySha256": "f4957d7218a004a27bf8964e45147df6a88092c6e7344582082840d62c6c579a",
|
||||
"sdkSeedSha256": {
|
||||
"Opc.Ua.Client.dll": "620da406fabdbc75d11f9827ea7e39d2d3c762471bd2264b0d11f1d6cd9247cd",
|
||||
"Opc.Ua.Configuration.dll": "6ed6a594c93c3ff35d1a16539a9fe87c1e2640549cc2f48ed6bae333dc8bf0e7",
|
||||
"Opc.Ua.Core.Schema.dll": "51cfad48657d95d38074bb88be29aea6f5b361a07397247d58838d066b99772e",
|
||||
"Opc.Ua.Core.Types.dll": "a7dd141b8dabe8848ff28c2fb8e16a85a0508e6a49e6b1a1c1e267175b99af96",
|
||||
"Opc.Ua.Core.dll": "9d0857aa6b7f3c49037d4ea557b4da8edf07e146069464481783e66c5c9cd35a",
|
||||
"Opc.Ua.Security.Certificates.dll": "7f4969aceb2f0db5328c6882cb4b117342f3b18905c38acdba600683860c4fd0",
|
||||
"Opc.Ua.Server.dll": "930e714a2f6e15d393840bcdc95e1afa8012bc740c3738a655f958142721864e",
|
||||
"Opc.Ua.Types.dll": "c542f69882f4e8a4ebd5ccc28a3881d6967c7ffb6dcc3c56dc6c776f39c2ede1"
|
||||
},
|
||||
"compileCommand": [
|
||||
"clang",
|
||||
"-std=c11",
|
||||
"-O0",
|
||||
"-g",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"<user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/scripts/validation/opcua-lifecycle-memory/native-retention/NativeImportControl.c",
|
||||
"-framework",
|
||||
"Security",
|
||||
"-framework",
|
||||
"CoreFoundation",
|
||||
"-o",
|
||||
"/private/tmp/opcua2-native-import-20261110a/NativeImportControl"
|
||||
],
|
||||
"compiler": "Apple clang version 21.0.0 (clang-2100.0.123.102)\nTarget: arm64-apple-darwin25.5.0\nThread model: posix\nInstalledDir: /Library/Developer/CommandLineTools/usr/bin\n",
|
||||
"os": "ProductName:\t\tmacOS\nProductVersion:\t\t26.5.2\nBuildVersion:\t\t25F84\n",
|
||||
"architecture": "arm64",
|
||||
"hostDotnet": ".NET SDK:\n Version: 10.0.403\n Commit: 5a2162bea6\n Workload version: 10.0.400-manifests.a00bebb6\n MSBuild version: 18.9.15+5a2162bea\n\nRuntime Environment:\n OS Name: Mac OS X\n OS Version: 26.5\n OS Platform: Darwin\n RID: osx-arm64\n Base Path: /opt/homebrew/Cellar/dotnet/10.0.403/libexec/sdk/10.0.403/\n\n.NET workloads installed:\nThere are no installed workloads to display.\nConfigured to use workload sets when installing new manifests.\nNo workload sets are installed. Run \"dotnet workload restore\" to install a workload set.\n\nHost:\n Version: 10.0.14\n Architecture: arm64\n Commit: 0427b9abbe\n\n.NET SDKs installed:\n 10.0.403 [/opt/homebrew/Cellar/dotnet/10.0.403/libexec/sdk]\n\n.NET runtimes installed:\n Microsoft.AspNetCore.App 10.0.14 [/opt/homebrew/Cellar/dotnet/10.0.403/libexec/shared/Microsoft.AspNetCore.App]\n Microsoft.NETCore.App 10.0.14 [/opt/homebrew/Cellar/dotnet/10.0.403/libexec/shared/Microsoft.NETCore.App]\n\nOther architectures found:\n None\n\nEnvironment variables:\n Not set\n\nglobal.json file:\n <user-home>/.codex/worktrees/opcua2-client-lifecycle/OtOpcUa/global.json\n\nLearn more:\n https://aka.ms/dotnet/info\n\nDownload .NET:\n https://aka.ms/dotnet/download\n",
|
||||
"repetitions": 200,
|
||||
"stages": [
|
||||
"before",
|
||||
"first",
|
||||
"repeated",
|
||||
"idle"
|
||||
],
|
||||
"idleSeconds": 5,
|
||||
"fullSoakGate": false,
|
||||
"managedCallerInMeasuredChild": false,
|
||||
"managedPfxParsingEmulated": false,
|
||||
"legacyKeyLifetime": "Explicit CFRelease after identity disposal; managed local has no explicit using scope.",
|
||||
"wrapperNativeCopyCallsPerOperation": 9,
|
||||
"checkpointDeadlineSeconds": 180,
|
||||
"deadlineScope": "Checkpoint/child deadline; in-progress bounded probes and bounded reap add overhead.",
|
||||
"defaultOrSearchListSetters": false,
|
||||
"fixtureCreationUmask": "0077",
|
||||
"binarySha256": "e5c5caf2a8f882f82b738396156b378fd157ebd1a825d462449cd2a0497830fd",
|
||||
"seedBinarySha256": "1fa7de542aee11acab18dcc8d4096b21fcbc16e4edc1fca2243159dd1176adc0",
|
||||
"linkedLibraries": "/private/tmp/opcua2-native-import-20261110a/NativeImportControl:\n\t/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 61901.101.4)\n\t/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 4424.1.255)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)\n"
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{"kind":"fingerprint","stage":"before","defaultStatus":0,"defaultPathStatus":0,"searchStatus":0,"searchPathStatus":0,"defaultSha256":"08c2c77dd8eaf8042b4020991ea06c6215cc391532869bbf6ced2cf1126c5e63","searchSha256":"c05fae700986dc7a6a147a938ef2adc4a274173420e137929ed58ad986eb2a3c","searchCount":2,"ownedQueryReferencesReleased":2}
|
||||
{"kind":"checkpoint","mode":"native-bridge-import","stage":"before","operations":0,"processId":65339,"utc":"2026-11-10T22:25:23.068Z","elapsedSeconds":0.006057,"rssBytes":7815168,"createdRefs":0,"releasedRefs":0,"managedRuntimePresent":false}
|
||||
{"kind":"operation","iteration":1,"certReturn":1,"certStatus":0,"modernReturn":1,"modernErrorCode":0,"modernErrorPresent":false,"createStatus":0,"pathStatus":0,"neverLockStatus":0,"externalReturn":1,"externalErrorCode":0,"externalErrorPresent":false,"legacyReturn":1,"legacyStatus":0,"moveReturn":1,"moveStatus":0,"copyCertStatus":-2147483648,"deleteStatus":0,"privatePathVerified":true,"deleteDuringFinalIdentityUntrack":false,"fallbackCleanup":false,"logicalOwnersRemaining":0,"ownedOutputs":9,"releasedOutputs":9,"failed":true,"copies":[{"stage":"import-cert-track","status":0,"returnedRef":false,"privateAssociation":false},{"stage":"move-outer-copy","status":0,"returnedRef":false,"privateAssociation":false},{"stage":"move-cert-untrack","status":0,"returnedRef":false,"privateAssociation":false},{"stage":"move-cert-track","status":0,"returnedRef":true,"privateAssociation":true},{"stage":"move-identity-track","status":0,"returnedRef":false,"privateAssociation":false},{"stage":"release-original-cert","status":0,"returnedRef":true,"privateAssociation":true},{"stage":"release-identity","status":0,"returnedRef":false,"privateAssociation":false}]}
|
||||
{"kind":"failed","message":"Native bridge import operation failed after ownership cleanup","createdRefs":1,"releasedRefs":1,"elapsedSeconds":1.172310}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
Determining projects to restore...
|
||||
Restored /private/tmp/opcua2-native-import-20261110a/ImportSeed.csproj (in 31 ms).
|
||||
ImportSeed -> /private/tmp/opcua2-native-import-20261110a/bin/Release/net10.0/ImportSeed.dll
|
||||
|
||||
Build succeeded.
|
||||
0 Warning(s)
|
||||
0 Error(s)
|
||||
|
||||
Time Elapsed 00:00:01.25
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"kind":"seed","leafSha256":"31a4b6181cc10f0bc862a83199688b35d3c5e2590a2c00ab4cae6c3b1dc3d179","runtime":"10.0.14","sdkVersion":"2.0.1.0","managerDisposed":true,"certificateCoresOutstanding":0,"keyFormat":"PKCS1-private-RSA","privateFileMode":"0600","cryptographyRuntime":{"informationalVersion":"10.0.14\u002B0427b9abbead3fa64c6655eb4290c537397131fa","sha256":"f4957d7218a004a27bf8964e45147df6a88092c6e7344582082840d62c6c579a"}}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"status": "failed-partial-control-model",
|
||||
"projection": "Replace local user-home prefix only; raw receipts unchanged.",
|
||||
"rawOutput": "/tmp/opcua2-native-import-20261110a",
|
||||
"rawSha256": {
|
||||
"samples.jsonl": "06ca824cf08e2c26e595a4e70091f2f5a953ac3022908966ae9ee99987c1043d",
|
||||
"provenance.json": "ad87286e153b8235aa13f3a60e9bc973f9c8886a038e947ed11304361e236590",
|
||||
"native-build.log": "2fd25e9c530541c224872a28cf03b7d720be28fdd0216946b533d6292aad1a75",
|
||||
"seed-build.log": "bd5eb3fbfeb649aa12254fa52689a778c97364c832bee0b6968f0432da129022",
|
||||
"seed.jsonl": "8b4d9cb09fc134ffed468d4a0acc12d317cb9a77c0f90569ef52e83577baf333",
|
||||
"fixture-cleanup.json": "0d5c8fe16558d2a21406c88d62ed87690649d3deac7bd333d8741672a23f86c7",
|
||||
"fixture-before-harness-cleanup.json": "eaaf36a5e7f7d1d3635e987d8f9a2600ce55f6482011ec9a7abf3e4dd759d9c4"
|
||||
},
|
||||
"selectedProjectionSha256": {
|
||||
"samples.jsonl": "06ca824cf08e2c26e595a4e70091f2f5a953ac3022908966ae9ee99987c1043d",
|
||||
"provenance.json": "c6f6453edfb0c0601099e6275303359abec52f7566a47961a640bd97d554cbe9",
|
||||
"native-build.log": "05fac84aff441e7a6b5ce2c3ac1d68cc3705b85495c39dd339964168335c5316",
|
||||
"seed-build.log": "bd5eb3fbfeb649aa12254fa52689a778c97364c832bee0b6968f0432da129022",
|
||||
"seed.jsonl": "8b4d9cb09fc134ffed468d4a0acc12d317cb9a77c0f90569ef52e83577baf333",
|
||||
"fixture-cleanup.json": "0d5c8fe16558d2a21406c88d62ed87690649d3deac7bd333d8741672a23f86c7",
|
||||
"fixture-before-harness-cleanup.json": "eaaf36a5e7f7d1d3635e987d8f9a2600ce55f6482011ec9a7abf3e4dd759d9c4"
|
||||
},
|
||||
"rawLocalOnlySnapshotsSha256": {
|
||||
"heap-before.txt": "de4896c196fcf96e58933716049772e523e268be932cef7f9a76cf270f71cd79",
|
||||
"lsof-before.txt": "f12e1115e87c4110c2961cb61c0b28ea3395c2ae780a72b431d71d4bfdd2a965"
|
||||
},
|
||||
"retainedInstalledBinaryInspectionSha256": {
|
||||
"opcua2-native-runtime-loader-decompiled.cs": "89434500d1723f419e83ff45fcb6e44e73b58847f4201265613587ad82a79424",
|
||||
"opcua2-native-runtime-pkcs12key-decompiled.cs": "60af9993d62032adf6be178efd93eef0f9c1a7a1bb2e091938d7171ab12875e2",
|
||||
"opcua2-native-runtime-pkcs12key-base-decompiled.cs": "cf1e412c9752822610e6e9c96341d2b83052e2e284edc3d1c0086e95893a39b0",
|
||||
"opcua2-native-runtime-rsa-decompiled.cs": "a4d9bdc8c0d78dac63f19639fb1da319e209a0f4af324edefd0fac8bc4b3becc",
|
||||
"opcua2-native-runtime-applepal-decompiled.cs": "94e7ead2224719e0102983e0a13c97a9c713018640d73a7153ffea6f732312bf",
|
||||
"opcua2-native-runtime-appleinterop-decompiled.cs": "da9b4ad8bd114e5b58a82607b37fbc8bc71f79a3a56dead60c88e718d8758c92",
|
||||
"opcua2-native-runtime-keychainitem-decompiled.cs": "5fb58039e59d4839198fd74ed98978eed0d57008322489fd08d70d6481dd4fd4",
|
||||
"opcua2-native-runtime-keyhandle-decompiled.cs": "595f2cf7d1c71a13dbdd21379a0ab072ce7d629753ca01f59e57bf789bec223e",
|
||||
"opcua2-native-runtime-keypair-decompiled.cs": "623cf239bd9f755c701ba50ceffd4043591538111ca72261af4cd59c97db61c9",
|
||||
"opcua2-native-runtime-certhandle-decompiled.cs": "d53053dfee192fe336e6fae055199ddcf4340da805ed8744e5e52c66f685e453",
|
||||
"opcua2-native-runtime-identityhandle-decompiled.cs": "80b94acf27a0fbe38efcdc30c86e003ee2abfea30a3da5e9d535e1373ce97069",
|
||||
"opcua2-native-runtime-keychain-decompiled.cs": "96c2e996638ffdd17c35483aaece7fdbc499fd004ab549e2e128bf82a64b5dc2",
|
||||
"opcua2-native-runtime-keychain-base-decompiled.cs": "6f2abdb5779646a9bfd589d8fc765cab2e0290edee8396b2c7b2e6a4470e2bb4",
|
||||
"opcua2-native-runtime-x509contenttype-decompiled.cs": "bed5256e025ed5b85f04b4cce132fcd7279fb06b5dd377d25f4caf39a8682b60",
|
||||
"opcua2-native-runtime-apple-disassembly.txt": "75af92a3b05e4076025d43389cb3e51d8f6c8547aa485a4b5984469af7f02935"
|
||||
},
|
||||
"noRepeatCompletionOrRetentionInference": true
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One fixed-200 native bridge import diagnostic; build/run only after review."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from pure_apple import complete_rows, save, snapshot, STAGES
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
RUNTIME = Path('/opt/homebrew/Cellar/dotnet/10.0.403/libexec/shared/Microsoft.NETCore.App/10.0.14')
|
||||
BRIDGE = RUNTIME / 'libSystem.Security.Cryptography.Native.Apple.dylib'
|
||||
BRIDGE_SHA = '0333994e217a3d111d36d7f31460eea69a286f9fc819e2d7c746df13d2aec46c'
|
||||
CRYPTO_SHA = 'f4957d7218a004a27bf8964e45147df6a88092c6e7344582082840d62c6c579a'
|
||||
DOTNET = '/opt/homebrew/Cellar/dotnet/10.0.403/libexec/dotnet'
|
||||
EXPECTED_SDK = {
|
||||
'Opc.Ua.Client.dll': '620da406fabdbc75d11f9827ea7e39d2d3c762471bd2264b0d11f1d6cd9247cd',
|
||||
'Opc.Ua.Configuration.dll': '6ed6a594c93c3ff35d1a16539a9fe87c1e2640549cc2f48ed6bae333dc8bf0e7',
|
||||
'Opc.Ua.Core.Schema.dll': '51cfad48657d95d38074bb88be29aea6f5b361a07397247d58838d066b99772e',
|
||||
'Opc.Ua.Core.Types.dll': 'a7dd141b8dabe8848ff28c2fb8e16a85a0508e6a49e6b1a1c1e267175b99af96',
|
||||
'Opc.Ua.Core.dll': '9d0857aa6b7f3c49037d4ea557b4da8edf07e146069464481783e66c5c9cd35a',
|
||||
'Opc.Ua.Security.Certificates.dll': '7f4969aceb2f0db5328c6882cb4b117342f3b18905c38acdba600683860c4fd0',
|
||||
'Opc.Ua.Server.dll': '930e714a2f6e15d393840bcdc95e1afa8012bc740c3738a655f958142721864e',
|
||||
'Opc.Ua.Types.dll': 'c542f69882f4e8a4ebd5ccc28a3881d6967c7ffb6dcc3c56dc6c776f39c2ede1',
|
||||
}
|
||||
CHILDREN = []
|
||||
REAP_OUTCOMES = []
|
||||
COPY_STAGES = ['import-cert-track', 'move-outer-copy', 'move-cert-untrack', 'move-cert-track',
|
||||
'move-identity-track', 'identity-cert-track', 'release-original-cert',
|
||||
'release-copied-cert', 'release-identity']
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def reap(process):
|
||||
outcome = {'processId': process.pid, 'confirmedReaped': False}
|
||||
try:
|
||||
if process.poll() is None:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
try:
|
||||
process.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
process.wait(timeout=5)
|
||||
outcome['exitCode'] = process.wait(timeout=0)
|
||||
outcome['confirmedReaped'] = True
|
||||
except Exception as error:
|
||||
outcome['errorType'] = type(error).__name__
|
||||
raise
|
||||
finally:
|
||||
REAP_OUTCOMES.append(outcome)
|
||||
|
||||
|
||||
def bounded(command, log, timeout, env=None):
|
||||
with log.open('w') as stream:
|
||||
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
env=env, start_new_session=True)
|
||||
CHILDREN.append(process)
|
||||
try:
|
||||
status = process.wait(timeout=timeout)
|
||||
if status:
|
||||
raise RuntimeError('Bounded subprocess failed: ' + log.name)
|
||||
finally:
|
||||
reap(process)
|
||||
|
||||
|
||||
def wipe_owned(root):
|
||||
"""Best-effort caller-owned file overwrite/unlink; no physical secure-erasure claim."""
|
||||
if not root.exists():
|
||||
return
|
||||
files = [p for p in root.rglob('*') if p.is_file() and not p.is_symlink()]
|
||||
if len(files) > 100 or any(p.stat().st_size > 1024 * 1024 or p.stat().st_nlink != 1 for p in files):
|
||||
raise RuntimeError('Unexpected owned fixture cleanup size/link layout; preserve private root')
|
||||
for path in files:
|
||||
size = path.stat().st_size
|
||||
with path.open('r+b') as stream:
|
||||
stream.write(bytes(size)); stream.flush(); os.fsync(stream.fileno())
|
||||
shutil.rmtree(root)
|
||||
|
||||
|
||||
def main():
|
||||
os.umask(0o077) # This collector and its children only; includes normal SDK seed PFX files.
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--references', required=True, type=Path)
|
||||
parser.add_argument('--output', required=True, type=Path, help='Fresh private output directory')
|
||||
args = parser.parse_args()
|
||||
if sys.platform != 'darwin' or os.uname().machine != 'arm64':
|
||||
parser.error('Pinned diagnostic requires macOS arm64')
|
||||
if sha(BRIDGE) != BRIDGE_SHA or sha(RUNTIME / 'System.Security.Cryptography.dll') != CRYPTO_SHA:
|
||||
raise RuntimeError('Installed native/managed cryptography identity changed')
|
||||
references = args.references.resolve()
|
||||
if {p.name: sha(p) for p in references.glob('Opc.Ua*.dll')} != EXPECTED_SDK:
|
||||
raise RuntimeError('Pinned SDK2 seed binary set changed')
|
||||
output = args.output.resolve(); output.mkdir(parents=True, exist_ok=False); os.chmod(output, 0o700)
|
||||
binary = output / 'NativeImportControl'
|
||||
command = ['clang', '-std=c11', '-O0', '-g', '-Wall', '-Wextra', str(HERE / 'NativeImportControl.c'),
|
||||
'-framework', 'Security', '-framework', 'CoreFoundation', '-o', str(binary)]
|
||||
provenance = {'sourcesSha256': {name: sha(HERE / name) for name in ['NativeImportControl.c', 'ImportSeed.cs', 'native_import.py', 'pure_apple.py']},
|
||||
'nativeBridgeSha256': BRIDGE_SHA, 'managedCryptographySha256': CRYPTO_SHA, 'sdkSeedSha256': EXPECTED_SDK,
|
||||
'compileCommand': command, 'compiler': subprocess.check_output(['clang', '--version'], text=True, timeout=10),
|
||||
'os': subprocess.check_output(['sw_vers'], text=True, timeout=10), 'architecture': os.uname().machine,
|
||||
'hostDotnet': subprocess.check_output([DOTNET, '--info'], text=True, timeout=10),
|
||||
'repetitions': 200, 'stages': STAGES, 'idleSeconds': 5, 'fullSoakGate': False,
|
||||
'managedCallerInMeasuredChild': False, 'managedPfxParsingEmulated': False,
|
||||
'legacyKeyLifetime': 'Explicit CFRelease after identity disposal; managed local has no explicit using scope.',
|
||||
'wrapperNativeCopyCallsPerOperation': 9, 'checkpointDeadlineSeconds': 180,
|
||||
'deadlineScope': 'Checkpoint/child deadline; in-progress bounded probes and bounded reap add overhead.',
|
||||
'defaultOrSearchListSetters': False, 'fixtureCreationUmask': '0077'}
|
||||
save(output / 'provenance.json', provenance)
|
||||
bounded(command, output / 'native-build.log', 60)
|
||||
project = ET.Element('Project', Sdk='Microsoft.NET.Sdk'); properties = ET.SubElement(project, 'PropertyGroup')
|
||||
for name, value in {'OutputType': 'Exe', 'TargetFramework': 'net10.0', 'ImplicitUsings': 'enable', 'Nullable': 'enable',
|
||||
'EnableDefaultCompileItems': 'false', 'TreatWarningsAsErrors': 'true', 'UseSharedCompilation': 'false'}.items():
|
||||
ET.SubElement(properties, name).text = value
|
||||
items = ET.SubElement(project, 'ItemGroup'); ET.SubElement(items, 'FrameworkReference', Include='Microsoft.AspNetCore.App')
|
||||
ET.SubElement(items, 'Compile', Include=str(HERE / 'ImportSeed.cs'))
|
||||
for name in EXPECTED_SDK:
|
||||
ref = ET.SubElement(items, 'Reference', Include=Path(name).stem); ET.SubElement(ref, 'HintPath').text = str(references / name)
|
||||
ET.indent(project); ET.ElementTree(project).write(output / 'ImportSeed.csproj', encoding='unicode')
|
||||
bounded([DOTNET, 'build', str(output / 'ImportSeed.csproj'), '-c', 'Release', '-m:1', '--disable-build-servers', '--nologo'], output / 'seed-build.log', 120)
|
||||
provenance['binarySha256'] = sha(binary)
|
||||
provenance['seedBinarySha256'] = sha(output / 'bin/Release/net10.0/ImportSeed.dll')
|
||||
provenance['linkedLibraries'] = subprocess.check_output(['otool', '-L', str(binary)], text=True, timeout=10)
|
||||
save(output / 'provenance.json', provenance)
|
||||
task = output / 'task'; task.mkdir(mode=0o700)
|
||||
for name in ['seed', 'seed-tmp', 'seed-data', 'tmp']:
|
||||
(task / name).mkdir(mode=0o700)
|
||||
try:
|
||||
bounded([DOTNET, str(output / 'bin/Release/net10.0/ImportSeed.dll'), str(task / 'seed')], output / 'seed.jsonl', 75,
|
||||
env=dict(os.environ, TMPDIR=str(task / 'seed-tmp'), XDG_DATA_HOME=str(task / 'seed-data')))
|
||||
seeds = complete_rows(output / 'seed.jsonl')
|
||||
if len(seeds) != 1 or seeds[0]['kind'] != 'seed' or seeds[0]['sdkVersion'] != '2.0.1.0' or seeds[0]['cryptographyRuntime']['sha256'] != CRYPTO_SHA:
|
||||
raise RuntimeError('Seed/runtime count or identity mismatch')
|
||||
if sha(task / 'seed/leaf.der') != seeds[0]['leafSha256']:
|
||||
raise RuntimeError('Seed leaf file identity mismatch')
|
||||
if any(p.stat().st_mode & 0o077 for p in (task / 'seed').rglob('*') if p.is_file()):
|
||||
raise RuntimeError('Seed fixture file permissions are not private0600')
|
||||
for name in ['pki']:
|
||||
wipe_owned(task / 'seed' / name)
|
||||
wipe_owned(task / 'seed-tmp'); wipe_owned(task / 'seed-data')
|
||||
if sha(BRIDGE) != BRIDGE_SHA:
|
||||
raise RuntimeError('Native bridge changed before measured execution')
|
||||
started = time.monotonic()
|
||||
with (output / 'samples.jsonl').open('w') as log:
|
||||
process = subprocess.Popen([str(binary), str(task), str(BRIDGE)], stdout=log, stderr=subprocess.STDOUT,
|
||||
env=dict(os.environ, TMPDIR=str(task / 'tmp')), start_new_session=True)
|
||||
CHILDREN.append(process)
|
||||
try:
|
||||
deadline = time.monotonic() + 180; samples = []
|
||||
for stage in STAGES:
|
||||
while True:
|
||||
rows = complete_rows(output / 'samples.jsonl')
|
||||
if any(r['kind'] == 'failed' for r in rows):
|
||||
raise RuntimeError('Native import failed; preserve all status/ownership rows')
|
||||
checkpoints = [r for r in rows if r['kind'] == 'checkpoint' and r.get('stage') == stage]
|
||||
if checkpoints:
|
||||
samples.append({**checkpoints[0], **snapshot(process.pid, task, output, stage)})
|
||||
(task / ('continue-' + stage)).touch(); break
|
||||
if process.poll() is not None or time.monotonic() >= deadline:
|
||||
raise RuntimeError('Native checkpoint exceeded bound: ' + stage)
|
||||
time.sleep(.05)
|
||||
status = process.wait(timeout=15)
|
||||
rows = complete_rows(output / 'samples.jsonl')
|
||||
operations = [r for r in rows if r['kind'] == 'operation']; fingerprints = [r for r in rows if r['kind'] == 'fingerprint']
|
||||
complete = [r for r in rows if r['kind'] == 'complete']
|
||||
if status or len(rows) != 207 or len(operations) != 200 or len(fingerprints) != 2 or len(complete) != 1 or [r['iteration'] for r in operations] != list(range(1, 201)):
|
||||
raise RuntimeError('Native fixed operation/completion counts failed')
|
||||
for row in operations:
|
||||
if row['failed'] or row['fallbackCleanup'] or not row['privatePathVerified'] or not row['deleteDuringFinalIdentityUntrack'] or row['logicalOwnersRemaining'] or row['modernErrorPresent'] or row['externalErrorPresent'] or row['ownedOutputs'] != row['releasedOutputs']:
|
||||
raise RuntimeError('Native ownership order/cleanup failed')
|
||||
if [c['stage'] for c in row['copies']] != COPY_STAGES or any(c['status'] for c in row['copies']) or [c['returnedRef'] for c in row['copies']] != [False] * 3 + [True] * 6:
|
||||
raise RuntimeError('Native wrapper-copy sequence failed')
|
||||
if any(row[k] for k in ['certStatus', 'createStatus', 'pathStatus', 'neverLockStatus', 'legacyStatus', 'moveStatus', 'copyCertStatus', 'deleteStatus', 'modernErrorCode', 'externalErrorCode']) or any(row[k] != 1 for k in ['certReturn', 'modernReturn', 'externalReturn', 'legacyReturn', 'moveReturn']):
|
||||
raise RuntimeError('Native bridge statuses failed')
|
||||
if sha(task / 'seed/leaf.der') != seeds[0]['leafSha256'] or complete[0]['operations'] != 200 or complete[0]['bridgeSha256'] != BRIDGE_SHA or complete[0]['ownedOutputs'] != complete[0]['releasedOutputs']:
|
||||
raise RuntimeError('Native identity/completion ownership guard failed')
|
||||
if any(samples[-1][k] for k in ['ownedKeychainFdCount', 'ownedKeychainFileCount']):
|
||||
raise RuntimeError('Private keychain files/descriptors retained')
|
||||
save(output / 'result.json', {'status': 'passed', 'seed': seeds[0], 'samples': samples, 'fingerprints': fingerprints,
|
||||
'complete': complete[0], 'wallSecondsIncludingProbes': time.monotonic() - started, 'fullSoakGate': False,
|
||||
'nativeInventoryIsNotOpenHandlesOrExclusiveRss': True, 'measuredChildManagedRuntimePresent': False})
|
||||
finally:
|
||||
reap(process)
|
||||
finally:
|
||||
save(output / 'fixture-before-harness-cleanup.json', {'remainingOwnedKeychainFiles': sum(p.is_file() and '.keychain' in p.name for p in (task / 'tmp').rglob('*'))})
|
||||
all_reaped = all(child.returncode is not None for child in CHILDREN)
|
||||
cleanup = {'allLaunchedChildrenConfirmedReaped': all_reaped, 'reapOutcomes': REAP_OUTCOMES,
|
||||
'privateMaterialOverwrittenThenUnlinkedAfterChildReaping': False, 'physicalSecureErasureClaimed': False}
|
||||
if not all_reaped:
|
||||
cleanup.update(status='failed-preserved-private-fixture', ownedFixtureRootRemoved=False)
|
||||
save(output / 'fixture-cleanup.json', cleanup)
|
||||
raise RuntimeError('A launched child was not reaped; private fixture preserved without overwrite/unlink')
|
||||
try:
|
||||
wipe_owned(task)
|
||||
cleanup.update(status='passed', ownedFixtureRootRemoved=not task.exists(),
|
||||
privateMaterialOverwrittenThenUnlinkedAfterChildReaping=True)
|
||||
except Exception as error:
|
||||
cleanup.update(status='failed', errorType=type(error).__name__, ownedFixtureRootRemoved=not task.exists())
|
||||
raise
|
||||
finally:
|
||||
save(output / 'fixture-cleanup.json', cleanup)
|
||||
print(output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user