diff --git a/_test_crypto/bux.toml b/_test_crypto/bux.toml new file mode 100644 index 0000000..0bbf836 --- /dev/null +++ b/_test_crypto/bux.toml @@ -0,0 +1 @@ +[Package] diff --git a/_test_crypto/src/Main.bux b/_test_crypto/src/Main.bux new file mode 100644 index 0000000..51523e7 --- /dev/null +++ b/_test_crypto/src/Main.bux @@ -0,0 +1,18 @@ +import Std::Io::{PrintLine}; +import Std::Crypto; + +func Main() -> int { + PrintLine("SHA256:"); + PrintLine(Crypto_Sha256("hello")); + + PrintLine("HMAC-SHA256:"); + PrintLine(Crypto_HmacSha256("secret", "message")); + + PrintLine("Base64 encode:"); + PrintLine(Crypto_Base64Encode("hello")); + + PrintLine("Random bytes (base64):"); + PrintLine(Crypto_RandomBytes(16)); + + return 0; +} diff --git a/compiler/bootstrap/cli.nim b/compiler/bootstrap/cli.nim index dfaaefb..80a9809 100644 --- a/compiler/bootstrap/cli.nim +++ b/compiler/bootstrap/cli.nim @@ -536,7 +536,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = # Compile with cc let outputName = if man.name != "": man.name else: "bux_out" let outputFile = buildDir / outputName - let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm 2>&1" + let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1" if opts.verbose: printInfo(&"running: {ccCmd}", useColor) let (output, exitCode) = execCmdEx(ccCmd) diff --git a/examples/jwt.bux b/examples/jwt.bux new file mode 100644 index 0000000..a634f3c --- /dev/null +++ b/examples/jwt.bux @@ -0,0 +1,58 @@ +// JWT - Create a JSON Web Token +import Std::Io::{PrintLine}; +import Std::Json; +import Std::Crypto; +import Std::String::{String_Replace, String_Concat, String_EndsWith, String_Slice, String_Len}; + +func Base64UrlEncode(s: String) -> String { + let b64: String = Crypto_Base64Encode(s); + let r1: String = String_Replace(b64, "+", "-"); + let r2: String = String_Replace(r1, "/", "_"); + var result: String = r2; + while String_EndsWith(result, "=") { + result = String_Slice(result, 0, String_Len(result) - 1); + } + return result; +} + +func Base64UrlEncodeSig(b64: String) -> String { + let r1: String = String_Replace(b64, "+", "-"); + let r2: String = String_Replace(r1, "/", "_"); + var result: String = r2; + while String_EndsWith(result, "=") { + result = String_Slice(result, 0, String_Len(result) - 1); + } + return result; +} + +func Jwt_Create(payload: JsonValue, secret: String) -> String { + let header: JsonValue = Json_Object(); + Json_ObjectSet(&header, "alg", Json_String("HS256")); + Json_ObjectSet(&header, "typ", Json_String("JWT")); + + let headerB64: String = Base64UrlEncode(Json_Stringify(header)); + let payloadB64: String = Base64UrlEncode(Json_Stringify(payload)); + + let signingInput: String = String_Concat(headerB64, "."); + signingInput = String_Concat(signingInput, payloadB64); + + let sigRaw: String = Crypto_HmacSha256Raw(secret, signingInput); + let sigB64: String = Base64UrlEncodeSig(sigRaw); + + return String_Concat(String_Concat(signingInput, "."), sigB64); +} + +func Main() -> int { + PrintLine("=== JWT Demo ==="); + + let payload: JsonValue = Json_Object(); + Json_ObjectSet(&payload, "sub", Json_String("user123")); + Json_ObjectSet(&payload, "name", Json_String("Bux Developer")); + Json_ObjectSet(&payload, "admin", Json_Bool(true)); + + let token: String = Jwt_Create(payload, "my-secret-key"); + PrintLine("Token:"); + PrintLine(token); + + return 0; +} diff --git a/library/runtime/runtime.c b/library/runtime/runtime.c index b9dee1f..4213a89 100644 --- a/library/runtime/runtime.c +++ b/library/runtime/runtime.c @@ -1248,3 +1248,59 @@ int bux_socket_close(int fd) { const char* bux_socket_error(void) { return strerror(errno); } + + +/* ============================================================================ + * Cryptography primitives (OpenSSL) + * ============================================================================ */ + +#include +#include +#include + +void bux_sha256(const char* data, int len, unsigned char* out) { + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); + EVP_DigestUpdate(ctx, data, (size_t)len); + EVP_DigestFinal_ex(ctx, out, NULL); + EVP_MD_CTX_free(ctx); +} + +void bux_hmac_sha256(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) { + unsigned int outlen = 32; + HMAC(EVP_sha256(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen); +} + +int bux_random_bytes(unsigned char* buf, int len) { + return RAND_bytes(buf, len); +} + +char* bux_base64_encode(const unsigned char* in, int inlen) { + int outlen = 4 * ((inlen + 2) / 3); + char* out = (char*)bux_alloc(outlen + 1); + int elen = EVP_EncodeBlock((unsigned char*)out, in, inlen); + out[elen] = '\0'; + return out; +} + +char* bux_base64_decode(const char* in, int inlen, int* outlen) { + int maxlen = 3 * inlen / 4; + char* out = (char*)bux_alloc(maxlen + 1); + *outlen = EVP_DecodeBlock((unsigned char*)out, (const unsigned char*)in, inlen); + if (*outlen < 0) { + *outlen = 0; + out[0] = '\0'; + } + return out; +} + +char* bux_bytes_to_hex(const unsigned char* data, int len) { + char* out = (char*)bux_alloc((size_t)len * 2 + 1); + const char* hex = "0123456789abcdef"; + for (int i = 0; i < len; i++) { + out[i * 2] = hex[(data[i] >> 4) & 0x0F]; + out[i * 2 + 1] = hex[data[i] & 0x0F]; + } + out[len * 2] = '\0'; + return out; +} diff --git a/library/std/Crypto.bux b/library/std/Crypto.bux new file mode 100644 index 0000000..5312a8f --- /dev/null +++ b/library/std/Crypto.bux @@ -0,0 +1,64 @@ +module Std::Crypto { +extern func bux_sha256(data: String, len: int, out: *void); +extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void); +extern func bux_random_bytes(buf: *void, len: int) -> int; +extern func bux_base64_encode(data: String, len: int) -> String; +extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String; +extern func bux_bytes_to_hex(data: *void, len: int) -> String; + +/* SHA-256: returns lowercase hex string of the hash */ +func Crypto_Sha256(data: String) -> String { + let len: int = String_Len(data) as int; + let hashBuf: *void = Alloc(32); + bux_sha256(data, len, hashBuf); + let result: String = bux_bytes_to_hex(hashBuf as *void, 32); + Free(hashBuf); + return result; +} + +/* HMAC-SHA256: returns lowercase hex string */ +func Crypto_HmacSha256(key: String, message: String) -> String { + let keylen: int = String_Len(key) as int; + let msglen: int = String_Len(message) as int; + let hmacBuf: *void = Alloc(32); + bux_hmac_sha256(key, keylen, message, msglen, hmacBuf); + let result: String = bux_bytes_to_hex(hmacBuf as *void, 32); + Free(hmacBuf); + return result; +} + +/* Random bytes: returns a string of n random bytes */ +func Crypto_RandomBytes(n: int) -> String { + if n <= 0 { return ""; } + let buf: *void = Alloc(n as uint); + if bux_random_bytes(buf, n) != 1 { + Free(buf); + return ""; + } + let result: String = bux_base64_encode(buf as String, n); + Free(buf); + return result; +} + +/* Base64 encode */ +func Crypto_Base64Encode(s: String) -> String { + return bux_base64_encode(s, String_Len(s) as int); +} + +/* HMAC-SHA256 raw: returns base64 of raw binary hmac */ +func Crypto_HmacSha256Raw(key: String, message: String) -> String { + let keylen: int = String_Len(key) as int; + let msglen: int = String_Len(message) as int; + let hmacBuf: *void = Alloc(32); + bux_hmac_sha256(key, keylen, message, msglen, hmacBuf); + let result: String = bux_base64_encode(hmacBuf as String, 32); + Free(hmacBuf); + return result; +} + +/* Base64 decode */ +func Crypto_Base64Decode(s: String) -> String { + let outlen: int = 0; + return bux_base64_decode(s, String_Len(s) as int, &outlen); +} +}