Make encode and validate take similar options

pull/2/head
LeRoyce Pearson 2021-05-11 01:20:54 -06:00
parent c3d434f588
commit b8267a940b
1 changed files with 15 additions and 13 deletions

28
jwt.zig
View File

@ -21,13 +21,13 @@ const JWTType = enum {
JWE, JWE,
}; };
pub const HeaderOptions = struct { pub const SignatureOptions = struct {
alg: Algorithm, alg: Algorithm,
key: []const u8, key: []const u8,
kid: ?[]const u8 = null, kid: ?[]const u8 = null,
}; };
pub fn encode(allocator: *std.mem.Allocator, payload: anytype, headerOptions: HeaderOptions) ![]const u8 { pub fn encode(allocator: *std.mem.Allocator, payload: anytype, signatureOptions: SignatureOptions) ![]const u8 {
var payload_json = std.ArrayList(u8).init(allocator); var payload_json = std.ArrayList(u8).init(allocator);
defer payload_json.deinit(); defer payload_json.deinit();
@ -35,9 +35,9 @@ pub fn encode(allocator: *std.mem.Allocator, payload: anytype, headerOptions: He
var protected_header = std.json.ObjectMap.init(allocator); var protected_header = std.json.ObjectMap.init(allocator);
defer protected_header.deinit(); defer protected_header.deinit();
try protected_header.put("alg", .{ .String = std.meta.tagName(headerOptions.alg) }); try protected_header.put("alg", .{ .String = std.meta.tagName(signatureOptions.alg) });
try protected_header.put("typ", .{ .String = "JWT" }); try protected_header.put("typ", .{ .String = "JWT" });
if (headerOptions.kid) |kid| { if (signatureOptions.kid) |kid| {
try protected_header.put("kid", .{ .String = kid }); try protected_header.put("kid", .{ .String = kid });
} }
@ -60,9 +60,9 @@ pub fn encode(allocator: *std.mem.Allocator, payload: anytype, headerOptions: He
jwt_text.items[protected_header_base64_len] = '.'; jwt_text.items[protected_header_base64_len] = '.';
_ = base64url.Encoder.encode(payload_base64, payload_json.items); _ = base64url.Encoder.encode(payload_base64, payload_json.items);
switch (headerOptions.alg) { switch (signatureOptions.alg) {
.HS256 => { .HS256 => {
const signature = generate_signature_hmac_sha256(headerOptions.key, protected_header_base64, payload_base64); const signature = generate_signature_hmac_sha256(signatureOptions.key, protected_header_base64, payload_base64);
const signature_base64_len = base64url.Encoder.calcSize(signature.len); const signature_base64_len = base64url.Encoder.calcSize(signature.len);
try jwt_text.resize(payload_base64_len + 1 + protected_header_base64_len + 1 + signature_base64_len); try jwt_text.resize(payload_base64_len + 1 + protected_header_base64_len + 1 + signature_base64_len);
@ -76,7 +76,7 @@ pub fn encode(allocator: *std.mem.Allocator, payload: anytype, headerOptions: He
return jwt_text.toOwnedSlice(); return jwt_text.toOwnedSlice();
} }
pub fn validate(allocator: *std.mem.Allocator, algorithm: Algorithm, key: []const u8, tokenText: []const u8) !ValueTree { pub fn validate(allocator: *std.mem.Allocator, tokenText: []const u8, signatureOptions: SignatureOptions) !ValueTree {
// 1. Verify that the JWT contains at least one period ('.') // 1. Verify that the JWT contains at least one period ('.')
// character. // character.
// 2. Let the Encoded JOSE Header be the portion of the JWT before the // 2. Let the Encoded JOSE Header be the portion of the JWT before the
@ -120,7 +120,7 @@ pub fn validate(allocator: *std.mem.Allocator, algorithm: Algorithm, key: []cons
const alg = std.meta.stringToEnum(Algorithm, alg_val.String) orelse return error.InvalidAlgorithm; const alg = std.meta.stringToEnum(Algorithm, alg_val.String) orelse return error.InvalidAlgorithm;
// Make sure that the algorithm matches: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ // Make sure that the algorithm matches: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
if (alg != algorithm) return error.InvalidAlgorithm; if (alg != signatureOptions.alg) return error.InvalidAlgorithm;
// TODO: Determine if "jku"/"jwk" need to be parsed and validated // TODO: Determine if "jku"/"jwk" need to be parsed and validated
@ -168,9 +168,9 @@ pub fn validate(allocator: *std.mem.Allocator, algorithm: Algorithm, key: []cons
defer allocator.free(signature); defer allocator.free(signature);
try base64url.Decoder.decode(signature, signature_base64); try base64url.Decoder.decode(signature, signature_base64);
switch (algorithm) { switch (signatureOptions.alg) {
.HS256 => { .HS256 => {
const gen_sig = generate_signature_hmac_sha256(key, jose_base64, payload_base64); const gen_sig = generate_signature_hmac_sha256(signatureOptions.key, jose_base64, payload_base64);
if (!std.mem.eql(u8, signature, &gen_sig)) { if (!std.mem.eql(u8, signature, &gen_sig)) {
return error.InvalidSignature; return error.InvalidSignature;
} }
@ -257,7 +257,7 @@ test "validate jws hmac sha-256" {
defer std.testing.allocator.free(key); defer std.testing.allocator.free(key);
try base64url.Decoder.decode(key, key_base64); try base64url.Decoder.decode(key, key_base64);
var claims_tree = try validate(std.testing.allocator, .HS256, key, token); var claims_tree = try validate(std.testing.allocator, token, .{ .alg = .HS256, .key = key });
defer claims_tree.deinit(); defer claims_tree.deinit();
var claims = claims_tree.root; var claims = claims_tree.root;
@ -276,10 +276,12 @@ test "generate and then validate jws hmac sha-256" {
.iat = @as(i64, 1516239022), .iat = @as(i64, 1516239022),
}; };
const token = try encode(std.testing.allocator, payload, .{ .alg = .HS256, .key = key }); const signatureOptions = SignatureOptions{ .alg = .HS256, .key = key };
const token = try encode(std.testing.allocator, payload, signatureOptions);
defer std.testing.allocator.free(token); defer std.testing.allocator.free(token);
var claims_tree = try validate(std.testing.allocator, .HS256, key, token); var claims_tree = try validate(std.testing.allocator, token, signatureOptions);
defer claims_tree.deinit(); defer claims_tree.deinit();
var claims = claims_tree.root; var claims = claims_tree.root;