Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions crates/enclaveapp-apple/src/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@ impl EnclaveEncryptor for SecureEnclaveEncryptor {
};

if rc != 0 {
return Err(Error::EncryptFailed {
detail: format!("FFI returned error code {rc}"),
});
let detail = match keychain::last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::EncryptFailed { detail });
}

ciphertext.truncate(ciphertext_len as usize);
Expand Down Expand Up @@ -155,9 +157,11 @@ impl EnclaveEncryptor for SecureEnclaveEncryptor {
};

if rc != 0 {
return Err(Error::DecryptFailed {
detail: format!("FFI returned error code {rc}"),
});
let detail = match keychain::last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::DecryptFailed { detail });
}

plaintext.truncate(plaintext_len as usize);
Expand Down
2 changes: 2 additions & 0 deletions crates/enclaveapp-apple/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ extern "C" {
pub_key_len: *mut i32,
) -> i32;

pub fn enclaveapp_se_last_error(buf: *mut u8, buf_len: *mut i32) -> i32;

pub fn enclaveapp_se_delete_key(data_rep: *const u8, data_rep_len: i32) -> i32;

pub fn enclaveapp_se_encrypt(
Expand Down
33 changes: 28 additions & 5 deletions crates/enclaveapp-apple/src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ use zeroize::Zeroizing;

const SE_ERR_BUFFER_TOO_SMALL: i32 = 4;

#[allow(unsafe_code)]
pub(crate) fn last_bridge_error() -> Option<String> {
let mut buf = vec![0_u8; 1024];
let mut buf_len: i32 = buf.len() as i32;
let rc = unsafe { ffi::enclaveapp_se_last_error(buf.as_mut_ptr(), &mut buf_len) };
if rc != 0 || buf_len <= 0 {
return None;
}
let len = buf_len as usize;
buf.truncate(len);
String::from_utf8(buf).ok().filter(|s| !s.is_empty())
}

/// Configuration for keychain operations, scoped to an application.
#[derive(Debug)]
pub struct KeychainConfig {
Expand Down Expand Up @@ -195,9 +208,11 @@ where
}

if rc != 0 {
return Err(Error::GenerateFailed {
detail: format!("FFI returned error code {rc}"),
});
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::GenerateFailed { detail });
}

// Contract sanity: pub_key buffer is fixed at 65 bytes.
Expand Down Expand Up @@ -552,9 +567,13 @@ pub fn public_key_from_data_rep(key_type: KeyType, data_rep: &[u8]) -> Result<Ve
};

if rc != 0 {
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::KeyOperation {
operation: "public_key".into(),
detail: format!("FFI returned error code {rc}"),
detail,
});
}

Expand Down Expand Up @@ -891,9 +910,13 @@ fn delete_key_from_data_rep(data_rep: &[u8]) -> Result<()> {
if rc == 0 {
Ok(())
} else {
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
Err(Error::KeyOperation {
operation: "delete_key".into(),
detail: format!("FFI returned error code {rc}"),
detail,
})
}
}
Expand Down
10 changes: 7 additions & 3 deletions crates/enclaveapp-apple/src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@ impl SecureEnclaveSigner {
// The LAContext is still valid; don't evict it.
label: label.to_string(),
},
_ => Error::SignFailed {
detail: format!("FFI returned error code {rc}"),
},
_ => {
let detail = match keychain::last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
Error::SignFailed { detail }
}
});
}

Expand Down
56 changes: 53 additions & 3 deletions crates/enclaveapp-apple/swift/bridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,41 @@ let SE_ERR_KEYCHAIN_NO_WINDOW_SERVER: Int32 = 15
/// NOT evict the LAContext cache for this label.
let SE_ERR_USER_CANCEL: Int32 = 16

// MARK: - Thread-local last error detail

private var _lastError: String = ""
private let _lastErrorLock = NSLock()

func setLastError(_ msg: String) {
_lastErrorLock.lock()
_lastError = msg
_lastErrorLock.unlock()
}

func clearLastError() {
_lastErrorLock.lock()
_lastError = ""
_lastErrorLock.unlock()
}

@_cdecl("enclaveapp_se_last_error")
public func enclaveapp_se_last_error(
_ buf: UnsafeMutablePointer<UInt8>,
_ buf_len: UnsafeMutablePointer<Int32>
) -> Int32 {
_lastErrorLock.lock()
let msg = _lastError
_lastErrorLock.unlock()
let data = Data(msg.utf8)
if buf_len.pointee < Int32(data.count) {
buf_len.pointee = Int32(data.count)
return SE_ERR_BUFFER_TOO_SMALL
}
data.copyBytes(to: buf, count: data.count)
buf_len.pointee = Int32(data.count)
return SE_OK
}

// MARK: - ECIES format constants

let ECIES_VERSION: UInt8 = 0x01
Expand Down Expand Up @@ -232,11 +267,19 @@ func makeAccessControl(_ authPolicy: Int32) -> SecAccessControl? {
case 1: flags.insert(.userPresence)
case 2: flags.insert(.biometryAny)
case 3: flags.insert(.devicePasscode)
default: return nil
default:
setLastError("unsupported auth_policy value \(authPolicy)")
return nil
}
return SecAccessControlCreateWithFlags(
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags, nil
var error: Unmanaged<CFError>?
let ac = SecAccessControlCreateWithFlags(
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags, &error
)
if ac == nil {
let desc = error.map { $0.takeRetainedValue().localizedDescription } ?? "unknown"
setLastError("SecAccessControlCreateWithFlags failed: \(desc)")
}
return ac
}

// MARK: - Helper: copy uncompressed public key
Expand Down Expand Up @@ -340,6 +383,7 @@ public func enclaveapp_se_generate_signing_key(

return copyDataRep(key.dataRepresentation, data_rep_out, data_rep_len)
} catch {
setLastError("key generation failed: \(error.localizedDescription)")
return SE_ERR_GENERATE
}
}
Expand All @@ -359,6 +403,7 @@ public func enclaveapp_se_signing_public_key(
let key = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: data)
return copyUncompressedPubKey(key.publicKey.rawRepresentation, pub_key_out, pub_key_len)
} catch {
setLastError("key load failed: \(error.localizedDescription)")
return SE_ERR_LOAD
}
}
Expand Down Expand Up @@ -437,6 +482,7 @@ public func enclaveapp_se_sign(
"enclaveapp: se_sign: \(nsErr.domain) \(nsErr.code): \(nsErr.localizedDescription)\n"
).utf8))
}
setLastError("sign failed: \(nsErr.domain) \(nsErr.code): \(nsErr.localizedDescription)")
return SE_ERR_SIGN
}
}
Expand Down Expand Up @@ -474,6 +520,7 @@ public func enclaveapp_se_generate_encryption_key(

return copyDataRep(key.dataRepresentation, data_rep_out, data_rep_len)
} catch {
setLastError("key generation failed: \(error.localizedDescription)")
return SE_ERR_GENERATE
}
}
Expand All @@ -491,6 +538,7 @@ public func enclaveapp_se_encryption_public_key(
let key = try SecureEnclave.P256.KeyAgreement.PrivateKey(dataRepresentation: data)
return copyUncompressedPubKey(key.publicKey.rawRepresentation, pub_key_out, pub_key_len)
} catch {
setLastError("key load failed: \(error.localizedDescription)")
return SE_ERR_LOAD
}
}
Expand Down Expand Up @@ -575,6 +623,7 @@ public func enclaveapp_se_encrypt(

return SE_OK
} catch {
setLastError("encrypt failed: \(error.localizedDescription)")
return SE_ERR_ENCRYPT
}
}
Expand Down Expand Up @@ -656,6 +705,7 @@ public func enclaveapp_se_decrypt(

return SE_OK
} catch {
setLastError("decrypt failed: \(error.localizedDescription)")
return SE_ERR_DECRYPT
}
}
Expand Down
Loading