mirror of
https://github.com/ziglang/zig.git
synced 2025-12-14 18:23:12 +00:00
- flatten std.crypto.hash.Sha1 and give it a writable interface that optimizes splats - flatten std.hash.crc and give it a writable interface that optimizes splats - remove old writer impls from std.crypto - add fs.File.Writer.moveToReader - add fs.File.Writer.seekTo - add std.io.Reader.Hashed and std.io.Writer.Hashed which are passthrough streams. Instead of passing through to null writer, use the writable interface implemented directly on hashers which doesn't have to account for passing through the data. - add std.io.BufferedWriter.writeSplatAll
353 lines
11 KiB
Zig
353 lines
11 KiB
Zig
//! ASN.1 types for public consumption.
|
|
const std = @import("std");
|
|
pub const der = @import("./asn1/der.zig");
|
|
pub const Oid = @import("./asn1/Oid.zig");
|
|
|
|
pub const Index = u32;
|
|
|
|
pub const Tag = struct {
|
|
number: Number,
|
|
/// Whether this ASN.1 type contains other ASN.1 types.
|
|
constructed: bool,
|
|
class: Class,
|
|
|
|
/// These values apply to class == .universal.
|
|
pub const Number = enum(u16) {
|
|
// 0 is reserved by spec
|
|
boolean = 1,
|
|
integer = 2,
|
|
bitstring = 3,
|
|
octetstring = 4,
|
|
null = 5,
|
|
oid = 6,
|
|
object_descriptor = 7,
|
|
real = 9,
|
|
enumerated = 10,
|
|
embedded = 11,
|
|
string_utf8 = 12,
|
|
oid_relative = 13,
|
|
time = 14,
|
|
// 15 is reserved to mean that the tag is >= 32
|
|
sequence = 16,
|
|
/// Elements may appear in any order.
|
|
sequence_of = 17,
|
|
string_numeric = 18,
|
|
string_printable = 19,
|
|
string_teletex = 20,
|
|
string_videotex = 21,
|
|
string_ia5 = 22,
|
|
utc_time = 23,
|
|
generalized_time = 24,
|
|
string_graphic = 25,
|
|
string_visible = 26,
|
|
string_general = 27,
|
|
string_universal = 28,
|
|
string_char = 29,
|
|
string_bmp = 30,
|
|
date = 31,
|
|
time_of_day = 32,
|
|
date_time = 33,
|
|
duration = 34,
|
|
/// IRI = Internationalized Resource Identifier
|
|
oid_iri = 35,
|
|
oid_iri_relative = 36,
|
|
_,
|
|
};
|
|
|
|
pub const Class = enum(u2) {
|
|
universal,
|
|
application,
|
|
context_specific,
|
|
private,
|
|
};
|
|
|
|
pub fn init(number: Tag.Number, constructed: bool, class: Tag.Class) Tag {
|
|
return .{ .number = number, .constructed = constructed, .class = class };
|
|
}
|
|
|
|
pub fn universal(number: Tag.Number, constructed: bool) Tag {
|
|
return .{ .number = number, .constructed = constructed, .class = .universal };
|
|
}
|
|
|
|
pub fn decode(reader: anytype) !Tag {
|
|
const tag1: FirstTag = @bitCast(try reader.readByte());
|
|
var number: u14 = tag1.number;
|
|
|
|
if (tag1.number == 15) {
|
|
const tag2: NextTag = @bitCast(try reader.readByte());
|
|
number = tag2.number;
|
|
if (tag2.continues) {
|
|
const tag3: NextTag = @bitCast(try reader.readByte());
|
|
number = (number << 7) + tag3.number;
|
|
if (tag3.continues) return error.InvalidLength;
|
|
}
|
|
}
|
|
|
|
return Tag{
|
|
.number = @enumFromInt(number),
|
|
.constructed = tag1.constructed,
|
|
.class = tag1.class,
|
|
};
|
|
}
|
|
|
|
pub fn encode(self: Tag, writer: *std.io.BufferedWriter) std.io.Writer.Error!void {
|
|
var tag1: FirstTag = .{
|
|
.number = undefined,
|
|
.constructed = self.constructed,
|
|
.class = self.class,
|
|
};
|
|
switch (@intFromEnum(self.number)) {
|
|
0...std.math.maxInt(u5) => |n| {
|
|
tag1.number = @intCast(n);
|
|
try writer.writeByte(@bitCast(tag1));
|
|
},
|
|
std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {
|
|
tag1.number = 15;
|
|
const tag2 = NextTag{ .number = @intCast(n), .continues = false };
|
|
try writer.writeByte(@bitCast(tag1));
|
|
try writer.writeByte(@bitCast(tag2));
|
|
},
|
|
else => |n| {
|
|
tag1.number = 15;
|
|
const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };
|
|
const tag3 = NextTag{ .number = @truncate(n), .continues = false };
|
|
try writer.writeByte(@bitCast(tag1));
|
|
try writer.writeByte(@bitCast(tag2));
|
|
try writer.writeByte(@bitCast(tag3));
|
|
},
|
|
}
|
|
}
|
|
|
|
const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
|
|
const NextTag = packed struct(u8) { number: u7, continues: bool };
|
|
|
|
pub fn toExpected(self: Tag) ExpectedTag {
|
|
return ExpectedTag{
|
|
.number = self.number,
|
|
.constructed = self.constructed,
|
|
.class = self.class,
|
|
};
|
|
}
|
|
|
|
pub fn fromZig(comptime T: type) Tag {
|
|
switch (@typeInfo(T)) {
|
|
.@"struct", .@"enum", .@"union" => {
|
|
if (@hasDecl(T, "asn1_tag")) return T.asn1_tag;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
switch (@typeInfo(T)) {
|
|
.@"struct", .@"union" => return universal(.sequence, true),
|
|
.bool => return universal(.boolean, false),
|
|
.int => return universal(.integer, false),
|
|
.@"enum" => |e| {
|
|
if (@hasDecl(T, "oids")) return Oid.asn1_tag;
|
|
return universal(if (e.is_exhaustive) .enumerated else .integer, false);
|
|
},
|
|
.optional => |o| return fromZig(o.child),
|
|
.null => return universal(.null, false),
|
|
else => @compileError("cannot map Zig type to asn1_tag " ++ @typeName(T)),
|
|
}
|
|
}
|
|
};
|
|
|
|
test Tag {
|
|
const buf = [_]u8{0xa3};
|
|
var stream = std.io.fixedBufferStream(&buf);
|
|
const t = Tag.decode(stream.reader());
|
|
try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
|
|
}
|
|
|
|
/// A decoded view.
|
|
pub const Element = struct {
|
|
tag: Tag,
|
|
slice: Slice,
|
|
|
|
pub const Slice = struct {
|
|
start: Index,
|
|
end: Index,
|
|
|
|
pub fn len(self: Slice) Index {
|
|
return self.end - self.start;
|
|
}
|
|
|
|
pub fn view(self: Slice, bytes: []const u8) []const u8 {
|
|
return bytes[self.start..self.end];
|
|
}
|
|
};
|
|
|
|
pub const DecodeError = error{ InvalidLength, EndOfStream };
|
|
|
|
/// Safely decode a DER/BER/CER element at `index`:
|
|
/// - Ensures length uses shortest form
|
|
/// - Ensures length is within `bytes`
|
|
/// - Ensures length is less than `std.math.maxInt(Index)`
|
|
pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
|
|
var stream = std.io.fixedBufferStream(bytes[index..]);
|
|
var reader = stream.reader();
|
|
|
|
const tag = try Tag.decode(reader);
|
|
const size_or_len_size = try reader.readByte();
|
|
|
|
var start = index + 2;
|
|
var end = start + size_or_len_size;
|
|
// short form between 0-127
|
|
if (size_or_len_size < 128) {
|
|
if (end > bytes.len) return error.InvalidLength;
|
|
} else {
|
|
// long form between 0 and std.math.maxInt(u1024)
|
|
const len_size: u7 = @truncate(size_or_len_size);
|
|
start += len_size;
|
|
if (len_size > @sizeOf(Index)) return error.InvalidLength;
|
|
|
|
const len = try reader.readVarInt(Index, .big, len_size);
|
|
if (len < 128) return error.InvalidLength; // should have used short form
|
|
|
|
end = std.math.add(Index, start, len) catch return error.InvalidLength;
|
|
if (end > bytes.len) return error.InvalidLength;
|
|
}
|
|
|
|
return Element{ .tag = tag, .slice = Slice{ .start = start, .end = end } };
|
|
}
|
|
};
|
|
|
|
test Element {
|
|
const short_form = [_]u8{ 0x30, 0x03, 0x02, 0x01, 0x09 };
|
|
try std.testing.expectEqual(Element{
|
|
.tag = Tag.universal(.sequence, true),
|
|
.slice = Element.Slice{ .start = 2, .end = short_form.len },
|
|
}, Element.decode(&short_form, 0));
|
|
|
|
const long_form = [_]u8{ 0x30, 129, 129 } ++ [_]u8{0} ** 129;
|
|
try std.testing.expectEqual(Element{
|
|
.tag = Tag.universal(.sequence, true),
|
|
.slice = Element.Slice{ .start = 3, .end = long_form.len },
|
|
}, Element.decode(&long_form, 0));
|
|
}
|
|
|
|
/// For decoding.
|
|
pub const ExpectedTag = struct {
|
|
number: ?Tag.Number = null,
|
|
constructed: ?bool = null,
|
|
class: ?Tag.Class = null,
|
|
|
|
pub fn init(number: ?Tag.Number, constructed: ?bool, class: ?Tag.Class) ExpectedTag {
|
|
return .{ .number = number, .constructed = constructed, .class = class };
|
|
}
|
|
|
|
pub fn primitive(number: ?Tag.Number) ExpectedTag {
|
|
return .{ .number = number, .constructed = false, .class = .universal };
|
|
}
|
|
|
|
pub fn match(self: ExpectedTag, tag: Tag) bool {
|
|
if (self.number) |e| {
|
|
if (tag.number != e) return false;
|
|
}
|
|
if (self.constructed) |e| {
|
|
if (tag.constructed != e) return false;
|
|
}
|
|
if (self.class) |e| {
|
|
if (tag.class != e) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
pub const FieldTag = struct {
|
|
number: std.meta.Tag(Tag.Number),
|
|
class: Tag.Class,
|
|
explicit: bool = true,
|
|
|
|
pub fn initExplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
|
|
return .{ .number = number, .class = class, .explicit = true };
|
|
}
|
|
|
|
pub fn initImplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
|
|
return .{ .number = number, .class = class, .explicit = false };
|
|
}
|
|
|
|
pub fn fromContainer(comptime Container: type, comptime field_name: []const u8) ?FieldTag {
|
|
if (@hasDecl(Container, "asn1_tags") and @hasField(@TypeOf(Container.asn1_tags), field_name)) {
|
|
return @field(Container.asn1_tags, field_name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn toTag(self: FieldTag) Tag {
|
|
return Tag.init(@enumFromInt(self.number), self.explicit, self.class);
|
|
}
|
|
};
|
|
|
|
pub const BitString = struct {
|
|
/// Number of bits in rightmost byte that are unused.
|
|
right_padding: u3 = 0,
|
|
bytes: []const u8,
|
|
|
|
pub fn bitLen(self: BitString) usize {
|
|
return self.bytes.len * 8 - self.right_padding;
|
|
}
|
|
|
|
const asn1_tag = Tag.universal(.bitstring, false);
|
|
|
|
pub fn decodeDer(decoder: *der.Decoder) !BitString {
|
|
const ele = try decoder.element(asn1_tag.toExpected());
|
|
const bytes = decoder.view(ele);
|
|
|
|
if (bytes.len < 1) return error.InvalidBitString;
|
|
const padding = bytes[0];
|
|
if (padding >= 8) return error.InvalidBitString;
|
|
const right_padding: u3 = @intCast(padding);
|
|
|
|
// DER requires that unused bits be zero.
|
|
if (@ctz(bytes[bytes.len - 1]) < right_padding) return error.InvalidBitString;
|
|
|
|
return BitString{ .bytes = bytes[1..], .right_padding = right_padding };
|
|
}
|
|
|
|
pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
|
|
try encoder.writer().writeAll(self.bytes);
|
|
try encoder.writer().writeByte(self.right_padding);
|
|
try encoder.length(self.bytes.len + 1);
|
|
try encoder.tag(asn1_tag);
|
|
}
|
|
};
|
|
|
|
pub fn Opaque(comptime tag: Tag) type {
|
|
return struct {
|
|
bytes: []const u8,
|
|
|
|
pub fn decodeDer(decoder: *der.Decoder) !@This() {
|
|
const ele = try decoder.element(tag.toExpected());
|
|
if (tag.constructed) decoder.index = ele.slice.end;
|
|
return .{ .bytes = decoder.view(ele) };
|
|
}
|
|
|
|
pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
|
|
try encoder.tagBytes(tag, self.bytes);
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Use sparingly.
|
|
pub const Any = struct {
|
|
tag: Tag,
|
|
bytes: []const u8,
|
|
|
|
pub fn decodeDer(decoder: *der.Decoder) !@This() {
|
|
const ele = try decoder.element(ExpectedTag{});
|
|
return .{ .tag = ele.tag, .bytes = decoder.view(ele) };
|
|
}
|
|
|
|
pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
|
|
try encoder.tagBytes(self.tag, self.bytes);
|
|
}
|
|
};
|
|
|
|
test {
|
|
_ = der;
|
|
_ = Oid;
|
|
_ = @import("asn1/test.zig");
|
|
}
|