| 1 | //! Distinguised Encoding Rules as defined in X.690 and X.691. |
| 2 | //! |
| 3 | //! Subset of Basic Encoding Rules (BER) which eliminates flexibility in |
| 4 | //! an effort to acheive normality. Used in PKI. |
| 5 | const std = @import("std"); |
| 6 | const asn1 = @import("../asn1.zig"); |
| 7 | |
| 8 | pub const Decoder = @import("der/Decoder.zig"); |
| 9 | pub const Encoder = @import("der/Encoder.zig"); |
| 10 | |
| 11 | pub fn decode(comptime T: type, encoded: []const u8) !T { |
| 12 | var decoder = Decoder{ .bytes = encoded }; |
| 13 | const res = try decoder.any(T); |
| 14 | std.debug.assert(decoder.index == encoded.len); |
| 15 | return res; |
| 16 | } |
| 17 | |
| 18 | /// Caller owns returned memory. |
| 19 | pub fn encode(allocator: std.mem.Allocator, value: anytype) ![]u8 { |
| 20 | var encoder = Encoder.init(allocator); |
| 21 | defer encoder.deinit(); |
| 22 | try encoder.any(value); |
| 23 | return try encoder.buffer.toOwnedSlice(); |
| 24 | } |
| 25 | |
| 26 | test encode { |
| 27 | // https://lapo.it/asn1js/#MAgGAyoDBAIBBA |
| 28 | const Value = struct { a: asn1.Oid, b: i32 }; |
| 29 | const test_case = .{ |
| 30 | .value = Value{ .a = asn1.Oid.fromDotComptime("1.2.3.4"), .b = 4 }, |
| 31 | .encoded = &[_]u8{ 0x30, 0x08, 0x06, 0x03, 0x2A, 0x03, 0x04, 0x02, 0x01, 0x04 }, |
| 32 | }; |
| 33 | const allocator = std.testing.allocator; |
| 34 | const actual = try encode(allocator, test_case.value); |
| 35 | defer allocator.free(actual); |
| 36 | |
| 37 | try std.testing.expectEqualSlices(u8, test_case.encoded, actual); |
| 38 | } |
| 39 | |
| 40 | test decode { |
| 41 | // https://lapo.it/asn1js/#MAgGAyoDBAIBBA |
| 42 | const Value = struct { a: asn1.Oid, b: i32 }; |
| 43 | const test_case = .{ |
| 44 | .value = Value{ .a = asn1.Oid.fromDotComptime("1.2.3.4"), .b = 4 }, |
| 45 | .encoded = &[_]u8{ 0x30, 0x08, 0x06, 0x03, 0x2A, 0x03, 0x04, 0x02, 0x01, 0x04 }, |
| 46 | }; |
| 47 | const decoded = try decode(Value, test_case.encoded); |
| 48 | |
| 49 | try std.testing.expectEqualDeep(test_case.value, decoded); |
| 50 | } |
| 51 | |
| 52 | test "integer round trip across signed and unsigned boundaries" { |
| 53 | const allocator = std.testing.allocator; |
| 54 | inline for (.{ u8, u16, u32, i8, i16, i32 }) |T| { |
| 55 | const cases = comptime blk: { |
| 56 | const min = std.math.minInt(T); |
| 57 | const max = std.math.maxInt(T); |
| 58 | break :blk [_]T{ 0, 1, max, min, @divTrunc(max, 2), @divTrunc(min, 2) }; |
| 59 | }; |
| 60 | for (cases) |value| { |
| 61 | const buf = try encode(allocator, value); |
| 62 | defer allocator.free(buf); |
| 63 | const decoded = try decode(T, buf); |
| 64 | try std.testing.expectEqual(value, decoded); |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | test "encode skips null optional fields" { |
| 70 | const Value = struct { a: ?u8, b: u8 }; |
| 71 | const allocator = std.testing.allocator; |
| 72 | const actual = try encode(allocator, Value{ .a = null, .b = 5 }); |
| 73 | defer allocator.free(actual); |
| 74 | |
| 75 | try std.testing.expectEqualSlices(u8, &.{ 0x30, 0x03, 0x02, 0x01, 0x05 }, actual); |
| 76 | } |
| 77 | |
| 78 | test "encode preserves outer sequence tag after implicit field tags" { |
| 79 | const Value = struct { |
| 80 | a: u8, |
| 81 | b: u8, |
| 82 | |
| 83 | pub const asn1_tags = .{ |
| 84 | .a = asn1.FieldTag.initImplicit(0, .context_specific), |
| 85 | }; |
| 86 | }; |
| 87 | const allocator = std.testing.allocator; |
| 88 | const actual = try encode(allocator, Value{ .a = 1, .b = 2 }); |
| 89 | defer allocator.free(actual); |
| 90 | |
| 91 | try std.testing.expectEqualSlices(u8, &.{ 0x30, 0x06, 0x80, 0x01, 0x01, 0x02, 0x01, 0x02 }, actual); |
| 92 | } |
| 93 | |
| 94 | test { |
| 95 | _ = Decoder; |
| 96 | _ = Encoder; |
| 97 | } |