struct Oid [src]
Alias for std.crypto.asn1.Oid
Globally unique hierarchical identifier made of a sequence of integers.
Commonly used to identify standards, algorithms, certificate extensions,
organizations, or policy documents.
Fields
encoded: []const u8
Members
Source
//! Globally unique hierarchical identifier made of a sequence of integers.
//!
//! Commonly used to identify standards, algorithms, certificate extensions,
//! organizations, or policy documents.
encoded: []const u8,
pub const InitError = std.fmt.ParseIntError || error{MissingPrefix} || std.io.FixedBufferStream(u8).WriteError;
pub fn fromDot(dot_notation: []const u8, out: []u8) InitError!Oid {
var split = std.mem.splitScalar(u8, dot_notation, '.');
const first_str = split.next() orelse return error.MissingPrefix;
const second_str = split.next() orelse return error.MissingPrefix;
const first = try std.fmt.parseInt(u8, first_str, 10);
const second = try std.fmt.parseInt(u8, second_str, 10);
var stream = std.io.fixedBufferStream(out);
var writer = stream.writer();
try writer.writeByte(first * 40 + second);
var i: usize = 1;
while (split.next()) |s| {
var parsed = try std.fmt.parseUnsigned(Arc, s, 10);
const n_bytes = if (parsed == 0) 0 else std.math.log(Arc, encoding_base, parsed);
for (0..n_bytes) |j| {
const place = std.math.pow(Arc, encoding_base, n_bytes - @as(Arc, @intCast(j)));
const digit: u8 = @intCast(@divFloor(parsed, place));
try writer.writeByte(digit | 0x80);
parsed -= digit * place;
i += 1;
}
try writer.writeByte(@intCast(parsed));
i += 1;
}
return .{ .encoded = stream.getWritten() };
}
test fromDot {
var buf: [256]u8 = undefined;
for (test_cases) |t| {
const actual = try fromDot(t.dot_notation, &buf);
try std.testing.expectEqualSlices(u8, t.encoded, actual.encoded);
}
}
pub fn toDot(self: Oid, writer: anytype) @TypeOf(writer).Error!void {
const encoded = self.encoded;
const first = @divTrunc(encoded[0], 40);
const second = encoded[0] - first * 40;
try writer.print("{d}.{d}", .{ first, second });
var i: usize = 1;
while (i != encoded.len) {
const n_bytes: usize = brk: {
var res: usize = 1;
var j: usize = i;
while (encoded[j] & 0x80 != 0) {
res += 1;
j += 1;
}
break :brk res;
};
var n: usize = 0;
for (0..n_bytes) |j| {
const place = std.math.pow(usize, encoding_base, n_bytes - j - 1);
n += place * (encoded[i] & 0b01111111);
i += 1;
}
try writer.print(".{d}", .{n});
}
}
test toDot {
var buf: [256]u8 = undefined;
for (test_cases) |t| {
var stream = std.io.fixedBufferStream(&buf);
try toDot(Oid{ .encoded = t.encoded }, stream.writer());
try std.testing.expectEqualStrings(t.dot_notation, stream.getWritten());
}
}
const TestCase = struct {
encoded: []const u8,
dot_notation: []const u8,
pub fn init(comptime hex: []const u8, dot_notation: []const u8) TestCase {
return .{ .encoded = &hexToBytes(hex), .dot_notation = dot_notation };
}
};
const test_cases = [_]TestCase{
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
TestCase.init("2b0601040182371514", "1.3.6.1.4.1.311.21.20"),
// https://luca.ntop.org/Teaching/Appunti/asn1.html
TestCase.init("2a864886f70d", "1.2.840.113549"),
// https://www.sysadmins.lv/blog-en/how-to-encode-object-identifier-to-an-asn1-der-encoded-string.aspx
TestCase.init("2a868d20", "1.2.100000"),
TestCase.init("2a864886f70d01010b", "1.2.840.113549.1.1.11"),
TestCase.init("2b6570", "1.3.101.112"),
};
pub const asn1_tag = asn1.Tag.init(.oid, false, .universal);
pub fn decodeDer(decoder: *der.Decoder) !Oid {
const ele = try decoder.element(asn1_tag.toExpected());
return Oid{ .encoded = decoder.view(ele) };
}
pub fn encodeDer(self: Oid, encoder: *der.Encoder) !void {
try encoder.tagBytes(asn1_tag, self.encoded);
}
fn encodedLen(dot_notation: []const u8) usize {
var buf: [256]u8 = undefined;
const oid = fromDot(dot_notation, &buf) catch unreachable;
return oid.encoded.len;
}
/// Returns encoded bytes of OID.
fn encodeComptime(comptime dot_notation: []const u8) [encodedLen(dot_notation)]u8 {
@setEvalBranchQuota(4000);
comptime var buf: [256]u8 = undefined;
const oid = comptime fromDot(dot_notation, &buf) catch unreachable;
return oid.encoded[0..oid.encoded.len].*;
}
test encodeComptime {
try std.testing.expectEqual(
hexToBytes("2b0601040182371514"),
comptime encodeComptime("1.3.6.1.4.1.311.21.20"),
);
}
pub fn fromDotComptime(comptime dot_notation: []const u8) Oid {
const tmp = comptime encodeComptime(dot_notation);
return Oid{ .encoded = &tmp };
}
/// Maps of:
/// - Oid -> enum
/// - Enum -> oid
pub fn StaticMap(comptime Enum: type) type {
const enum_info = @typeInfo(Enum).@"enum";
const EnumToOid = std.EnumArray(Enum, []const u8);
const ReturnType = struct {
oid_to_enum: std.StaticStringMap(Enum),
enum_to_oid: EnumToOid,
pub fn oidToEnum(self: @This(), encoded: []const u8) ?Enum {
return self.oid_to_enum.get(encoded);
}
pub fn enumToOid(self: @This(), value: Enum) Oid {
const bytes = self.enum_to_oid.get(value);
return .{ .encoded = bytes };
}
};
return struct {
pub fn initComptime(comptime key_pairs: anytype) ReturnType {
const struct_info = @typeInfo(@TypeOf(key_pairs)).@"struct";
const error_msg = "Each field of '" ++ @typeName(Enum) ++ "' must map to exactly one OID";
if (!enum_info.is_exhaustive or enum_info.fields.len != struct_info.fields.len) {
@compileError(error_msg);
}
comptime var enum_to_oid = EnumToOid.initUndefined();
const KeyPair = struct { []const u8, Enum };
comptime var static_key_pairs: [enum_info.fields.len]KeyPair = undefined;
comptime for (enum_info.fields, 0..) |f, i| {
if (!@hasField(@TypeOf(key_pairs), f.name)) {
@compileError("Field '" ++ f.name ++ "' missing Oid.StaticMap entry");
}
const encoded = &encodeComptime(@field(key_pairs, f.name));
const tag: Enum = @enumFromInt(f.value);
static_key_pairs[i] = .{ encoded, tag };
enum_to_oid.set(tag, encoded);
};
const oid_to_enum = std.StaticStringMap(Enum).initComptime(static_key_pairs);
if (oid_to_enum.values().len != enum_info.fields.len) @compileError(error_msg);
return ReturnType{ .oid_to_enum = oid_to_enum, .enum_to_oid = enum_to_oid };
}
};
}
/// Strictly for testing.
fn hexToBytes(comptime hex: []const u8) [hex.len / 2]u8 {
var res: [hex.len / 2]u8 = undefined;
_ = std.fmt.hexToBytes(&res, hex) catch unreachable;
return res;
}
const std = @import("std");
const Oid = @This();
const Arc = u32;
const encoding_base = 128;
const Allocator = std.mem.Allocator;
const der = @import("der.zig");
const asn1 = @import("../asn1.zig");