// BLISP Web Token (BWT) // A BWT is a string: "(b64plain_header).(b64_plain_payload).(b64signature)" enum{hmac_key_len_min=32}; // HMAC-SHA256 is 32 bytes // HMAC signing (internal) static str bwt_hmac_signature_(arena *A, str b64header, str b64payload, str secret_key){ // sign this: "(b64header).(b64payload)" => signature str signing_input = str_printf(A, "%.*s.%.*s", pstr(b64header), pstr(b64payload)); if (signing_input.len == 0) return (str){0}; uint8_t *hmac_digest = new(A, uint8_t, EVP_MAX_MD_SIZE); unsigned int hmac_len; uint8_t * s = HMAC(EVP_sha256(), secret_key.data, secret_key.len, signing_input.data, (size_t)signing_input.len, hmac_digest, &hmac_len); unless(s){ log("ERROR: bwt_hmac_signature_ HMAC signature failed, continuing..."); return (str){0}; } return str_from_buf(hmac_digest, hmac_len); } /* --------------------------------------------------------------------------- Create BWT - returns a new str allocated in arena A. Header: #(:alg 'HS256' :typ 'BWT') Payload: #(:sub #'user_id' :exp #d) final result "(b64header).(b64payload).(b64signature)" Returns an empty str on error. --------------------------------------------------------------------------- */ static str create_bwt(arena *A, str secret_key, str user_id, int duration_secs, time_t epoch_now_secs) { if(duration_secs <= 0) return (str){0}; if (secret_key.len < hmac_key_len_min || user_id.len == 0) return (str){0}; str header = kstr("#(:alg 'HS256' :typ 'BWT')"); time_t expiry = epoch_now_secs + duration_secs; str payload = str_printf(A, "#(:sub #%d'%.*s' :exp #d%ld)", (int)user_id.len, (int)user_id.len, user_id.data, expiry); if (payload.len == 0) return (str){0}; str b64header = base64url_encode(A, header); str b64payload = base64url_encode(A, payload); if (b64header.len == 0 || b64payload.len == 0) return (str){0}; str const signature = bwt_hmac_signature_(A, b64header, b64payload, secret_key); str b64signature = base64url_encode(A, signature); if (b64signature.len == 0) return (str){0}; // final result "(b64header).(b64payload).(b64signature)" str token = str_printf(A, "%.*s.%.*s.%.*s", pstr(b64header), pstr(b64payload), pstr(b64signature)); log("create_bwt token created for user_id: [%.*s]\n", pstr(user_id)); return token; } /* --------------------------------------------------------------------------- Verify BWT - uses arena for temporary allocations and to store the extracted user_id. token ::= "(b64header).(b64payload).(b64signature)" secret_key length must match the HMAC algorithm used EVP_sha256. Returns user_id on success, empty str on error. Sets *expiry to the expiry timestamp (seconds since epoch). --------------------------------------------------------------------------- */ static str verify_bwt(arena *A, str token, str secret_key, int64_t *expiry, time_t epoch_now_secs) { if (token.len == 0 || secret_key.len == 0) return (str){0}; // split token: "(b64header).(b64payload).(b64signature)" str_pair parts1 = str_split_at(token, '.'); str b64header = parts1.a; str_pair parts2 = str_split_at(parts1.b, '.'); str b64payload = parts2.a; str b64signature = parts2.b; if(0==b64header.len || 0==b64payload.len || 0==b64signature.len) return (str){0}; str const expected_signature = bwt_hmac_signature_(A, b64header, b64payload, secret_key); str signature = base64url_decode(A, b64signature); if (signature.len == 0) return (str){0}; if (signature.len != hmac_key_len_min) return (str){0}; if (expected_signature.len != (unsigned int)signature.len) return (str){0}; if (CRYPTO_memcmp(expected_signature.data, signature.data, expected_signature.len) != 0) return (str){0}; str payload_blisp = base64url_decode(A, b64payload); if (payload_blisp.len == 0) return (str){0}; bl_result sub = bl_find_map_string(payload_blisp, kstr("sub")); bl_result exp = bl_find_map_integer(payload_blisp, kstr("exp")); unless(sub.ok && exp.ok) return (str){0}; if (exp.i <= epoch_now_secs) { log("verify_bwt token expired for user_id: [%.*s] expiry=%ld now=%ld\n", pstr(sub.s), exp.i, epoch_now_secs); return (str){0}; } if (expiry) *expiry = exp.i; return str_dup(A, sub.s); }