Build secure Rust watchOS TOTP core (#56)
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -5135,6 +5135,14 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ironstorage-watch-apple"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ironstorage",
|
||||||
|
"uniffi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_executable"
|
name = "is_executable"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ members = [
|
|||||||
"apps/tui",
|
"apps/tui",
|
||||||
"crates/apple",
|
"crates/apple",
|
||||||
"crates/storage",
|
"crates/storage",
|
||||||
|
"crates/watch-apple",
|
||||||
"tools/macos-packager",
|
"tools/macos-packager",
|
||||||
]
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|||||||
7
apple/Generated/ironstorage_watch.modulemap
Normal file
7
apple/Generated/ironstorage_watch.modulemap
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module ironstorage_watch {
|
||||||
|
header "ironstorage_watchFFI.h"
|
||||||
|
export *
|
||||||
|
use "Darwin"
|
||||||
|
use "_Builtin_stdbool"
|
||||||
|
use "_Builtin_stdint"
|
||||||
|
}
|
||||||
1209
apple/Generated/ironstorage_watch.swift
Normal file
1209
apple/Generated/ironstorage_watch.swift
Normal file
File diff suppressed because it is too large
Load Diff
578
apple/Generated/ironstorage_watchFFI.h
Normal file
578
apple/Generated/ironstorage_watchFFI.h
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
// This file was autogenerated by some hot garbage in the `uniffi` crate.
|
||||||
|
// Trust me, you don't want to mess with it!
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// The following structs are used to implement the lowest level
|
||||||
|
// of the FFI, and thus useful to multiple uniffied crates.
|
||||||
|
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
|
||||||
|
#ifdef UNIFFI_SHARED_H
|
||||||
|
// We also try to prevent mixing versions of shared uniffi header structs.
|
||||||
|
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
|
||||||
|
#ifndef UNIFFI_SHARED_HEADER_V4
|
||||||
|
#error Combining helper code from multiple versions of uniffi is not supported
|
||||||
|
#endif // ndef UNIFFI_SHARED_HEADER_V4
|
||||||
|
#else
|
||||||
|
#define UNIFFI_SHARED_H
|
||||||
|
#define UNIFFI_SHARED_HEADER_V4
|
||||||
|
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
|
||||||
|
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
|
||||||
|
|
||||||
|
typedef struct RustBuffer
|
||||||
|
{
|
||||||
|
uint64_t capacity;
|
||||||
|
uint64_t len;
|
||||||
|
uint8_t *_Nullable data;
|
||||||
|
} RustBuffer;
|
||||||
|
|
||||||
|
typedef struct ForeignBytes
|
||||||
|
{
|
||||||
|
int32_t len;
|
||||||
|
const uint8_t *_Nullable data;
|
||||||
|
} ForeignBytes;
|
||||||
|
|
||||||
|
// Error definitions
|
||||||
|
typedef struct RustCallStatus {
|
||||||
|
int8_t code;
|
||||||
|
RustBuffer errorBuf;
|
||||||
|
} RustCallStatus;
|
||||||
|
|
||||||
|
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
|
||||||
|
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
|
||||||
|
#endif // def UNIFFI_SHARED_H
|
||||||
|
#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
|
||||||
|
#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
|
||||||
|
typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
|
||||||
|
typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
|
||||||
|
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
|
||||||
|
typedef void (*UniffiCallbackInterfaceFree)(uint64_t
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
|
||||||
|
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
|
||||||
|
typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
|
||||||
|
typedef struct UniffiForeignFutureDroppedCallbackStruct {
|
||||||
|
uint64_t handle;
|
||||||
|
UniffiForeignFutureDroppedCallback _Nonnull free;
|
||||||
|
} UniffiForeignFutureDroppedCallbackStruct;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
|
||||||
|
typedef struct UniffiForeignFutureResultU8 {
|
||||||
|
uint8_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultU8;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
|
||||||
|
typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
|
||||||
|
typedef struct UniffiForeignFutureResultI8 {
|
||||||
|
int8_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultI8;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
|
||||||
|
typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
|
||||||
|
typedef struct UniffiForeignFutureResultU16 {
|
||||||
|
uint16_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultU16;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
|
||||||
|
typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
|
||||||
|
typedef struct UniffiForeignFutureResultI16 {
|
||||||
|
int16_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultI16;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
|
||||||
|
typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
|
||||||
|
typedef struct UniffiForeignFutureResultU32 {
|
||||||
|
uint32_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultU32;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
|
||||||
|
typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
|
||||||
|
typedef struct UniffiForeignFutureResultI32 {
|
||||||
|
int32_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultI32;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
|
||||||
|
typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
|
||||||
|
typedef struct UniffiForeignFutureResultU64 {
|
||||||
|
uint64_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultU64;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
|
||||||
|
typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
|
||||||
|
typedef struct UniffiForeignFutureResultI64 {
|
||||||
|
int64_t returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultI64;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
|
||||||
|
typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
|
||||||
|
typedef struct UniffiForeignFutureResultF32 {
|
||||||
|
float returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultF32;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
|
||||||
|
typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
|
||||||
|
typedef struct UniffiForeignFutureResultF64 {
|
||||||
|
double returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultF64;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
|
||||||
|
typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
|
||||||
|
typedef struct UniffiForeignFutureResultRustBuffer {
|
||||||
|
RustBuffer returnValue;
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultRustBuffer;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
|
||||||
|
typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
|
||||||
|
typedef struct UniffiForeignFutureResultVoid {
|
||||||
|
RustCallStatus callStatus;
|
||||||
|
} UniffiForeignFutureResultVoid;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
|
||||||
|
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_CLONE_WATCHCORE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_CLONE_WATCHCORE
|
||||||
|
uint64_t uniffi_ironstorage_watch_fn_clone_watchcore(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FREE_WATCHCORE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FREE_WATCHCORE
|
||||||
|
void uniffi_ironstorage_watch_fn_free_watchcore(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||||
|
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_apply_snapshot(uint64_t ptr, RustBuffer snapshot, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
|
||||||
|
void uniffi_ironstorage_watch_fn_method_watchcore_no_persisted_snapshot(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
|
||||||
|
void uniffi_ironstorage_watch_fn_method_watchcore_protected_data_unavailable(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_RECORDS_AT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_RECORDS_AT
|
||||||
|
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_records_at(uint64_t ptr, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FUNC_WATCH_CORE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_FUNC_WATCH_CORE
|
||||||
|
uint64_t uniffi_ironstorage_watch_fn_func_watch_core(RustCallStatus *_Nonnull out_status
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_ALLOC
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_ALLOC
|
||||||
|
RustBuffer ffi_ironstorage_watch_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_FROM_BYTES
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_FROM_BYTES
|
||||||
|
RustBuffer ffi_ironstorage_watch_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_FREE
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_FREE
|
||||||
|
void ffi_ironstorage_watch_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_RESERVE
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUSTBUFFER_RESERVE
|
||||||
|
RustBuffer ffi_ironstorage_watch_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U8
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U8
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_u8(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U8
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_u8(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U8
|
||||||
|
uint8_t ffi_ironstorage_watch_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I8
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I8
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_i8(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I8
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_i8(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I8
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I8
|
||||||
|
int8_t ffi_ironstorage_watch_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U16
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U16
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_u16(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U16
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_u16(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U16
|
||||||
|
uint16_t ffi_ironstorage_watch_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I16
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I16
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_i16(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I16
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_i16(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I16
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I16
|
||||||
|
int16_t ffi_ironstorage_watch_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U32
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U32
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_u32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U32
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_u32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U32
|
||||||
|
uint32_t ffi_ironstorage_watch_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I32
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I32
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_i32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I32
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_i32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I32
|
||||||
|
int32_t ffi_ironstorage_watch_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_U64
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_U64
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_u64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_U64
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_u64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_U64
|
||||||
|
uint64_t ffi_ironstorage_watch_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_I64
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_I64
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_i64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_I64
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_i64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_I64
|
||||||
|
int64_t ffi_ironstorage_watch_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_F32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_F32
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_F32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_F32
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_f32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_F32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_F32
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_f32(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_F32
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_F32
|
||||||
|
float ffi_ironstorage_watch_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_F64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_F64
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_F64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_F64
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_f64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_F64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_F64
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_f64(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_F64
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_F64
|
||||||
|
double ffi_ironstorage_watch_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_RUST_BUFFER
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_RUST_BUFFER
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_rust_buffer(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_RUST_BUFFER
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_rust_buffer(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_RUST_BUFFER
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_RUST_BUFFER
|
||||||
|
RustBuffer ffi_ironstorage_watch_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_POLL_VOID
|
||||||
|
void ffi_ironstorage_watch_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_CANCEL_VOID
|
||||||
|
void ffi_ironstorage_watch_rust_future_cancel_void(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_FREE_VOID
|
||||||
|
void ffi_ironstorage_watch_rust_future_free_void(uint64_t handle
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_VOID
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_RUST_FUTURE_COMPLETE_VOID
|
||||||
|
void ffi_ironstorage_watch_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_FUNC_WATCH_CORE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_FUNC_WATCH_CORE
|
||||||
|
uint16_t uniffi_ironstorage_watch_checksum_func_watch_core(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||||
|
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_apply_snapshot(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_NO_PERSISTED_SNAPSHOT
|
||||||
|
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_no_persisted_snapshot(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_PROTECTED_DATA_UNAVAILABLE
|
||||||
|
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_protected_data_unavailable(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_RECORDS_AT
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_RECORDS_AT
|
||||||
|
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_records_at(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_UNIFFI_CONTRACT_VERSION
|
||||||
|
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_WATCH_UNIFFI_CONTRACT_VERSION
|
||||||
|
uint32_t ffi_ironstorage_watch_uniffi_contract_version(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
|
||||||
1
apple/IronStorage-Watch-Bridging-Header.h
Normal file
1
apple/IronStorage-Watch-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated/ironstorage_watchFFI.h"
|
||||||
@@ -1,57 +1,177 @@
|
|||||||
|
import Security
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WatchConnectivity
|
import WatchConnectivity
|
||||||
|
|
||||||
|
private enum SecureSnapshotStore {
|
||||||
|
private static let service = "de.rfc1437.ironstorage.watch.snapshot"
|
||||||
|
private static let account = "selected-totp"
|
||||||
|
|
||||||
|
static func load() throws -> Data? {
|
||||||
|
var result: CFTypeRef?
|
||||||
|
let status = SecItemCopyMatching([
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrService: service,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
kSecReturnData: true,
|
||||||
|
kSecMatchLimit: kSecMatchLimitOne,
|
||||||
|
] as CFDictionary, &result)
|
||||||
|
if status == errSecItemNotFound { return nil }
|
||||||
|
guard status == errSecSuccess, let data = result as? Data else {
|
||||||
|
throw SnapshotStoreError(status: status)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
static func replace(_ snapshot: Data) throws {
|
||||||
|
let query = [
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrService: service,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
] as CFDictionary
|
||||||
|
let status = SecItemUpdate(query, [kSecValueData: snapshot] as CFDictionary)
|
||||||
|
if status == errSecItemNotFound {
|
||||||
|
let added = SecItemAdd([
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrService: service,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
kSecValueData: snapshot,
|
||||||
|
kSecAttrAccessible: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
|
||||||
|
] as CFDictionary, nil)
|
||||||
|
guard added == errSecSuccess else { throw SnapshotStoreError(status: added) }
|
||||||
|
} else if status != errSecSuccess {
|
||||||
|
throw SnapshotStoreError(status: status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func delete() throws {
|
||||||
|
let status = SecItemDelete([
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrService: service,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
] as CFDictionary)
|
||||||
|
guard status == errSecSuccess || status == errSecItemNotFound else {
|
||||||
|
throw SnapshotStoreError(status: status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SnapshotStoreError: Error {
|
||||||
|
let status: OSStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessionDelegate {
|
private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessionDelegate {
|
||||||
private static let snapshotKey = "de.rfc1437.ironstorage.watch.snapshot"
|
private static let snapshotKey = "de.rfc1437.ironstorage.watch.snapshot"
|
||||||
private static let deliveredReceiptKey = "de.rfc1437.ironstorage.watch.delivered"
|
private static let receiptKey = "de.rfc1437.ironstorage.watch.delivered"
|
||||||
|
|
||||||
|
@Published private(set) var records: [WatchTotpRecord] = []
|
||||||
|
private let core = watchCore()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
restoreSnapshot()
|
||||||
guard WCSession.isSupported() else { return }
|
guard WCSession.isSupported() else { return }
|
||||||
let session = WCSession.default
|
let session = WCSession.default
|
||||||
session.delegate = self
|
session.delegate = self
|
||||||
session.activate()
|
session.activate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func receive(_ applicationContext: [String: Any], session: WCSession) {
|
func sceneBecameActive() {
|
||||||
guard
|
restoreSnapshot()
|
||||||
applicationContext[Self.snapshotKey] is Data,
|
}
|
||||||
let receipt = applicationContext[Self.deliveredReceiptKey] as? Data
|
|
||||||
else { return }
|
func sceneBecameInactive() {
|
||||||
let acknowledgement = [Self.deliveredReceiptKey: receipt]
|
try? core.protectedDataUnavailable()
|
||||||
if session.isReachable {
|
records.removeAll(keepingCapacity: false)
|
||||||
session.sendMessage(acknowledgement, replyHandler: nil) { _ in
|
}
|
||||||
session.transferUserInfo(acknowledgement)
|
|
||||||
|
private func restoreSnapshot() {
|
||||||
|
do {
|
||||||
|
if var snapshot = try SecureSnapshotStore.load() {
|
||||||
|
defer { snapshot.resetBytes(in: 0..<snapshot.count) }
|
||||||
|
let update = try core.applySnapshot(snapshot: snapshot)
|
||||||
|
if update.persistence == .delete { try SecureSnapshotStore.delete() }
|
||||||
|
} else {
|
||||||
|
try core.noPersistedSnapshot()
|
||||||
}
|
}
|
||||||
} else {
|
refreshRecords()
|
||||||
session.transferUserInfo(acknowledgement)
|
} catch {
|
||||||
|
try? core.protectedDataUnavailable()
|
||||||
|
records.removeAll(keepingCapacity: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func session(
|
private func receive(_ applicationContext: [String: Any], session: WCSession) {
|
||||||
|
guard var snapshot = applicationContext[Self.snapshotKey] as? Data else { return }
|
||||||
|
defer { snapshot.resetBytes(in: 0..<snapshot.count) }
|
||||||
|
do {
|
||||||
|
let update = try core.applySnapshot(snapshot: snapshot)
|
||||||
|
switch update.persistence {
|
||||||
|
case .keep:
|
||||||
|
break
|
||||||
|
case .replace:
|
||||||
|
try SecureSnapshotStore.replace(snapshot)
|
||||||
|
case .delete:
|
||||||
|
try SecureSnapshotStore.delete()
|
||||||
|
}
|
||||||
|
refreshRecords()
|
||||||
|
guard !update.receipt.isEmpty else { return }
|
||||||
|
let acknowledgement = [Self.receiptKey: update.receipt]
|
||||||
|
if session.isReachable {
|
||||||
|
session.sendMessage(acknowledgement, replyHandler: nil) { _ in
|
||||||
|
session.transferUserInfo(acknowledgement)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.transferUserInfo(acknowledgement)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
try? core.noPersistedSnapshot()
|
||||||
|
records.removeAll(keepingCapacity: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func refreshRecords() {
|
||||||
|
records = (try? core.recordsAt(
|
||||||
|
unixSeconds: UInt64(Date().timeIntervalSince1970)
|
||||||
|
)) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated func session(
|
||||||
_ session: WCSession,
|
_ session: WCSession,
|
||||||
activationDidCompleteWith activationState: WCSessionActivationState,
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
error: Error?
|
error: Error?
|
||||||
) {
|
) {
|
||||||
guard activationState == .activated, error == nil else { return }
|
guard activationState == .activated, error == nil else { return }
|
||||||
receive(session.receivedApplicationContext, session: session)
|
Task { @MainActor [weak self] in
|
||||||
|
self?.receive(session.receivedApplicationContext, session: session)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func session(
|
nonisolated func session(
|
||||||
_ session: WCSession,
|
_ session: WCSession,
|
||||||
didReceiveApplicationContext applicationContext: [String: Any]
|
didReceiveApplicationContext applicationContext: [String: Any]
|
||||||
) {
|
) {
|
||||||
receive(applicationContext, session: session)
|
Task { @MainActor [weak self] in
|
||||||
|
self?.receive(applicationContext, session: session)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct IronStorageWatchApp: App {
|
struct IronStorageWatchApp: App {
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@StateObject private var transport = WatchSnapshotTransport()
|
@StateObject private var transport = WatchSnapshotTransport()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentUnavailableView("No TOTP Codes", systemImage: "timer")
|
ContentUnavailableView("No TOTP Codes", systemImage: "timer")
|
||||||
|
.onChange(of: scenePhase) { _, phase in
|
||||||
|
if phase == .active {
|
||||||
|
transport.sceneBecameActive()
|
||||||
|
} else {
|
||||||
|
transport.sceneBecameInactive()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,28 +11,42 @@ else
|
|||||||
profile=release
|
profile=release
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${PLATFORM_NAME:-}" == watch* ]]; then
|
||||||
|
package=ironstorage-watch-apple
|
||||||
|
library=ironstorage_watch
|
||||||
|
else
|
||||||
|
package=ironstorage-apple
|
||||||
|
library=ironstorage_apple
|
||||||
|
fi
|
||||||
|
|
||||||
libraries=()
|
libraries=()
|
||||||
for arch in $ARCHS; do
|
for arch in $ARCHS; do
|
||||||
if [[ "$arch" == "arm64" && "${LLVM_TARGET_TRIPLE_SUFFIX:-}" == "-simulator" ]]; then
|
if [[ "$package" == "ironstorage-watch-apple" && "$arch" == "arm64" && "${LLVM_TARGET_TRIPLE_SUFFIX:-}" == "-simulator" ]]; then
|
||||||
|
target=aarch64-apple-watchos-sim
|
||||||
|
elif [[ "$package" == "ironstorage-watch-apple" && "$arch" == "arm64" ]]; then
|
||||||
|
target=aarch64-apple-watchos
|
||||||
|
elif [[ "$package" == "ironstorage-watch-apple" && "$arch" == "x86_64" ]]; then
|
||||||
|
target=x86_64-apple-watchos-sim
|
||||||
|
elif [[ "$arch" == "arm64" && "${LLVM_TARGET_TRIPLE_SUFFIX:-}" == "-simulator" ]]; then
|
||||||
target=aarch64-apple-ios-sim
|
target=aarch64-apple-ios-sim
|
||||||
elif [[ "$arch" == "arm64" ]]; then
|
elif [[ "$arch" == "arm64" ]]; then
|
||||||
target=aarch64-apple-ios
|
target=aarch64-apple-ios
|
||||||
elif [[ "$arch" == "x86_64" ]]; then
|
elif [[ "$arch" == "x86_64" ]]; then
|
||||||
target=x86_64-apple-ios
|
target=x86_64-apple-ios
|
||||||
else
|
else
|
||||||
echo "Unsupported iOS architecture: $arch" >&2
|
echo "Unsupported ${PLATFORM_NAME:-Apple} architecture: $arch" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$profile" == "debug" ]]; then
|
if [[ "$profile" == "debug" ]]; then
|
||||||
rustup run stable cargo build --locked \
|
rustup run stable cargo build --locked \
|
||||||
--target "$target" --package ironstorage-apple --lib
|
--target "$target" --package "$package" --lib
|
||||||
else
|
else
|
||||||
rustup run stable cargo build --locked --release \
|
rustup run stable cargo build --locked --release \
|
||||||
--target "$target" --package ironstorage-apple --lib
|
--target "$target" --package "$package" --lib
|
||||||
fi
|
fi
|
||||||
libraries+=("$CARGO_TARGET_DIR/$target/$profile/libironstorage_apple.a")
|
libraries+=("$CARGO_TARGET_DIR/$target/$profile/lib$library.a")
|
||||||
done
|
done
|
||||||
|
|
||||||
mkdir -p "$DERIVED_FILE_DIR/rust"
|
mkdir -p "$DERIVED_FILE_DIR/rust"
|
||||||
lipo -create -output "$DERIVED_FILE_DIR/rust/libironstorage_apple.a" "${libraries[@]}"
|
lipo -create -output "$DERIVED_FILE_DIR/rust/lib$library.a" "${libraries[@]}"
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ targets:
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.ironstorage.watch
|
PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.ironstorage.watch
|
||||||
PRODUCT_NAME: IronStorage Watch
|
PRODUCT_NAME: IronStorage Watch
|
||||||
SKIP_INSTALL: YES
|
SKIP_INSTALL: YES
|
||||||
|
"EXCLUDED_ARCHS[sdk=watchsimulator*]": x86_64
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER: IronStorage-Watch-Bridging-Header.h
|
||||||
|
LIBRARY_SEARCH_PATHS: "$(inherited) $(DERIVED_FILE_DIR)/rust"
|
||||||
|
OTHER_LDFLAGS: "$(inherited) -lironstorage_watch"
|
||||||
info:
|
info:
|
||||||
path: Watch-Info.plist
|
path: Watch-Info.plist
|
||||||
properties:
|
properties:
|
||||||
@@ -86,3 +90,11 @@ targets:
|
|||||||
sources:
|
sources:
|
||||||
- Assets.xcassets
|
- Assets.xcassets
|
||||||
- Sources/Watch
|
- Sources/Watch
|
||||||
|
- Generated/ironstorage_watch.swift
|
||||||
|
preBuildScripts:
|
||||||
|
- name: Build Rust Watch core
|
||||||
|
basedOnDependencyAnalysis: false
|
||||||
|
script: |
|
||||||
|
./build_rust_core.bash
|
||||||
|
outputFiles:
|
||||||
|
- $(DERIVED_FILE_DIR)/rust/libironstorage_watch.a
|
||||||
|
|||||||
@@ -6,47 +6,79 @@ edition.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["full"]
|
||||||
|
full = [
|
||||||
|
"dep:apple-native-keyring-store",
|
||||||
|
"dep:arboard",
|
||||||
|
"dep:cap-std",
|
||||||
|
"dep:cap-tempfile",
|
||||||
|
"dep:clap",
|
||||||
|
"dep:clap_complete",
|
||||||
|
"dep:flate2",
|
||||||
|
"dep:gix",
|
||||||
|
"dep:gix-config",
|
||||||
|
"dep:image",
|
||||||
|
"dep:keepass",
|
||||||
|
"dep:keyring-core",
|
||||||
|
"dep:pgp",
|
||||||
|
"dep:qrcode",
|
||||||
|
"dep:rand",
|
||||||
|
"dep:regex",
|
||||||
|
"dep:reqwest",
|
||||||
|
"dep:rqrr",
|
||||||
|
"dep:secret-service",
|
||||||
|
"dep:security-framework",
|
||||||
|
"dep:serde",
|
||||||
|
"dep:shlex",
|
||||||
|
"dep:toml",
|
||||||
|
"dep:url",
|
||||||
|
"dep:windows-native-keyring-store",
|
||||||
|
"dep:zbus-secret-service-keyring-store",
|
||||||
|
]
|
||||||
|
watch = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cap-std.workspace = true
|
cap-std = { workspace = true, optional = true }
|
||||||
cap-tempfile.workspace = true
|
cap-tempfile = { workspace = true, optional = true }
|
||||||
clap.workspace = true
|
clap = { workspace = true, optional = true }
|
||||||
clap_complete.workspace = true
|
clap_complete = { workspace = true, optional = true }
|
||||||
data-encoding.workspace = true
|
data-encoding.workspace = true
|
||||||
flate2.workspace = true
|
flate2 = { workspace = true, optional = true }
|
||||||
gix.workspace = true
|
gix = { workspace = true, optional = true }
|
||||||
gix-config.workspace = true
|
gix-config = { workspace = true, optional = true }
|
||||||
hmac.workspace = true
|
hmac.workspace = true
|
||||||
image.workspace = true
|
image = { workspace = true, optional = true }
|
||||||
keyring-core.workspace = true
|
keyring-core = { workspace = true, optional = true }
|
||||||
keepass.workspace = true
|
keepass = { workspace = true, optional = true }
|
||||||
pgp.workspace = true
|
pgp = { workspace = true, optional = true }
|
||||||
qrcode.workspace = true
|
qrcode = { workspace = true, optional = true }
|
||||||
rand.workspace = true
|
rand = { workspace = true, optional = true }
|
||||||
regex.workspace = true
|
regex = { workspace = true, optional = true }
|
||||||
reqwest.workspace = true
|
reqwest = { workspace = true, optional = true }
|
||||||
rqrr.workspace = true
|
rqrr = { workspace = true, optional = true }
|
||||||
serde.workspace = true
|
serde = { workspace = true, optional = true }
|
||||||
sha1.workspace = true
|
sha1.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
shlex.workspace = true
|
shlex = { workspace = true, optional = true }
|
||||||
toml.workspace = true
|
toml = { workspace = true, optional = true }
|
||||||
url.workspace = true
|
url = { workspace = true, optional = true }
|
||||||
zeroize.workspace = true
|
zeroize.workspace = true
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||||
apple-native-keyring-store.workspace = true
|
apple-native-keyring-store = { workspace = true, optional = true }
|
||||||
security-framework.workspace = true
|
security-framework = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-native-keyring-store.workspace = true
|
windows-native-keyring-store = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
arboard.workspace = true
|
arboard = { workspace = true, optional = true }
|
||||||
secret-service.workspace = true
|
secret-service = { workspace = true, optional = true }
|
||||||
zbus-secret-service-keyring-store.workspace = true
|
zbus-secret-service-keyring-store = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||||
arboard.workspace = true
|
arboard = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|||||||
@@ -5,32 +5,60 @@
|
|||||||
//!
|
//!
|
||||||
//! This crate is the sole owner of stored and derived password-store objects.
|
//! This crate is the sole owner of stored and derived password-store objects.
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod command;
|
pub mod command;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod desktop;
|
pub mod desktop;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod document;
|
pub mod document;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod generate;
|
pub mod generate;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod kdbx;
|
pub mod kdbx;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile;
|
pub mod mobile;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_authentication;
|
pub mod mobile_authentication;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_entry;
|
pub mod mobile_entry;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_home;
|
pub mod mobile_home;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_key_transfer;
|
pub mod mobile_key_transfer;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_mutation;
|
pub mod mobile_mutation;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_onboarding;
|
pub mod mobile_onboarding;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_passwords;
|
pub mod mobile_passwords;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mobile_totp;
|
pub mod mobile_totp;
|
||||||
pub mod mobile_watch;
|
pub mod mobile_watch;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod mutation;
|
pub mod mutation;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod otp;
|
pub mod otp;
|
||||||
|
mod otp_core;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod presentation;
|
pub mod presentation;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod read;
|
pub mod read;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod recipient;
|
pub mod recipient;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod repository;
|
pub mod repository;
|
||||||
|
mod secret;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod secret_store;
|
pub mod secret_store;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod write;
|
pub mod write;
|
||||||
|
|
||||||
/// Product name shared by the presentation adapters.
|
/// Product name shared by the presentation adapters.
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ impl<'a> MobileTotpService<'a> {
|
|||||||
let period = uri.period().ok_or(OtpError::NotTotp)?;
|
let period = uri.period().ok_or(OtpError::NotTotp)?;
|
||||||
retained.insert(path.clone());
|
retained.insert(path.clone());
|
||||||
entries.push(WatchSnapshotEntry::new(
|
entries.push(WatchSnapshotEntry::new(
|
||||||
path.clone(),
|
path.to_string(),
|
||||||
uri.issuer().map(str::to_owned),
|
uri.issuer().map(str::to_owned),
|
||||||
uri.account().to_owned(),
|
uri.account().to_owned(),
|
||||||
uri.algorithm(),
|
uri.algorithm(),
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
//! Versioned, replacement-only Apple Watch TOTP snapshots and sender state.
|
//! Versioned Apple Watch TOTP snapshots and the minimal offline Watch runtime.
|
||||||
|
|
||||||
use std::{
|
use std::{error::Error, fmt, path::Path};
|
||||||
error::Error,
|
|
||||||
fmt, fs,
|
|
||||||
io::Write as _,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
use cap_std::{ambient_authority, fs::Dir};
|
use cap_std::{ambient_authority, fs::Dir};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
use cap_tempfile::TempFile;
|
use cap_tempfile::TempFile;
|
||||||
use data_encoding::{HEXLOWER, HEXLOWER_PERMISSIVE};
|
use data_encoding::HEXLOWER;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use data_encoding::HEXLOWER_PERMISSIVE;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use std::{fs, io::Write as _, path::PathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
otp::OtpAlgorithm,
|
otp_core::{self, OtpAlgorithm},
|
||||||
repository::{EntryPath, SecretBytes},
|
secret::SecretBytes,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SNAPSHOT_MAGIC: &[u8; 4] = b"ISWS";
|
const SNAPSHOT_MAGIC: &[u8; 4] = b"ISWS";
|
||||||
const RECEIPT_MAGIC: &[u8; 4] = b"ISWR";
|
const RECEIPT_MAGIC: &[u8; 4] = b"ISWR";
|
||||||
const SNAPSHOT_VERSION: u16 = 1;
|
const SNAPSHOT_VERSION: u16 = 1;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
const JOURNAL_VERSION: u32 = 1;
|
const JOURNAL_VERSION: u32 = 1;
|
||||||
const MAX_SNAPSHOT_BYTES: usize = 256 * 1024;
|
const MAX_SNAPSHOT_BYTES: usize = 256 * 1024;
|
||||||
const MAX_ENTRIES: usize = 256;
|
const MAX_ENTRIES: usize = 256;
|
||||||
@@ -57,7 +61,7 @@ impl MobileWatchSnapshotStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct WatchSnapshotEntry {
|
pub struct WatchSnapshotEntry {
|
||||||
path: EntryPath,
|
path: String,
|
||||||
issuer: Option<String>,
|
issuer: Option<String>,
|
||||||
account: String,
|
account: String,
|
||||||
algorithm: OtpAlgorithm,
|
algorithm: OtpAlgorithm,
|
||||||
@@ -68,7 +72,7 @@ pub struct WatchSnapshotEntry {
|
|||||||
|
|
||||||
impl WatchSnapshotEntry {
|
impl WatchSnapshotEntry {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
path: EntryPath,
|
path: String,
|
||||||
issuer: Option<String>,
|
issuer: Option<String>,
|
||||||
account: String,
|
account: String,
|
||||||
algorithm: OtpAlgorithm,
|
algorithm: OtpAlgorithm,
|
||||||
@@ -87,7 +91,7 @@ impl WatchSnapshotEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &EntryPath {
|
pub fn path(&self) -> &str {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
pub fn issuer(&self) -> Option<&str> {
|
pub fn issuer(&self) -> Option<&str> {
|
||||||
@@ -156,6 +160,7 @@ impl fmt::Debug for WatchSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub struct WatchSnapshotTransfer {
|
pub struct WatchSnapshotTransfer {
|
||||||
revision: u64,
|
revision: u64,
|
||||||
selected_entries: u32,
|
selected_entries: u32,
|
||||||
@@ -163,6 +168,7 @@ pub struct WatchSnapshotTransfer {
|
|||||||
delivered_receipt: Vec<u8>,
|
delivered_receipt: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
impl WatchSnapshotTransfer {
|
impl WatchSnapshotTransfer {
|
||||||
pub fn revision(&self) -> u64 {
|
pub fn revision(&self) -> u64 {
|
||||||
self.revision
|
self.revision
|
||||||
@@ -178,6 +184,7 @@ impl WatchSnapshotTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
impl fmt::Debug for WatchSnapshotTransfer {
|
impl fmt::Debug for WatchSnapshotTransfer {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
formatter
|
formatter
|
||||||
@@ -273,14 +280,184 @@ impl WatchSnapshotReceiver {
|
|||||||
self.current = None;
|
self.current = None;
|
||||||
self.accepted = None;
|
self.accepted = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_secrets(&mut self) {
|
||||||
|
self.current = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum WatchPersistenceAction {
|
||||||
|
Keep,
|
||||||
|
Replace,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct WatchSnapshotUpdate {
|
||||||
|
apply: WatchSnapshotApply,
|
||||||
|
persistence: WatchPersistenceAction,
|
||||||
|
revision: Option<u64>,
|
||||||
|
selected_entries: u32,
|
||||||
|
receipt: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatchSnapshotUpdate {
|
||||||
|
pub fn apply(&self) -> WatchSnapshotApply {
|
||||||
|
self.apply
|
||||||
|
}
|
||||||
|
pub fn persistence(&self) -> WatchPersistenceAction {
|
||||||
|
self.persistence
|
||||||
|
}
|
||||||
|
pub fn revision(&self) -> Option<u64> {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
pub fn selected_entries(&self) -> u32 {
|
||||||
|
self.selected_entries
|
||||||
|
}
|
||||||
|
pub fn receipt(&self) -> &[u8] {
|
||||||
|
&self.receipt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WatchTotpRecord {
|
||||||
|
path: String,
|
||||||
|
issuer: Option<String>,
|
||||||
|
account: String,
|
||||||
|
code: SecretBytes,
|
||||||
|
period: u64,
|
||||||
|
valid_until: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatchTotpRecord {
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
pub fn issuer(&self) -> Option<&str> {
|
||||||
|
self.issuer.as_deref()
|
||||||
|
}
|
||||||
|
pub fn account(&self) -> &str {
|
||||||
|
&self.account
|
||||||
|
}
|
||||||
|
pub fn code(&self) -> &SecretBytes {
|
||||||
|
&self.code
|
||||||
|
}
|
||||||
|
pub fn period(&self) -> u64 {
|
||||||
|
self.period
|
||||||
|
}
|
||||||
|
pub fn valid_until(&self) -> u64 {
|
||||||
|
self.valid_until
|
||||||
|
}
|
||||||
|
pub fn remaining_at(&self, unix_seconds: u64) -> u64 {
|
||||||
|
self.valid_until.saturating_sub(unix_seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for WatchTotpRecord {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter
|
||||||
|
.debug_struct("WatchTotpRecord")
|
||||||
|
.field("path", &self.path)
|
||||||
|
.field("issuer", &self.issuer)
|
||||||
|
.field("account", &self.account)
|
||||||
|
.field("code", &"[REDACTED]")
|
||||||
|
.field("period", &self.period)
|
||||||
|
.field("valid_until", &self.valid_until)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct WatchRuntime {
|
||||||
|
receiver: WatchSnapshotReceiver,
|
||||||
|
protected_data_available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatchRuntime {
|
||||||
|
pub fn apply_snapshot(
|
||||||
|
&mut self,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
) -> Result<WatchSnapshotUpdate, WatchSnapshotError> {
|
||||||
|
let apply = self.receiver.apply(SecretBytes::new(bytes))?;
|
||||||
|
self.protected_data_available = true;
|
||||||
|
let persistence = match apply {
|
||||||
|
WatchSnapshotApply::Replaced | WatchSnapshotApply::PairingChanged => {
|
||||||
|
WatchPersistenceAction::Replace
|
||||||
|
}
|
||||||
|
WatchSnapshotApply::Revoked => WatchPersistenceAction::Delete,
|
||||||
|
WatchSnapshotApply::Duplicate | WatchSnapshotApply::Stale => {
|
||||||
|
WatchPersistenceAction::Keep
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let current = self.receiver.current();
|
||||||
|
Ok(WatchSnapshotUpdate {
|
||||||
|
apply,
|
||||||
|
persistence,
|
||||||
|
revision: current.map(WatchSnapshot::revision),
|
||||||
|
selected_entries: current
|
||||||
|
.map(|snapshot| u32::try_from(snapshot.entries().len()).unwrap_or(u32::MAX))
|
||||||
|
.unwrap_or(0),
|
||||||
|
receipt: self.receiver.current_receipt().unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn records_at(
|
||||||
|
&self,
|
||||||
|
unix_seconds: u64,
|
||||||
|
) -> Result<Vec<WatchTotpRecord>, WatchSnapshotError> {
|
||||||
|
if !self.protected_data_available {
|
||||||
|
return Err(WatchSnapshotError::ProtectedDataUnavailable);
|
||||||
|
}
|
||||||
|
let Some(snapshot) = self.receiver.current() else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
snapshot
|
||||||
|
.entries()
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
|
let counter = unix_seconds / entry.period;
|
||||||
|
let valid_until = counter
|
||||||
|
.checked_add(1)
|
||||||
|
.and_then(|counter| counter.checked_mul(entry.period))
|
||||||
|
.ok_or(WatchSnapshotError::InvalidEntry)?;
|
||||||
|
let code = otp_core::code_for_counter(
|
||||||
|
entry.algorithm,
|
||||||
|
entry.secret.expose(),
|
||||||
|
entry.digits,
|
||||||
|
counter,
|
||||||
|
)
|
||||||
|
.map_err(|_| WatchSnapshotError::InvalidEntry)?;
|
||||||
|
Ok(WatchTotpRecord {
|
||||||
|
path: entry.path.clone(),
|
||||||
|
issuer: entry.issuer.clone(),
|
||||||
|
account: entry.account.clone(),
|
||||||
|
code,
|
||||||
|
period: entry.period,
|
||||||
|
valid_until,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn protected_data_unavailable(&mut self) {
|
||||||
|
self.receiver.clear_secrets();
|
||||||
|
self.protected_data_available = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_persisted_snapshot(&mut self) {
|
||||||
|
self.receiver.revoke();
|
||||||
|
self.protected_data_available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub struct WatchSnapshotSender {
|
pub struct WatchSnapshotSender {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
journal: SenderJournal,
|
journal: SenderJournal,
|
||||||
status: MobileWatchSnapshotStatus,
|
status: MobileWatchSnapshotStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
impl WatchSnapshotSender {
|
impl WatchSnapshotSender {
|
||||||
pub fn load(path: PathBuf) -> Self {
|
pub fn load(path: PathBuf) -> Self {
|
||||||
let journal = load_journal(&path).unwrap_or_default();
|
let journal = load_journal(&path).unwrap_or_default();
|
||||||
@@ -436,6 +613,7 @@ impl WatchSnapshotSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
struct SenderJournal {
|
struct SenderJournal {
|
||||||
@@ -455,6 +633,7 @@ struct SenderJournal {
|
|||||||
current_revision: Option<u64>,
|
current_revision: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
impl Default for SenderJournal {
|
impl Default for SenderJournal {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -469,6 +648,7 @@ impl Default for SenderJournal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn journal_version() -> u32 {
|
fn journal_version() -> u32 {
|
||||||
JOURNAL_VERSION
|
JOURNAL_VERSION
|
||||||
}
|
}
|
||||||
@@ -476,10 +656,12 @@ fn journal_version() -> u32 {
|
|||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
enum ReceiptKind {
|
enum ReceiptKind {
|
||||||
|
#[cfg(feature = "full")]
|
||||||
Delivered = 1,
|
Delivered = 1,
|
||||||
Current = 2,
|
Current = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
struct Receipt {
|
struct Receipt {
|
||||||
kind: ReceiptKind,
|
kind: ReceiptKind,
|
||||||
pairing: [u8; 32],
|
pairing: [u8; 32],
|
||||||
@@ -487,6 +669,7 @@ struct Receipt {
|
|||||||
_digest: [u8; 32],
|
_digest: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn encode_entries(entries: &[WatchSnapshotEntry]) -> Result<Vec<u8>, WatchSnapshotError> {
|
fn encode_entries(entries: &[WatchSnapshotEntry]) -> Result<Vec<u8>, WatchSnapshotError> {
|
||||||
if entries.len() > MAX_ENTRIES {
|
if entries.len() > MAX_ENTRIES {
|
||||||
return Err(WatchSnapshotError::TooManyEntries);
|
return Err(WatchSnapshotError::TooManyEntries);
|
||||||
@@ -518,6 +701,7 @@ fn encode_entries(entries: &[WatchSnapshotEntry]) -> Result<Vec<u8>, WatchSnapsh
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn encode_snapshot(
|
fn encode_snapshot(
|
||||||
pairing: [u8; 32],
|
pairing: [u8; 32],
|
||||||
revision: u64,
|
revision: u64,
|
||||||
@@ -563,8 +747,10 @@ fn decode_snapshot(bytes: &[u8]) -> Result<WatchSnapshot, WatchSnapshotError> {
|
|||||||
}
|
}
|
||||||
let mut entries = Vec::with_capacity(count);
|
let mut entries = Vec::with_capacity(count);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
let path = EntryPath::parse(&take_text(&mut input)?)
|
let path = take_text(&mut input)?;
|
||||||
.map_err(|_| WatchSnapshotError::InvalidEntry)?;
|
if !valid_entry_path(&path) {
|
||||||
|
return Err(WatchSnapshotError::InvalidEntry);
|
||||||
|
}
|
||||||
let issuer = match take_u8(&mut input)? {
|
let issuer = match take_u8(&mut input)? {
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => Some(take_text(&mut input)?),
|
1 => Some(take_text(&mut input)?),
|
||||||
@@ -631,6 +817,7 @@ fn encode_receipt(
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn decode_receipt(bytes: &[u8]) -> Result<Receipt, WatchSnapshotError> {
|
fn decode_receipt(bytes: &[u8]) -> Result<Receipt, WatchSnapshotError> {
|
||||||
if bytes.len() != 4 + 2 + 1 + 32 + 8 + 32 + 32 {
|
if bytes.len() != 4 + 2 + 1 + 32 + 8 + 32 + 32 {
|
||||||
return Err(WatchSnapshotError::InvalidReceipt);
|
return Err(WatchSnapshotError::InvalidReceipt);
|
||||||
@@ -663,6 +850,7 @@ fn decode_receipt(bytes: &[u8]) -> Result<Receipt, WatchSnapshotError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn status_from_journal(journal: &SenderJournal) -> MobileWatchSnapshotStatus {
|
fn status_from_journal(journal: &SenderJournal) -> MobileWatchSnapshotStatus {
|
||||||
let (state, revision, detail) = if let Some(revision) = journal.current_revision {
|
let (state, revision, detail) = if let Some(revision) = journal.current_revision {
|
||||||
(
|
(
|
||||||
@@ -699,6 +887,7 @@ fn status_from_journal(journal: &SenderJournal) -> MobileWatchSnapshotStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn snapshot_detail(state: &str, count: u32, revision: u64) -> String {
|
fn snapshot_detail(state: &str, count: u32, revision: u64) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Snapshot revision {revision} with {count} selected TOTP {} is {state}.",
|
"Snapshot revision {revision} with {count} selected TOTP {} is {state}.",
|
||||||
@@ -706,6 +895,7 @@ fn snapshot_detail(state: &str, count: u32, revision: u64) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn load_journal(path: &Path) -> Option<SenderJournal> {
|
fn load_journal(path: &Path) -> Option<SenderJournal> {
|
||||||
let metadata = fs::symlink_metadata(path).ok()?;
|
let metadata = fs::symlink_metadata(path).ok()?;
|
||||||
if metadata.file_type().is_symlink() || !metadata.is_file() {
|
if metadata.file_type().is_symlink() || !metadata.is_file() {
|
||||||
@@ -715,6 +905,7 @@ fn load_journal(path: &Path) -> Option<SenderJournal> {
|
|||||||
(journal.version == JOURNAL_VERSION).then_some(journal)
|
(journal.version == JOURNAL_VERSION).then_some(journal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn save_journal(path: &Path, journal: &SenderJournal) -> Result<(), WatchSnapshotError> {
|
fn save_journal(path: &Path, journal: &SenderJournal) -> Result<(), WatchSnapshotError> {
|
||||||
let parent = path.parent().ok_or(WatchSnapshotError::JournalWrite)?;
|
let parent = path.parent().ok_or(WatchSnapshotError::JournalWrite)?;
|
||||||
fs::create_dir_all(parent).map_err(|_| WatchSnapshotError::JournalWrite)?;
|
fs::create_dir_all(parent).map_err(|_| WatchSnapshotError::JournalWrite)?;
|
||||||
@@ -737,7 +928,7 @@ fn save_journal(path: &Path, journal: &SenderJournal) -> Result<(), WatchSnapsho
|
|||||||
.map_err(|_| WatchSnapshotError::JournalWrite)
|
.map_err(|_| WatchSnapshotError::JournalWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(all(feature = "full", unix))]
|
||||||
fn set_private_permissions(temporary: &TempFile<'_>) -> Result<(), WatchSnapshotError> {
|
fn set_private_permissions(temporary: &TempFile<'_>) -> Result<(), WatchSnapshotError> {
|
||||||
use cap_std::fs::{Permissions, PermissionsExt as _};
|
use cap_std::fs::{Permissions, PermissionsExt as _};
|
||||||
temporary
|
temporary
|
||||||
@@ -746,7 +937,7 @@ fn set_private_permissions(temporary: &TempFile<'_>) -> Result<(), WatchSnapshot
|
|||||||
.map_err(|_| WatchSnapshotError::JournalWrite)
|
.map_err(|_| WatchSnapshotError::JournalWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(all(feature = "full", not(unix)))]
|
||||||
fn set_private_permissions(_temporary: &TempFile<'_>) -> Result<(), WatchSnapshotError> {
|
fn set_private_permissions(_temporary: &TempFile<'_>) -> Result<(), WatchSnapshotError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -754,6 +945,7 @@ fn set_private_permissions(_temporary: &TempFile<'_>) -> Result<(), WatchSnapsho
|
|||||||
fn digest(bytes: &[u8]) -> [u8; 32] {
|
fn digest(bytes: &[u8]) -> [u8; 32] {
|
||||||
Sha256::digest(bytes).into()
|
Sha256::digest(bytes).into()
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn decode_hash(text: &str) -> Result<[u8; 32], WatchSnapshotError> {
|
fn decode_hash(text: &str) -> Result<[u8; 32], WatchSnapshotError> {
|
||||||
let bytes = HEXLOWER_PERMISSIVE
|
let bytes = HEXLOWER_PERMISSIVE
|
||||||
.decode(text.as_bytes())
|
.decode(text.as_bytes())
|
||||||
@@ -762,15 +954,18 @@ fn decode_hash(text: &str) -> Result<[u8; 32], WatchSnapshotError> {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| WatchSnapshotError::InvalidJournal)
|
.map_err(|_| WatchSnapshotError::InvalidJournal)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn put_u32(output: &mut Vec<u8>, value: u32) {
|
fn put_u32(output: &mut Vec<u8>, value: u32) {
|
||||||
output.extend_from_slice(&value.to_be_bytes());
|
output.extend_from_slice(&value.to_be_bytes());
|
||||||
}
|
}
|
||||||
fn put_u64(output: &mut Vec<u8>, value: u64) {
|
fn put_u64(output: &mut Vec<u8>, value: u64) {
|
||||||
output.extend_from_slice(&value.to_be_bytes());
|
output.extend_from_slice(&value.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn put_text(output: &mut Vec<u8>, value: &str) -> Result<(), WatchSnapshotError> {
|
fn put_text(output: &mut Vec<u8>, value: &str) -> Result<(), WatchSnapshotError> {
|
||||||
put_bytes(output, value.as_bytes(), MAX_TEXT_BYTES)
|
put_bytes(output, value.as_bytes(), MAX_TEXT_BYTES)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "full")]
|
||||||
fn put_bytes(output: &mut Vec<u8>, value: &[u8], max: usize) -> Result<(), WatchSnapshotError> {
|
fn put_bytes(output: &mut Vec<u8>, value: &[u8], max: usize) -> Result<(), WatchSnapshotError> {
|
||||||
if value.len() > max {
|
if value.len() > max {
|
||||||
return Err(WatchSnapshotError::InvalidEntry);
|
return Err(WatchSnapshotError::InvalidEntry);
|
||||||
@@ -826,6 +1021,16 @@ fn take_text(input: &mut &[u8]) -> Result<String, WatchSnapshotError> {
|
|||||||
.map_err(|_| WatchSnapshotError::InvalidEntry)
|
.map_err(|_| WatchSnapshotError::InvalidEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn valid_entry_path(value: &str) -> bool {
|
||||||
|
let path = Path::new(value);
|
||||||
|
!value.is_empty()
|
||||||
|
&& !value.ends_with('/')
|
||||||
|
&& !path.is_absolute()
|
||||||
|
&& path
|
||||||
|
.components()
|
||||||
|
.all(|component| matches!(component, std::path::Component::Normal(_)))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum WatchSnapshotError {
|
pub enum WatchSnapshotError {
|
||||||
InvalidPairing,
|
InvalidPairing,
|
||||||
@@ -841,6 +1046,7 @@ pub enum WatchSnapshotError {
|
|||||||
NoPendingSnapshot,
|
NoPendingSnapshot,
|
||||||
InvalidJournal,
|
InvalidJournal,
|
||||||
JournalWrite,
|
JournalWrite,
|
||||||
|
ProtectedDataUnavailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for WatchSnapshotError {
|
impl fmt::Display for WatchSnapshotError {
|
||||||
@@ -861,6 +1067,7 @@ impl fmt::Display for WatchSnapshotError {
|
|||||||
Self::NoPendingSnapshot => "there is no pending Apple Watch snapshot",
|
Self::NoPendingSnapshot => "there is no pending Apple Watch snapshot",
|
||||||
Self::InvalidJournal => "the Apple Watch synchronization journal is invalid",
|
Self::InvalidJournal => "the Apple Watch synchronization journal is invalid",
|
||||||
Self::JournalWrite => "the Apple Watch synchronization journal could not be saved",
|
Self::JournalWrite => "the Apple Watch synchronization journal could not be saved",
|
||||||
|
Self::ProtectedDataUnavailable => "protected Apple Watch data is unavailable",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
use std::{error::Error, fmt, ops::Range, str, sync::Mutex};
|
use std::{error::Error, fmt, ops::Range, str, sync::Mutex};
|
||||||
|
|
||||||
use data_encoding::BASE32_NOPAD;
|
use data_encoding::BASE32_NOPAD;
|
||||||
use hmac::Hmac;
|
|
||||||
use sha1::Sha1;
|
|
||||||
use sha2::{Sha256, Sha512};
|
|
||||||
use zeroize::Zeroize as _;
|
use zeroize::Zeroize as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{OtpAppendRequest, OtpInputSource, OtpInsertRequest},
|
command::{OtpAppendRequest, OtpInputSource, OtpInsertRequest},
|
||||||
crypto::{CryptoError, KeyStore, SecretProvider},
|
crypto::{CryptoError, KeyStore, SecretProvider},
|
||||||
git::{AutomaticEntryCommitter, GitError, GitIdentity},
|
git::{AutomaticEntryCommitter, GitError, GitIdentity},
|
||||||
|
otp_core,
|
||||||
recipient::{RecipientPolicyError, RecipientPolicyManager, SigningPolicy},
|
recipient::{RecipientPolicyError, RecipientPolicyManager, SigningPolicy},
|
||||||
repository::{EncryptedEntry, EntryPath, Repository, RepositoryError, SecretBytes},
|
repository::{EncryptedEntry, EntryPath, Repository, RepositoryError, SecretBytes},
|
||||||
write::{EntryAction, EntryCommit, EntryCommitError, EntryCommitter, OverwriteDecision},
|
write::{EntryAction, EntryCommit, EntryCommitError, EntryCommitter, OverwriteDecision},
|
||||||
@@ -27,12 +25,7 @@ pub enum OtpKind {
|
|||||||
Hotp,
|
Hotp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
pub use crate::otp_core::OtpAlgorithm;
|
||||||
pub enum OtpAlgorithm {
|
|
||||||
Sha1,
|
|
||||||
Sha256,
|
|
||||||
Sha512,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A validated key URI whose encoded and decoded secrets zeroize on drop.
|
/// A validated key URI whose encoded and decoded secrets zeroize on drop.
|
||||||
pub struct OtpUri {
|
pub struct OtpUri {
|
||||||
@@ -251,21 +244,8 @@ impl OtpUri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn code_for_counter(&self, counter: u64) -> Result<SecretBytes, OtpError> {
|
pub fn code_for_counter(&self, counter: u64) -> Result<SecretBytes, OtpError> {
|
||||||
let message = counter.to_be_bytes();
|
otp_core::code_for_counter(self.algorithm, self.secret.expose(), self.digits, counter)
|
||||||
let mut digest = match self.algorithm {
|
.map_err(|_| OtpError::InvalidSecret)
|
||||||
OtpAlgorithm::Sha1 => hmac_digest::<Hmac<Sha1>>(self.secret.expose(), &message)?,
|
|
||||||
OtpAlgorithm::Sha256 => hmac_digest::<Hmac<Sha256>>(self.secret.expose(), &message)?,
|
|
||||||
OtpAlgorithm::Sha512 => hmac_digest::<Hmac<Sha512>>(self.secret.expose(), &message)?,
|
|
||||||
};
|
|
||||||
let offset = usize::from(digest[digest.len() - 1] & 0x0f);
|
|
||||||
let binary = (u32::from(digest[offset]) & 0x7f) << 24
|
|
||||||
| u32::from(digest[offset + 1]) << 16
|
|
||||||
| u32::from(digest[offset + 2]) << 8
|
|
||||||
| u32::from(digest[offset + 3]);
|
|
||||||
digest.zeroize();
|
|
||||||
let modulus = 10_u32.pow(self.digits);
|
|
||||||
let code = format!("{:0width$}", binary % modulus, width = self.digits as usize);
|
|
||||||
Ok(SecretBytes::new(code.into_bytes()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incremented_hotp(&self) -> Result<(u64, Self), OtpError> {
|
fn incremented_hotp(&self) -> Result<(u64, Self), OtpError> {
|
||||||
@@ -1204,19 +1184,6 @@ fn percent_encode(value: &str) -> String {
|
|||||||
encoded
|
encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hmac_digest<M>(key: &[u8], message: &[u8]) -> Result<Vec<u8>, OtpError>
|
|
||||||
where
|
|
||||||
M: hmac::digest::Mac + hmac::digest::KeyInit,
|
|
||||||
{
|
|
||||||
let mut mac =
|
|
||||||
<M as hmac::digest::Mac>::new_from_slice(key).map_err(|_| OtpError::InvalidSecret)?;
|
|
||||||
mac.update(message);
|
|
||||||
let mut output = mac.finalize().into_bytes();
|
|
||||||
let digest = output.to_vec();
|
|
||||||
output.fill(0);
|
|
||||||
Ok(digest)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn find_uri(
|
pub(crate) fn find_uri(
|
||||||
plaintext: &SecretBytes,
|
plaintext: &SecretBytes,
|
||||||
entry: &EntryPath,
|
entry: &EntryPath,
|
||||||
|
|||||||
97
crates/storage/src/otp_core.rs
Normal file
97
crates/storage/src/otp_core.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
use hmac::Hmac;
|
||||||
|
use sha1::Sha1;
|
||||||
|
use sha2::{Sha256, Sha512};
|
||||||
|
use zeroize::Zeroize as _;
|
||||||
|
|
||||||
|
use crate::secret::SecretBytes;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum OtpAlgorithm {
|
||||||
|
Sha1,
|
||||||
|
Sha256,
|
||||||
|
Sha512,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code_for_counter(
|
||||||
|
algorithm: OtpAlgorithm,
|
||||||
|
secret: &[u8],
|
||||||
|
digits: u32,
|
||||||
|
counter: u64,
|
||||||
|
) -> Result<SecretBytes, OtpCoreError> {
|
||||||
|
if secret.is_empty() || !matches!(digits, 6 | 8) {
|
||||||
|
return Err(OtpCoreError);
|
||||||
|
}
|
||||||
|
let message = counter.to_be_bytes();
|
||||||
|
let mut digest = match algorithm {
|
||||||
|
OtpAlgorithm::Sha1 => hmac_digest::<Hmac<Sha1>>(secret, &message)?,
|
||||||
|
OtpAlgorithm::Sha256 => hmac_digest::<Hmac<Sha256>>(secret, &message)?,
|
||||||
|
OtpAlgorithm::Sha512 => hmac_digest::<Hmac<Sha512>>(secret, &message)?,
|
||||||
|
};
|
||||||
|
let offset = usize::from(digest[digest.len() - 1] & 0x0f);
|
||||||
|
let binary = (u32::from(digest[offset]) & 0x7f) << 24
|
||||||
|
| u32::from(digest[offset + 1]) << 16
|
||||||
|
| u32::from(digest[offset + 2]) << 8
|
||||||
|
| u32::from(digest[offset + 3]);
|
||||||
|
digest.zeroize();
|
||||||
|
let code = format!(
|
||||||
|
"{:0width$}",
|
||||||
|
binary % 10_u32.pow(digits),
|
||||||
|
width = digits as usize
|
||||||
|
);
|
||||||
|
Ok(SecretBytes::new(code.into_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hmac_digest<M>(key: &[u8], message: &[u8]) -> Result<Vec<u8>, OtpCoreError>
|
||||||
|
where
|
||||||
|
M: hmac::digest::Mac + hmac::digest::KeyInit,
|
||||||
|
{
|
||||||
|
let mut mac = <M as hmac::digest::Mac>::new_from_slice(key).map_err(|_| OtpCoreError)?;
|
||||||
|
mac.update(message);
|
||||||
|
let mut output = mac.finalize().into_bytes();
|
||||||
|
let digest = output.to_vec();
|
||||||
|
output.fill(0);
|
||||||
|
Ok(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct OtpCoreError;
|
||||||
|
|
||||||
|
impl fmt::Display for OtpCoreError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter.write_str("OTP parameters are invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for OtpCoreError {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{OtpAlgorithm, code_for_counter};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rfc_6238_vectors_cover_every_watch_algorithm() {
|
||||||
|
let vectors = [
|
||||||
|
(
|
||||||
|
OtpAlgorithm::Sha1,
|
||||||
|
b"12345678901234567890".as_slice(),
|
||||||
|
"94287082",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
OtpAlgorithm::Sha256,
|
||||||
|
b"12345678901234567890123456789012".as_slice(),
|
||||||
|
"46119246",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
OtpAlgorithm::Sha512,
|
||||||
|
b"1234567890123456789012345678901234567890123456789012345678901234".as_slice(),
|
||||||
|
"90693936",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for (algorithm, secret, expected) in vectors {
|
||||||
|
let code = code_for_counter(algorithm, secret, 8, 59 / 30).expect("RFC vector");
|
||||||
|
assert_eq!(code.expose(), expected.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,9 @@ use std::{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
pub use crate::secret::SecretBytes;
|
||||||
use cap_std::{ambient_authority, fs::Dir};
|
use cap_std::{ambient_authority, fs::Dir};
|
||||||
use cap_tempfile::TempFile;
|
use cap_tempfile::TempFile;
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
const ENTRY_EXTENSION: &str = "gpg";
|
const ENTRY_EXTENSION: &str = "gpg";
|
||||||
const RECIPIENT_FILE: &str = ".gpg-id";
|
const RECIPIENT_FILE: &str = ".gpg-id";
|
||||||
@@ -132,35 +132,6 @@ impl fmt::Debug for EncryptedEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypted bytes that are redacted in diagnostics and zeroed when dropped.
|
|
||||||
pub struct SecretBytes(Vec<u8>);
|
|
||||||
|
|
||||||
impl SecretBytes {
|
|
||||||
pub fn new(bytes: Vec<u8>) -> Self {
|
|
||||||
Self(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expose(&self) -> &[u8] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expose_mut(&mut self) -> &mut [u8] {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for SecretBytes {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
formatter.write_str("SecretBytes([REDACTED])")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for SecretBytes {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.0.zeroize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct EntryRecord {
|
pub struct EntryRecord {
|
||||||
path: EntryPath,
|
path: EntryPath,
|
||||||
|
|||||||
32
crates/storage/src/secret.rs
Normal file
32
crates/storage/src/secret.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use zeroize::Zeroize as _;
|
||||||
|
|
||||||
|
/// Secret bytes that are redacted in diagnostics and zeroed when dropped.
|
||||||
|
pub struct SecretBytes(Vec<u8>);
|
||||||
|
|
||||||
|
impl SecretBytes {
|
||||||
|
pub fn new(bytes: Vec<u8>) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expose(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expose_mut(&mut self) -> &mut [u8] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SecretBytes {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter.write_str("SecretBytes([REDACTED])")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SecretBytes {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ use std::fs;
|
|||||||
|
|
||||||
use ironstorage::{
|
use ironstorage::{
|
||||||
mobile_watch::{
|
mobile_watch::{
|
||||||
MobileWatchSnapshotState, WatchSnapshotApply, WatchSnapshotEntry, WatchSnapshotReceiver,
|
MobileWatchSnapshotState, WatchPersistenceAction, WatchRuntime, WatchSnapshotApply,
|
||||||
WatchSnapshotSender,
|
WatchSnapshotEntry, WatchSnapshotReceiver, WatchSnapshotSender,
|
||||||
},
|
},
|
||||||
otp::OtpAlgorithm,
|
otp::OtpAlgorithm,
|
||||||
repository::{EntryPath, SecretBytes},
|
repository::{EntryPath, SecretBytes},
|
||||||
@@ -15,7 +15,7 @@ type TestResult = Result<(), Box<dyn std::error::Error>>;
|
|||||||
|
|
||||||
fn entry(path: &str, issuer: &str, account: &str, secret: &[u8]) -> WatchSnapshotEntry {
|
fn entry(path: &str, issuer: &str, account: &str, secret: &[u8]) -> WatchSnapshotEntry {
|
||||||
WatchSnapshotEntry::new(
|
WatchSnapshotEntry::new(
|
||||||
EntryPath::parse(path).expect("fixture path"),
|
EntryPath::parse(path).expect("fixture path").to_string(),
|
||||||
Some(issuer.to_owned()),
|
Some(issuer.to_owned()),
|
||||||
account.to_owned(),
|
account.to_owned(),
|
||||||
OtpAlgorithm::Sha256,
|
OtpAlgorithm::Sha256,
|
||||||
@@ -165,3 +165,49 @@ fn journal_keeps_revisions_monotonic_across_sender_reloads() -> TestResult {
|
|||||||
assert_eq!(changed.revision(), 2);
|
assert_eq!(changed.revision(), 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn watch_runtime_generates_view_ready_totp_and_clears_secrets_when_locked() -> TestResult {
|
||||||
|
let directory = tempfile::tempdir()?;
|
||||||
|
let mut sender = WatchSnapshotSender::load(directory.path().join("watch-snapshot.toml"));
|
||||||
|
let snapshot = sender.prepare(
|
||||||
|
"paired-watch",
|
||||||
|
vec![WatchSnapshotEntry::new(
|
||||||
|
"otp/alice".to_owned(),
|
||||||
|
Some("Acme".to_owned()),
|
||||||
|
"alice".to_owned(),
|
||||||
|
OtpAlgorithm::Sha1,
|
||||||
|
8,
|
||||||
|
30,
|
||||||
|
SecretBytes::new(b"12345678901234567890".to_vec()),
|
||||||
|
)],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut runtime = WatchRuntime::default();
|
||||||
|
let update = runtime.apply_snapshot(snapshot.snapshot().expose().to_vec())?;
|
||||||
|
assert_eq!(update.apply(), WatchSnapshotApply::Replaced);
|
||||||
|
assert_eq!(update.persistence(), WatchPersistenceAction::Replace);
|
||||||
|
assert_eq!(update.selected_entries(), 1);
|
||||||
|
assert!(!update.receipt().is_empty());
|
||||||
|
|
||||||
|
let records = runtime.records_at(59)?;
|
||||||
|
assert_eq!(records.len(), 1);
|
||||||
|
assert_eq!(records[0].path(), "otp/alice");
|
||||||
|
assert_eq!(records[0].code().expose(), b"94287082");
|
||||||
|
assert_eq!(records[0].valid_until(), 60);
|
||||||
|
assert_eq!(records[0].remaining_at(59), 1);
|
||||||
|
|
||||||
|
runtime.protected_data_unavailable();
|
||||||
|
assert!(runtime.records_at(59).is_err());
|
||||||
|
|
||||||
|
let restored = runtime.apply_snapshot(snapshot.snapshot().expose().to_vec())?;
|
||||||
|
assert_eq!(restored.persistence(), WatchPersistenceAction::Replace);
|
||||||
|
assert_eq!(runtime.records_at(59)?[0].code().expose(), b"94287082");
|
||||||
|
|
||||||
|
let revocation = sender.prepare("paired-watch", Vec::new())?;
|
||||||
|
let revoked = runtime.apply_snapshot(revocation.snapshot().expose().to_vec())?;
|
||||||
|
assert_eq!(revoked.apply(), WatchSnapshotApply::Revoked);
|
||||||
|
assert_eq!(revoked.persistence(), WatchPersistenceAction::Delete);
|
||||||
|
assert!(runtime.records_at(59)?.is_empty());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
15
crates/watch-apple/Cargo.toml
Normal file
15
crates/watch-apple/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "ironstorage-watch-apple"
|
||||||
|
description = "Minimal UniFFI boundary for the IronStorage watchOS TOTP core"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "ironstorage_watch"
|
||||||
|
crate-type = ["lib", "staticlib", "cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ironstorage = { path = "../storage", default-features = false, features = ["watch"] }
|
||||||
|
uniffi.workspace = true
|
||||||
190
crates/watch-apple/src/lib.rs
Normal file
190
crates/watch-apple/src/lib.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(clippy::disallowed_types)]
|
||||||
|
|
||||||
|
//! Mechanical UniFFI exports for the minimal watchOS Rust runtime.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ironstorage::mobile_watch::{
|
||||||
|
WatchPersistenceAction as StoragePersistenceAction, WatchRuntime as StorageWatchRuntime,
|
||||||
|
WatchSnapshotApply as StorageSnapshotApply, WatchSnapshotError as StorageWatchError,
|
||||||
|
WatchSnapshotUpdate as StorageSnapshotUpdate, WatchTotpRecord as StorageTotpRecord,
|
||||||
|
};
|
||||||
|
uniffi::setup_scaffolding!();
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||||
|
pub enum WatchSnapshotApply {
|
||||||
|
Replaced,
|
||||||
|
Revoked,
|
||||||
|
Duplicate,
|
||||||
|
Stale,
|
||||||
|
PairingChanged,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageSnapshotApply> for WatchSnapshotApply {
|
||||||
|
fn from(value: StorageSnapshotApply) -> Self {
|
||||||
|
match value {
|
||||||
|
StorageSnapshotApply::Replaced => Self::Replaced,
|
||||||
|
StorageSnapshotApply::Revoked => Self::Revoked,
|
||||||
|
StorageSnapshotApply::Duplicate => Self::Duplicate,
|
||||||
|
StorageSnapshotApply::Stale => Self::Stale,
|
||||||
|
StorageSnapshotApply::PairingChanged => Self::PairingChanged,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||||
|
pub enum WatchPersistenceAction {
|
||||||
|
Keep,
|
||||||
|
Replace,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StoragePersistenceAction> for WatchPersistenceAction {
|
||||||
|
fn from(value: StoragePersistenceAction) -> Self {
|
||||||
|
match value {
|
||||||
|
StoragePersistenceAction::Keep => Self::Keep,
|
||||||
|
StoragePersistenceAction::Replace => Self::Replace,
|
||||||
|
StoragePersistenceAction::Delete => Self::Delete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
|
||||||
|
pub struct WatchSnapshotUpdate {
|
||||||
|
pub apply: WatchSnapshotApply,
|
||||||
|
pub persistence: WatchPersistenceAction,
|
||||||
|
pub revision: Option<u64>,
|
||||||
|
pub selected_entries: u32,
|
||||||
|
pub receipt: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageSnapshotUpdate> for WatchSnapshotUpdate {
|
||||||
|
fn from(value: StorageSnapshotUpdate) -> Self {
|
||||||
|
Self {
|
||||||
|
apply: value.apply().into(),
|
||||||
|
persistence: value.persistence().into(),
|
||||||
|
revision: value.revision(),
|
||||||
|
selected_entries: value.selected_entries(),
|
||||||
|
receipt: value.receipt().to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
|
||||||
|
pub struct WatchTotpRecord {
|
||||||
|
pub path: String,
|
||||||
|
pub issuer: Option<String>,
|
||||||
|
pub account: String,
|
||||||
|
pub code: String,
|
||||||
|
pub period: u64,
|
||||||
|
pub valid_until: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageTotpRecord> for WatchTotpRecord {
|
||||||
|
fn from(value: StorageTotpRecord) -> Self {
|
||||||
|
Self {
|
||||||
|
path: value.path().to_owned(),
|
||||||
|
issuer: value.issuer().map(str::to_owned),
|
||||||
|
account: value.account().to_owned(),
|
||||||
|
code: String::from_utf8(value.code().expose().to_vec())
|
||||||
|
.expect("storage-generated TOTP codes are ASCII"),
|
||||||
|
period: value.period(),
|
||||||
|
valid_until: value.valid_until(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, uniffi::Error)]
|
||||||
|
pub enum WatchFfiError {
|
||||||
|
Failed { message: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WatchFfiError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Failed { message } => formatter.write_str(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for WatchFfiError {}
|
||||||
|
|
||||||
|
impl From<StorageWatchError> for WatchFfiError {
|
||||||
|
fn from(value: StorageWatchError) -> Self {
|
||||||
|
Self::Failed {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(uniffi::Object)]
|
||||||
|
pub struct WatchCore {
|
||||||
|
runtime: Mutex<StorageWatchRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[uniffi::export]
|
||||||
|
impl WatchCore {
|
||||||
|
pub fn apply_snapshot(&self, snapshot: Vec<u8>) -> Result<WatchSnapshotUpdate, WatchFfiError> {
|
||||||
|
self.runtime
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| lock_error())?
|
||||||
|
.apply_snapshot(snapshot)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn records_at(&self, unix_seconds: u64) -> Result<Vec<WatchTotpRecord>, WatchFfiError> {
|
||||||
|
self.runtime
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| lock_error())?
|
||||||
|
.records_at(unix_seconds)
|
||||||
|
.map(|records| records.into_iter().map(Into::into).collect())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn protected_data_unavailable(&self) -> Result<(), WatchFfiError> {
|
||||||
|
self.runtime
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| lock_error())?
|
||||||
|
.protected_data_unavailable();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_persisted_snapshot(&self) -> Result<(), WatchFfiError> {
|
||||||
|
self.runtime
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| lock_error())?
|
||||||
|
.no_persisted_snapshot();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lock_error() -> WatchFfiError {
|
||||||
|
WatchFfiError::Failed {
|
||||||
|
message: "Apple Watch TOTP state is unavailable".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[uniffi::export]
|
||||||
|
pub fn watch_core() -> Arc<WatchCore> {
|
||||||
|
Arc::new(WatchCore {
|
||||||
|
runtime: Mutex::new(StorageWatchRuntime::default()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn bridge_masks_records_when_protected_data_is_unavailable() {
|
||||||
|
let core = super::watch_core();
|
||||||
|
core.no_persisted_snapshot().expect("available Keychain");
|
||||||
|
assert!(core.records_at(59).expect("empty snapshot").is_empty());
|
||||||
|
core.protected_data_unavailable().expect("lock transition");
|
||||||
|
assert!(core.records_at(59).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user