std1.0.0-beta-7 SafeC docs

Source http/jwt.sc

View in the repository

// SafeC Standard Library — JWT implementation (see jwt.h).
#pragma once
#include <std/http/jwt.h>
#include <std/crypto/hmac.h>
#include <std/encoding/base64.h>
#include <std/serial/value.h>
#include <std/serial/json.h>
#include <std/collections/string.h>
#include <std/collections/vec.h>
#include <std/mem.h>
#include <std/str.h>

namespace std {

static struct String __b64url_encode(const unsigned char* data, unsigned long len) {
    struct String s; unsafe { s = base64_encode(data[0..len]); }
    unsafe {
        s.replace_char('+', '-');
        s.replace_char('/', '_');
        while (s.length() > 0UL && s.char_at(s.length() - 1UL) == (int)'=') {
            s.pop_char();
        }
    }
    return s;
}

static struct Vec<unsigned char> __b64url_decode(const char* s, int* ok) {
    struct String padded; unsafe { padded = String::from(s); }
    unsafe {
        padded.replace_char('-', '+');
        padded.replace_char('_', '/');
        while (padded.length() % 4UL != 0UL) { padded.push_char('='); }
    }
    struct Vec<unsigned char> out;
    unsafe { out = base64_decode(padded.as_ptr(), ok); }
    unsafe { padded.free(); }
    return out;
}

struct String jwt_sign(const &Value claims, const &char secret) {
    struct Value header;
    unsafe {
        header = Value::object();
        header.object_set("alg", Value::from_str("HS256"));
        header.object_set("typ", Value::from_str("JWT"));
    }
    struct String headerJson; struct String payloadJson;
    unsafe {
        headerJson = value_to_json(&header);
        payloadJson = value_to_json(claims);
        header.free();
    }

    struct String headerB64;
    struct String payloadB64;
    unsafe {
        headerB64 = __b64url_encode((const unsigned char*)headerJson.data, headerJson.len);
        payloadB64 = __b64url_encode((const unsigned char*)payloadJson.data, payloadJson.len);
        headerJson.free(); payloadJson.free();
    }

    struct String signingInput = String::new();
    unsafe {
        signingInput.push_str(&headerB64);
        signingInput.push_char('.');
        signingInput.push_str(&payloadB64);
    }

    unsigned char sig[HMAC_SHA256_SIZE];
    unsafe {
        hmac_sha256_n_((const unsigned char*)secret, str_len(secret),
                    (const unsigned char*)signingInput.data, signingInput.len, sig);
    }
    struct String sigB64 = __b64url_encode((const unsigned char*)sig, (unsigned long)HMAC_SHA256_SIZE);

    struct String token = String::new();
    unsafe {
        token.push_str(&signingInput);
        token.push_char('.');
        token.push_str(&sigB64);
    }
    unsafe { headerB64.free(); payloadB64.free(); signingInput.free(); sigB64.free(); }
    return token;
}

// Finds the index of the Nth (0-based) occurrence of 'c' in 's', or -1.
static long long __nth_char_index(const char* s, char c, int n) {
    long long idx = -1LL;
    int found = 0;
    unsigned long i = 0UL;
    unsafe {
        while (s[i] != (char)0) {
            if (s[i] == c) {
                if (found == n) { idx = (long long)i; break; }
                found = found + 1;
            }
            i = i + 1UL;
        }
    }
    return idx;
}

struct Value jwt_verify(const &char token_ref, const &char secret, ?&int ok) { const char* token = token_ref;
    long long dot1 = __nth_char_index(token, '.', 0);
    long long dot2 = __nth_char_index(token, '.', 1);
    if (dot1 < 0LL || dot2 < 0LL) {
        unsafe { *ok = 0; }
        return Value::null();
    }

    struct String headerB64;
    struct String payloadB64;
    struct String sigB64;
    struct String signingInput;
    unsafe {
        headerB64 = String::from_n_(token, (unsigned long)dot1);
        payloadB64 = String::from_n_((const char*)token + dot1 + 1LL, (unsigned long)(dot2 - dot1 - 1LL));
        sigB64 = String::from((const char*)token + dot2 + 1LL);
        signingInput = String::from_n_(token, (unsigned long)dot2);
    }

    // Header must decode to alg="HS256" — the only algorithm this file
    // signs with or trusts; a token's own claim to use anything else
    // (including "none") is rejected, not honored.
    int headerOk;
    struct Vec<unsigned char> headerBytes = __b64url_decode(headerB64.as_ptr(), &headerOk);
    int headerValid = 0;
    if (headerOk) {
        struct String headerJson;
        unsafe { headerJson = String::from_n_((const char*)headerBytes.data, headerBytes.len); }
        int parseOk;
        struct Value header;
        unsafe { header = json_parse(headerJson.as_ptr(), &parseOk); headerJson.free(); }
        if (parseOk) {
            struct Value* alg;
            unsafe { alg = header.object_get("alg"); }
            if (alg != (struct Value*)0) {
                const char* algStr;
                unsafe { algStr = alg->as_string(); }
                unsafe { headerValid = str_eq(algStr, "HS256"); }
            }
        }
        unsafe { header.free(); }
    }
    unsafe { headerBytes.free(); }

    unsigned char expectedSig[HMAC_SHA256_SIZE];
    unsafe {
        hmac_sha256_n_((const unsigned char*)secret, str_len(secret),
                    (const unsigned char*)signingInput.data, signingInput.len, expectedSig);
    }
    struct String expectedSigB64 = __b64url_encode((const unsigned char*)expectedSig, (unsigned long)HMAC_SHA256_SIZE);

    int sigMatch;
    unsafe { sigMatch = sigB64.eq_cstr(expectedSigB64.as_ptr()); }

    struct Value result = Value::null();
    if (headerValid && sigMatch) {
        int payloadOk;
        struct Vec<unsigned char> payloadBytes = __b64url_decode(payloadB64.as_ptr(), &payloadOk);
        if (payloadOk) {
            struct String payloadJson;
            unsafe { payloadJson = String::from_n_((const char*)payloadBytes.data, payloadBytes.len); }
            int parseOk;
            unsafe { result = json_parse(payloadJson.as_ptr(), &parseOk); payloadJson.free(); }
            unsafe { *ok = parseOk; }
        } else {
            unsafe { *ok = 0; }
        }
        unsafe { payloadBytes.free(); }
    } else {
        unsafe { *ok = 0; }
    }

    unsafe {
        headerB64.free(); payloadB64.free(); sigB64.free(); signingInput.free();
        expectedSigB64.free();
    }
    return result;
}

} // namespace std