mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 18:48:33 -04:00
feat: Added age-ffi.
This commit is contained in:
243
zig-vendor/age-ffi/zig/README.md
Normal file
243
zig-vendor/age-ffi/zig/README.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Age-FFI Zig Bindings
|
||||
|
||||
Idiomatic Zig bindings for the [age](https://age-encryption.org/) encryption library.
|
||||
|
||||
## Features
|
||||
|
||||
- **Complete FFI coverage** - All age-ffi functions exposed
|
||||
- **Memory safety** - RAII wrappers with automatic cleanup
|
||||
- **Idiomatic error handling** - Zig errors instead of C result codes
|
||||
- **Type safety** - Strong typing with Zig's type system
|
||||
- **Easy to use** - High-level API that feels native to Zig
|
||||
|
||||
## Building the C Library
|
||||
|
||||
First, build the Rust FFI library:
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This creates a static library at `../target/release/libage_ffi.a`.
|
||||
|
||||
## Using the Bindings
|
||||
|
||||
### In Your Build Script
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "my-app",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Add the age module
|
||||
const age_module = b.addModule("age", .{
|
||||
.root_source_file = .{ .path = "path/to/age-ffi/zig/age.zig" },
|
||||
});
|
||||
exe.root_module.addImport("age", age_module);
|
||||
|
||||
// Link the static library
|
||||
exe.addLibraryPath(.{ .path = "path/to/age-ffi/target/release" });
|
||||
exe.linkSystemLibrary("age_ffi");
|
||||
exe.linkLibC();
|
||||
|
||||
b.installArtifact(exe);
|
||||
}
|
||||
```
|
||||
|
||||
### In Your Code
|
||||
|
||||
```zig
|
||||
const age = @import("age");
|
||||
|
||||
// Generate a keypair
|
||||
var keypair = try age.generateKeypair();
|
||||
defer keypair.deinit();
|
||||
|
||||
// Encrypt
|
||||
const plaintext = "Secret message";
|
||||
var encrypted = try age.encrypt(plaintext, keypair.getPublicKey());
|
||||
defer encrypted.deinit();
|
||||
|
||||
// Decrypt
|
||||
var decrypted = try age.decrypt(encrypted.toSlice(), keypair.getPrivateKey());
|
||||
defer decrypted.deinit();
|
||||
```
|
||||
|
||||
## API Overview
|
||||
|
||||
### Key Generation
|
||||
|
||||
```zig
|
||||
// Generate new keypair
|
||||
var keypair = try age.generateKeypair();
|
||||
defer keypair.deinit();
|
||||
|
||||
// Derive public key from private key
|
||||
const public_key = try age.derivePublicKey(allocator, private_key);
|
||||
defer allocator.free(public_key);
|
||||
```
|
||||
|
||||
### Encryption
|
||||
|
||||
```zig
|
||||
// Simple encryption
|
||||
var encrypted = try age.encrypt(plaintext, recipient);
|
||||
defer encrypted.deinit();
|
||||
|
||||
// With ASCII armor
|
||||
var armored = try age.encryptArmor(plaintext, recipient);
|
||||
defer armored.deinit();
|
||||
|
||||
// Multiple recipients
|
||||
const recipients = [_][:0]const u8{ recipient1, recipient2 };
|
||||
var multi = try age.encryptMulti(plaintext, &recipients, false);
|
||||
defer multi.deinit();
|
||||
|
||||
// Passphrase-based
|
||||
var pass_enc = try age.encryptPassphrase(plaintext, passphrase, true);
|
||||
defer pass_enc.deinit();
|
||||
```
|
||||
|
||||
### Decryption
|
||||
|
||||
```zig
|
||||
// Simple decryption
|
||||
var decrypted = try age.decrypt(ciphertext, identity);
|
||||
defer decrypted.deinit();
|
||||
|
||||
// With multiple identities (tries each)
|
||||
const identities = [_][:0]const u8{ id1, id2 };
|
||||
var multi = try age.decryptMulti(ciphertext, &identities);
|
||||
defer multi.deinit();
|
||||
|
||||
// SSH key support
|
||||
var ssh_dec = try age.decryptSsh(ciphertext, ssh_private_key);
|
||||
defer ssh_dec.deinit();
|
||||
|
||||
// Passphrase-based
|
||||
var pass_dec = try age.decryptPassphrase(ciphertext, passphrase);
|
||||
defer pass_dec.deinit();
|
||||
```
|
||||
|
||||
### File Operations
|
||||
|
||||
```zig
|
||||
// Encrypt to file
|
||||
try age.encryptToFileArmor(plaintext, recipient, "/path/to/file.age");
|
||||
|
||||
// Decrypt from file
|
||||
var decrypted = try age.decryptFileWithIdentity("/path/to/file.age", identity);
|
||||
defer decrypted.deinit();
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```zig
|
||||
// Validate keys
|
||||
const is_valid = age.isValidX25519Recipient(recipient);
|
||||
|
||||
// Check recipient type
|
||||
const recipient_type = age.getRecipientType(recipient);
|
||||
// Returns: .invalid, .x25519, or .ssh
|
||||
```
|
||||
|
||||
### ASCII Armor
|
||||
|
||||
```zig
|
||||
// Add armor
|
||||
var armored = try age.armor(binary_data);
|
||||
defer armored.deinit();
|
||||
|
||||
// Remove armor
|
||||
var binary = try age.dearmor(armored_data);
|
||||
defer binary.deinit();
|
||||
```
|
||||
|
||||
## Memory Management
|
||||
|
||||
The bindings use RAII wrappers that automatically free resources:
|
||||
|
||||
- `Buffer` - Wraps `AgeBuffer`, freed on `deinit()`
|
||||
- `Keypair` - Wraps `AgeKeypair`, freed on `deinit()`
|
||||
- `CString` - Wraps C strings, freed on `deinit()`
|
||||
|
||||
Always call `defer x.deinit()` after creating these objects.
|
||||
|
||||
## Error Handling
|
||||
|
||||
All operations return `AgeError!T` with the following error types:
|
||||
|
||||
- `InvalidInput`
|
||||
- `EncryptionFailed`
|
||||
- `DecryptionFailed`
|
||||
- `KeygenFailed`
|
||||
- `IoError`
|
||||
- `InvalidRecipient`
|
||||
- `InvalidIdentity`
|
||||
- `NoRecipients`
|
||||
- `NoIdentities`
|
||||
- `ArmorError`
|
||||
- `PassphraseRequired`
|
||||
- `InvalidPassphrase`
|
||||
- `SshKeyError`
|
||||
- `MemoryAllocationFailed`
|
||||
- `InvalidUtf8`
|
||||
- `UnsupportedKey`
|
||||
|
||||
## Example
|
||||
|
||||
See `example.zig` for a comprehensive demonstration of all features.
|
||||
|
||||
Run the example:
|
||||
|
||||
```bash
|
||||
# Build the example (requires build.zig in this directory)
|
||||
zig build-exe example.zig -I.. -L../target/release -lage_ffi -lc
|
||||
|
||||
# Or manually:
|
||||
zig build-exe example.zig \
|
||||
-I.. \
|
||||
-L../target/release \
|
||||
-lage_ffi \
|
||||
-lc
|
||||
|
||||
./example
|
||||
```
|
||||
|
||||
## Low-Level C API
|
||||
|
||||
The module also exposes the raw C functions if you need direct FFI access:
|
||||
|
||||
```zig
|
||||
const result = age.age_encrypt(
|
||||
plaintext.ptr,
|
||||
plaintext.len,
|
||||
recipient.ptr,
|
||||
&output,
|
||||
);
|
||||
```
|
||||
|
||||
## Version Information
|
||||
|
||||
```zig
|
||||
const version = age.getVersion(); // age-ffi version
|
||||
const lib_version = age.getLibVersion(); // underlying age library version
|
||||
```
|
||||
|
||||
## Safety Notes
|
||||
|
||||
1. All C strings must be null-terminated (`:0` sentinel)
|
||||
2. Buffers returned by the library must be freed with `deinit()`
|
||||
3. Don't use buffers after calling `deinit()`
|
||||
4. The `toOwnedSlice()` method transfers ownership and calls `deinit()` automatically
|
||||
|
||||
## License
|
||||
|
||||
Same as the parent age-ffi project.
|
||||
Reference in New Issue
Block a user