M src/crypto_box.zig +7 -8
@@ 2,6 2,8 @@ const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const randombytes = @import("randombytes.zig");
+const sodium = @import("sodium.zig");
+const SodiumError = sodium.SodiumError;
const c = @cImport({
@cInclude("sodium.h");
@@ 28,14 30,6 @@ pub const MACBYTES = c.crypto_box_MACBYT
//
// Wasn't that enlightening?
// TODO: Figure out what kinds of errors these things can return.
-const SodiumError = error{
- KeyGenError,
- SealError,
- UnsealError,
- EncryptError,
- OpenError,
- BufferTooSmall,
-};
/// Generate a public/private key pair for use in other functions in
/// this module.
@@ 147,6 141,8 @@ pub fn open_easy(
}
}
+// TODO: wrap sodium_malloc in an Allocator and point people to it.
+
/// Dynamic sealing object. Uses a mem.Allocator to allocate memory
/// for ciphertext.
///
@@ 219,6 215,7 @@ pub const Unsealer = struct {
};
test "seal" {
+ try sodium.init();
var pk: [PUBLICKEYBYTES]u8 = undefined;
var sk: [SECRETKEYBYTES]u8 = undefined;
@@ 235,6 232,7 @@ test "seal" {
}
test "sealer" {
+ try sodium.init();
var pk: [PUBLICKEYBYTES]u8 = undefined;
var sk: [SECRETKEYBYTES]u8 = undefined;
try keyPair(&pk, &sk);
@@ 253,6 251,7 @@ test "sealer" {
}
test "authenticated encryption" {
+ try sodium.init();
var alice_pub: [PUBLICKEYBYTES]u8 = undefined;
var alice_priv: [SECRETKEYBYTES]u8 = undefined;
var bob_pub: [PUBLICKEYBYTES]u8 = undefined;
A => src/crypto_sign.zig +93 -0
@@ 0,0 1,93 @@
+const std = @import("std");
+const testing = std.testing;
+const sodium = @import("sodium.zig");
+const SodiumError = sodium.SodiumError;
+
+const c = @cImport({
+ @cInclude("sodium.h");
+});
+
+const PUBLICKEYBYTES = c.crypto_sign_PUBLICKEYBYTES;
+const SECRETKEYBYTES = c.crypto_sign_SECRETKEYBYTES;
+const BYTES = c.crypto_sign_BYTES;
+
+/// Generate a key pair for signing. Don't share the secret half.
+pub fn keyPair(
+ pub_key: *[PUBLICKEYBYTES]u8,
+ secret_key: *[SECRETKEYBYTES]u8,
+) !void {
+ if (c.crypto_sign_keypair(pub_key, secret_key) != 0) {
+ return SodiumError.KeyGenError;
+ }
+}
+
+/// Sign a message with secret_key. Puts signed result in signed_msg,
+/// which must be at least BYTES larger than msg.
+pub fn sign(
+ signed_msg: []u8,
+ message: []const u8,
+ secret_key: *[SECRETKEYBYTES]u8,
+) !void {
+ if (signed_msg.len < message.len + BYTES) {
+ return SodiumError.BufferTooSmall;
+ }
+
+ var len: c_ulonglong = undefined;
+ if (c.crypto_sign(
+ signed_msg.ptr,
+ &len,
+ message.ptr,
+ message.len,
+ secret_key,
+ ) != 0) {
+ return SodiumError.SignError;
+ }
+
+ if (len > signed_msg.len) {
+ return SodiumError.BufferTooSmall;
+ }
+}
+
+/// Verify the signature on signed_msg, and put the raw message in
+/// msg, which must be able to hold at least (signed_msg.len - BYTES)
+/// bytes. Use the signer's public key.
+pub fn open(
+ msg: []u8,
+ signed_msg: []const u8,
+ public_key: *[PUBLICKEYBYTES]u8,
+) !void {
+ if (msg.len + BYTES < signed_msg.len) {
+ return SodiumError.BufferTooSmall;
+ }
+
+ var len: c_ulonglong = undefined;
+ if (c.crypto_sign_open(
+ msg.ptr,
+ &len,
+ signed_msg.ptr,
+ signed_msg.len,
+ public_key,
+ ) != 0) {
+ // It's an error because your program should crash if it
+ // doesn't handle it.
+ return SodiumError.InvalidSignature;
+ }
+ if (msg.len < len) {
+ return SodiumError.BufferTooSmall;
+ }
+}
+
+test "signature" {
+ try sodium.init();
+ var pub_key: [PUBLICKEYBYTES]u8 = undefined;
+ var secret_key: [SECRETKEYBYTES]u8 = undefined;
+
+ try keyPair(&pub_key, &secret_key);
+
+ const msg = "A special message that should not be changed."[0..];
+ var signed: [msg.len + BYTES]u8 = undefined;
+ try sign(signed[0..], msg, &secret_key);
+ var received: [signed.len - BYTES]u8 = undefined;
+ try open(received[0..], signed[0..], &pub_key);
+ testing.expectEqualSlices(u8, msg, received[0..]);
+}
A => src/errors.zig +14 -0
@@ 0,0 1,14 @@
+pub const SodiumError = error{
+ KeyGenError,
+ SealError,
+ UnsealError,
+ EncryptError,
+ OpenError,
+ BufferTooSmall,
+ ChunkTooBig,
+ InvalidCiphertext,
+ InitError,
+ InvalidHeader,
+ SignError,
+ InvalidSignature,
+};
M src/randombytes.zig +2 -0
@@ 1,5 1,6 @@
const std = @import("std");
const testing = std.testing;
+const sodium = @import("sodium.zig");
const c = @cImport({
@cInclude("sodium.h");
@@ 47,6 48,7 @@ pub fn stir(void) void {
}
test "random numbers" {
+ try sodium.init();
_ = random();
_ = uniform(0xd00dface);
var buffer = [_]u8{ 1, 2, 3, 4, 5 };
M src/secretstream.zig +13 -18
@@ 2,20 2,13 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const testing = std.testing;
+const sodium = @import("sodium.zig");
+const SodiumError = sodium.SodiumError;
const c = @cImport({
@cInclude("sodium.h");
});
-const StreamError = error{
- BufferTooShort,
- ChunkTooBig,
- EncryptError,
- InvalidCiphertext,
- InitError,
- InvalidHeader,
-};
-
const Tag = enum {
MESSAGE = c.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
PUSH = c.crypto_secretstream_xchacha20poly1305_TAG_PUSH,
@@ 44,7 37,7 @@ pub fn init_push(
) !void {
const fun = c.crypto_secretstream_xchacha20poly1305_init_push;
if (fun(state, header, key) != 0) {
- return StreamError.InitError;
+ return SodiumError.InitError;
}
}
@@ 62,7 55,7 @@ pub fn push(
// crypto_secretstream_xchacha20poly1305_ABYTES, so let's make
// sure there's room.
if (ciphertext.len < (message.len + ABYTES)) {
- return StreamError.BufferTooShort;
+ return SodiumError.BufferTooSmall;
}
const res = c.crypto_secretstream_xchacha20poly1305_push(
state,
@@ 76,10 69,10 @@ pub fn push(
);
if (clen > ciphertext.len) {
// I don't trust guarantees that aren't in the source code.
- return StreamError.BufferTooShort;
+ return SodiumError.BufferTooSmall;
}
if (res != 0) {
- return StreamError.EncryptError;
+ return SodiumError.EncryptError;
}
}
@@ 92,7 85,7 @@ pub fn init_pull(
) !void {
const fun = c.crypto_secretstream_xchacha20poly1305_init_pull;
if (fun(state, header, key) != 0) {
- return StreamError.InvalidHeader;
+ return SodiumError.InvalidHeader;
}
}
@@ 106,7 99,7 @@ pub fn pull(
additional_data: ?[]const u8,
) !void {
if (message.len < ciphertext.len - ABYTES) {
- return StreamError.BufferTooShort;
+ return SodiumError.BufferTooSmall;
}
var mlen: c_ulonglong = undefined;
const res = c.crypto_secretstream_xchacha20poly1305_pull(
@@ 120,10 113,10 @@ pub fn pull(
if (additional_data) |ad| ad.len else 0,
);
if (res != 0) {
- return StreamError.InvalidCiphertext;
+ return SodiumError.InvalidCiphertext;
}
if (mlen > message.len) {
- return StreamError.BufferTooShort;
+ return SodiumError.BufferTooSmall;
}
}
@@ 161,7 154,7 @@ pub fn ChunkEncrypter(chunk_size: usize)
/// Call with data to encrypt it.
pub fn push_chunk(self: *Self, msg: []const u8) !void {
if (msg.len > chunk_size) {
- return StreamError.ChunkTooBig;
+ return SodiumError.ChunkTooBig;
}
// There's probably a better way to do this than to copy
// the whole array over to the stack, byte by byte.
@@ 229,6 222,7 @@ pub fn ChunkDecrypter(chunk_size: usize)
}
test "stream" {
+ try sodium.init();
const msg = "Check check wheeee";
const msg2 = "Eekers";
var key: [KEYBYTES]u8 = undefined;
@@ 252,6 246,7 @@ test "stream" {
}
test "chunks" {
+ try sodium.init();
const msg = "This message is longer than my chunk size.";
const malloc = std.heap.c_allocator;
var key: [KEYBYTES]u8 = undefined;
M src/sodium.zig +10 -0
@@ 4,10 4,20 @@ const c = @cImport({
});
pub const crypto_box = @import("crypto_box.zig");
+pub const crypto_sign = @import("crypto_sign.zig");
pub const secretstream = @import("secretstream.zig");
+pub const randombytes = @import("randombytes.zig");
+pub const SodiumError = @import("errors.zig").SodiumError;
+
+pub fn init() !void {
+ if (c.sodium_init() < 0) {
+ return SodiumError.InitError;
+ }
+}
test "nacl" {
_ = @import("crypto_box.zig");
_ = @import("secretstream.zig");
_ = @import("randombytes.zig");
+ _ = @import("crypto_sign.zig");
}