283 lines
24 KiB
C
283 lines
24 KiB
C
// 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,*action;int32_t status;int present,associated;
|
|
unsigned owners_before,owners_after;int native_owner_before,native_owner_after;
|
|
};
|
|
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,fallback_cleanup,import_state_released,leaf_checked,leaf_matches;
|
|
unsigned import_owners_before,import_owners_after;
|
|
const char *delete_stage;
|
|
int delete_copy_live,delete_cert_live,delete_copied_live,delete_identity_live,delete_legacy_live;
|
|
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,int copy_live){
|
|
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_stage=stage;s->delete_copy_live=copy_live;s->delete_cert_live=s->cert!=NULL;
|
|
s->delete_copied_live=s->copied!=NULL;s->delete_identity_live=s->identity!=NULL;s->delete_legacy_live=s->legacy!=NULL;
|
|
DROP(s->keychain);released_refs++;
|
|
}
|
|
static void relinquish(struct operation_state *s,const char *stage,int copy_live){
|
|
if(!s->logical_owners){s->failed=1;return;}
|
|
if(--s->logical_owners==0)delete_owner(s,stage,copy_live);
|
|
}
|
|
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,"lookup",status,result!=NULL,belongs,
|
|
s->logical_owners,s->logical_owners,s->keychain!=NULL,s->keychain!=NULL};else s->failed=1;
|
|
if(status || !type_ok || (result&&!belongs))s->failed=1;
|
|
*associated=status==0&&belongs;return result;
|
|
}
|
|
static void record_copy_action(struct operation_state *s,unsigned index,const char *action){
|
|
if(s->copy_count==index+1){struct copy_result *copy=&s->copies[index];copy->action=action;
|
|
copy->owners_after=s->logical_owners;copy->native_owner_after=s->keychain!=NULL;}
|
|
}
|
|
static void track(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
|
|
unsigned index=s->copy_count;int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
|
|
// Installed TrackItem does nothing for a null copy; never invent an owner.
|
|
if(associated){if(*tracked||!s->keychain)s->failed=1;else{s->logical_owners++;*tracked=1;}}
|
|
record_copy_action(s,index,"track");DROP(copy);
|
|
}
|
|
static void untrack(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
|
|
if(!item)return;
|
|
unsigned index=s->copy_count;int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
|
|
if(*tracked){if(associated){*tracked=0;relinquish(s,stage,copy!=NULL);}else s->failed=1;}
|
|
// A still-registered private keychain must never consume an untracked owner.
|
|
// After deletion the installed managed registry has no entry to release.
|
|
else if(associated&&s->keychain)s->failed=1;
|
|
record_copy_action(s,index,"untrack");
|
|
// 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.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.failed){s.failed=1;goto cleanup;}
|
|
if(iteration==1||iteration==REPETITIONS){s.leaf_checked=1;s.leaf_matches=leaf_equal(s.copied,der,der_length);
|
|
if(!s.leaf_matches){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;s.import_state_released=1;s.import_owners_before=s.logical_owners;
|
|
relinquish(&s,"release-import-state",0);s.import_owners_after=s.logical_owners;}
|
|
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",0);}
|
|
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.import_state_released||!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,\"deleteStage\":\"%s\",\"deleteLiveReferences\":{\"copy\":%s,\"originalCertificate\":%s,\"copiedCertificate\":%s,\"identity\":%s,\"legacyKey\":%s},\"importStateReleased\":%s,\"importOwnersBefore\":%u,\"importOwnersAfter\":%u,\"leafIdentityChecked\":%s,\"leafIdentityMatches\":%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_stage?s.delete_stage:"not-attempted",s.delete_copy_live?"true":"false",s.delete_cert_live?"true":"false",s.delete_copied_live?"true":"false",s.delete_identity_live?"true":"false",s.delete_legacy_live?"true":"false",s.import_state_released?"true":"false",s.import_owners_before,s.import_owners_after,s.leaf_checked?"true":"false",s.leaf_matches?"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\",\"action\":\"%s\",\"status\":%d,\"returnedRef\":%s,\"privateAssociation\":%s,\"logicalOwnersBefore\":%u,\"logicalOwnersAfter\":%u,\"nativeOwnerBefore\":%s,\"nativeOwnerAfter\":%s}",i?",":"",c.stage,c.action,c.status,c.present?"true":"false",c.associated?"true":"false",c.owners_before,c.owners_after,c.native_owner_before?"true":"false",c.native_owner_after?"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;
|
|
}
|