Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Crypto module in Ziglang #44

Open
wants to merge 1 commit into
base: 3.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@
#xml= Introduces a new type of variable that provides both serialization and de-serialization from XML format. | XML library, libxml2-dev
#xmpp= Gateway between OpenSIPS and a jabber server. It enables the exchange of IMs between SIP clients and XMPP(jabber) clients. | parsing/building XML files, typically libexpat1-devel
#uuid= UUID generator | uuid-dev
#crypto= Cryptography |

exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd identity jabber json ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp
exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd identity jabber json ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp crypto

include_modules?=

Expand Down
3 changes: 3 additions & 0 deletions modules/crypto/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
zig-cache/
zig-out/
*.so
10 changes: 10 additions & 0 deletions modules/crypto/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include ../../Makefile.defs

NAME=crypto.so

$NAME:
-@MAKE_DEFS="$$(echo "$${DEFS}" | xargs) " \
zig build -Doptimize=ReleaseFast
-@cp zig-out/lib/libcrypto.so ${NAME}

include ../../Makefile.modules
44 changes: 44 additions & 0 deletions modules/crypto/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});

const optimize = b.standardOptimizeOption(.{});

const lib = b.addSharedLibrary(.{
.name = "crypto",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});

lib.linkLibC();
lib.addIncludePath(.{ .path = "../../" });
lib.strip = true;

var defs = std.mem.tokenizeSequence(u8, std.os.getenv("MAKE_DEFS").?, "-D");
while (defs.next()) |def| {
var kv = std.mem.tokenize(u8, def, "=");
const key = kv.next().?;
const value = kv.next();
if (value == null) {
lib.defineCMacro(key[0 .. key.len - 1], "");
continue;
}
const trim: usize = if (value.?[0] == '\'') 1 else 0;
lib.defineCMacro(key, value.?[trim..(value.?.len - trim - 1)]);
}

b.installArtifact(lib);

const main_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});

const run_main_tests = b.addRunArtifact(main_tests);

const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_main_tests.step);
}
46 changes: 46 additions & 0 deletions modules/crypto/src/log.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const std = @import("std");
pub const c = @cImport({
@cInclude("sr_module.h");
});

// Fixed C-to-Zig dp_time:
fn dp_time() callconv(.C) [*c]u8 {
var ltime: c.time_t = undefined;
_ = c.time(&ltime);
_ = c.ctime_r(&ltime, c.ctime_buf);
c.ctime_buf[19] = 0;
return c.ctime_buf + 4;
}

fn logLevelText(comptime level: std.log.Level) []const u8 {
return switch (level) {
.err => "ERROR",
.warn => "WARNING",
.info => "INFO",
.debug => "DEBUG",
};
}

pub fn logger(
comptime level: std.log.Level,
comptime scope: @TypeOf(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
const prefix = "{s} [{d}] " ++ comptime logLevelText(level) ++ ":" ++ @tagName(scope) ++ ": ";
var log_buf: [4096:0]u8 = undefined;
const log = std.fmt.bufPrint(&log_buf, prefix ++ format ++ "\n\x00", .{ dp_time(), c.dp_my_pid() } ++ args) catch trimmed: {
std.debug.assert(log_buf.len > 5);
const trim_text: []const u8 = "...\n";
@memcpy(log_buf[log_buf.len - trim_text.len ..], trim_text);
break :trimmed &log_buf;
};
const opensips_level = switch (level) {
std.log.Level.err => -1,
std.log.Level.warn => 1,
std.log.Level.info => 3,
std.log.Level.debug => 4,
};
if (opensips_level > c.log_level.*) return;
_ = c.dprint(opensips_level, c.log_facility, null, null, @ptrCast(@constCast(log)), @ptrCast(@constCast(log)), null);
}
123 changes: 123 additions & 0 deletions modules/crypto/src/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const std = @import("std");
const opensips = @import("opensips.zig");
const c = @cImport({
@cInclude("sr_module.h");
});

// Logger Initialization
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = @import("log.zig").logger;
};

const log = std.log.scoped(.crypto);

// Module Definition
const module = opensips.moduleExport(.{
.name = "crypto",
.cmds = &[_]c.cmd_export_t{
opensips.cmdExport(.{
.name = "rand",
.flags = c.ALL_ROUTES,
.function = &rand,
.param1_flags = c.CMD_PARAM_INT,
.param2_flags = c.CMD_PARAM_VAR,
}),
opensips.cmdExport(.{
.name = "md5",
.flags = c.ALL_ROUTES,
.function = &md5,
.param1_flags = c.CMD_PARAM_STR,
.param2_flags = c.CMD_PARAM_VAR,
}),
opensips.cmdExport(.{
.name = "sha1",
.flags = c.ALL_ROUTES,
.function = &sha1,
.param1_flags = c.CMD_PARAM_STR,
.param2_flags = c.CMD_PARAM_VAR,
}),
opensips.cmdExport(.{
.name = "blake3",
.flags = c.ALL_ROUTES,
.function = &blake3,
.param1_flags = c.CMD_PARAM_STR,
.param2_flags = c.CMD_PARAM_VAR,
}),
},
.init_f = &init,
.destroy_f = &deinit,
});

comptime {
@export(module, .{ .name = "exports" });
}

// Module Loaded Event
var rand_impl: std.rand.DefaultCsprng = undefined;

fn init() callconv(.C) c_int {
var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
_ = std.os.getrandom(&seed) catch {
log.err("The Crypto module load failed: failed to get initial random.", .{});
return -1;
};
rand_impl = std.rand.DefaultCsprng.init(seed);
std.crypto.utils.secureZero(u8, &seed);

log.debug("The Crypto module has been loaded.", .{});
return 0;
}

// Module Unloaded Event
fn deinit() callconv(.C) c_int {
rand_impl = undefined;
log.debug("The Crypto module has been unloaded.", .{});
return 0;
}

// Exported Functions
pub fn rand(msg: [*c]const c.sip_msg, input_int: *c_int, out_var: *c.pv_spec_t) callconv(.C) c_int {
const len = opensips.inputInt(input_int);
var rand_raw: [1024]u8 = undefined;
if (len > rand_raw.len) {
std.log.err("Requested random bytes: {d}; upper limit: {d}. Failed to generate.", .{ len, rand_raw.len });
return -1;
}

rand_impl.random().bytes(rand_raw[0..len]);
var rand_hex: [rand_raw.len * 2]u8 = undefined;
var rand_hex_trimmed = std.fmt.bufPrint(&rand_hex, "{}", .{std.fmt.fmtSliceHexLower(rand_raw[0..len])}) catch unreachable;

opensips.outStr(msg, out_var, rand_hex_trimmed) catch {
std.log.err("Output variable setting failed.", .{});
return -1;
};
return 1;
}

pub fn md5(msg: [*c]const c.sip_msg, input_str: *c.__str, out_var: *c.pv_spec_t) callconv(.C) c_int {
return hash(std.crypto.hash.Md5, msg, input_str, out_var);
}

pub fn sha1(msg: [*c]const c.sip_msg, input_str: *c.__str, out_var: *c.pv_spec_t) callconv(.C) c_int {
return hash(std.crypto.hash.Sha1, msg, input_str, out_var);
}

pub fn blake3(msg: [*c]const c.sip_msg, input_str: *c.__str, out_var: *c.pv_spec_t) callconv(.C) c_int {
return hash(std.crypto.hash.Blake3, msg, input_str, out_var);
}

pub fn hash(comptime T: type, msg: [*c]const c.sip_msg, input_str: *c.__str, out_var: *c.pv_spec_t) c_int {
const input = opensips.inputStr(input_str);

var hash_raw: [T.digest_length]u8 = undefined;
T.hash(input, &hash_raw, .{});
const hash_hex = std.fmt.bytesToHex(&hash_raw, .lower);

opensips.outStr(msg, out_var, &hash_hex) catch {
std.log.err("Output variable setting failed.", .{});
return -1;
};
return 1;
}
84 changes: 84 additions & 0 deletions modules/crypto/src/opensips.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const std = @import("std");
pub const c = @cImport({
@cInclude("sr_module.h");
});

pub const OpenSIPSError = error{OpenSIPSError};

fn defaultInit(comptime T: type, init: anytype) T {
var data = std.mem.zeroes(T);
for (@typeInfo(@TypeOf(init)).Struct.fields) |field| {
if (@hasField(T, field.name)) {
const value = @field(init, field.name);
switch (@typeInfo(field.type)) {
.Pointer => {
@field(data, field.name) = @ptrCast(value);
},
else => {
@field(data, field.name) = value;
},
}
}
}
return data;
}

pub fn moduleExport(init: anytype) c.module_exports {
var module = defaultInit(c.module_exports, init);

// zig fmt: off
module.ver_info = .{
.version = c.OPENSIPS_FULL_VERSION,
.compile_flags = c.OPENSIPS_COMPILE_FLAGS,
.scm = .{
.type = c.VERSIONTYPE,
.rev = c.THISREVISION,
}
};
// zig fmt: on

return module;
}

pub fn cmdExport(init: anytype) c.cmd_export_t {
const T = @TypeOf(init);

var cmd = defaultInit(c.cmd_export_t, init);

var params: [9]c.cmd_param = undefined;
for (0..params.len) |idx| {
const flags_field = std.fmt.comptimePrint("param{d}_flags", .{idx + 1});
const fixup_field = std.fmt.comptimePrint("param{d}_fixup", .{idx + 1});
const free_fixup_field = std.fmt.comptimePrint("param{d}_free_fixup_field", .{idx + 1});
params[idx] = c.cmd_param{
.flags = if (@hasField(T, flags_field)) @field(init, flags_field) else 0,
.fixup = if (@hasField(T, fixup_field)) @field(init, fixup_field) else null,
.free_fixup = if (@hasField(T, free_fixup_field)) @field(init, free_fixup_field) else null,
};
}
cmd.params = params;

return cmd;
}

pub fn inputStr(input_str: *c.__str) []u8 {
return input_str.s[0..@intCast(input_str.len)];
}

pub fn inputInt(input_int: *c_int) usize {
return @intCast(input_int.*);
}

pub fn outStr(msg: [*c]const c.sip_msg, out_var: *c.pv_spec_t, str: []const u8) OpenSIPSError!void {
const out_val = c.pv_value_t{
.rs = .{
.s = @ptrCast(@constCast(str)),
.len = @intCast(str.len),
},
.ri = 0,
.flags = c.PV_VAL_STR,
};
if (c.pv_set_value(@constCast(msg), out_var, 0, @constCast(&out_val)) != 0) {
return error.OpenSIPSError;
}
}
2 changes: 1 addition & 1 deletion scripts/build/do_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -e
. $(dirname $0)/build.conf.sub

EXCLUDE_MODULES="db_oracle osp sngtc cachedb_cassandra cachedb_couchbase \
cachedb_mongodb auth_jwt event_kafka aaa_diameter"
cachedb_mongodb auth_jwt event_kafka aaa_diameter crypto"
if [ ! -z "${EXCLUDE_MODULES_ADD}" ]
then
EXCLUDE_MODULES="${EXCLUDE_MODULES} ${EXCLUDE_MODULES_ADD}"
Expand Down
22 changes: 22 additions & 0 deletions test.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
loadmodule "modules/crypto/crypto.so"
loadmodule "proto_udp.so"

socket = udp:0.0.0.0:5060

startup_route {
$var(result) = "";
$var(input) = "hello";

md5("$var(input)", $var(result));
xlog("MD5 hash of \"$var(input)\": $var(result)\n");

sha1("$var(input)", $var(result));
xlog("SHA1 hash of \"$var(input)\": $var(result)\n");

blake3("$var(input)", $var(result));
xlog("Blake3 hash of \"$var(input)\": $var(result)\n");

$var(length) = 32;
rand($var(length), $var(result));
xlog(" $var(length) random bytes: $var(result)\n");
}