Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std.crypto: Add ASN1 module with OIDs and DER #19976

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

clickingbuttons
Copy link
Contributor

Add module for mapping ASN1 types to Zig types. See asn1.Tag.fromZig for the mapping. Add DER encoder and decoder.

See asn1/test.zig for example usage of every ASN1 type.

This implementation allows ASN1 tags to be overriden with asn1_tag and asn1_tags:

const MyContainer = (enum | union | struct) {
    field: u32,

    pub const asn1_tag = asn1.Tag.init(...);

    // This specifies a tag's class, and if explicit, additional encoding
    // rules.
    pub const asn1_tags = .{
        .field = asn1.FieldTag.explicit(0, .context_specific),
    };
};

Despite having an enum tag type, ASN1 frequently uses OIDs as enum values. This is supported via an pub const oids field.

const MyEnum = enum {
    a,

    pub const oids = asn1.Oid.StaticMap(MyEnum).initComptime(.{
        .a = "1.2.3.4",
    });
};

Futhermore, a container may choose to implement encoding and decoding however it deems fit. This allows for derived fields since Zig has a far more powerful type system than ASN1.

// ASN1 has no standard way of tagging unions.
const MyContainer = union(enum) {
    derived: PowerfulZigType,

    const WeakAsn1Type = ...;

    pub fn encodeDer(self: MyContainer, encoder: *der.Encoder) !void {
        try encoder.any(WeakAsn1Type{...});
    }

    pub fn decodeDer(decoder: *der.Decoder) !MyContainer {
        const weak_asn1_type = try decoder.any(WeakAsn1Type);
        return .{ .derived = PowerfulZigType{...} };
    }
};

An unfortunate side-effect is that decoding and encoding cannot have complete complete error sets unless we limit what errors users may return. Luckily, PKI ASN1 types are NOT recursive so the inferred error set should be sufficient.

Finally, other encodings are possible, but this patch only implements a buffered DER encoder and decoder.

In an effort to keep the changeset minimal this PR does not actually use the DER parser for stdlib PKI, but a tested example of how it may be used for Certificate is available here.

Closes #19775.

Add module for mapping ASN1 types to Zig types. See
`asn1.Tag.fromZig` for the mapping. Add DER encoder and decoder.

See `asn1/test.zig` for example usage of every ASN1 type.

This implementation allows ASN1 tags to be overriden with `asn1_tag`
and `asn1_tags`:
```zig
const MyContainer = (enum | union | struct) {
    field: u32,

    pub const asn1_tag = asn1.Tag.init(...);

    // This specifies a tag's class, and if explicit, additional encoding
    // rules.
    pub const asn1_tags = .{
        .field = asn1.FieldTag.explicit(0, .context_specific),
    };
};
```

Despite having an enum tag type, ASN1 frequently uses OIDs as enum
values. This is supported via an `pub const oids` field.
```zig
const MyEnum = enum {
    a,

    pub const oids = asn1.Oid.StaticMap(MyEnum).initComptime(.{
        .a = "1.2.3.4",
    });
};
```

Futhermore, a container may choose to implement encoding and decoding
however it deems fit. This allows for derived fields since Zig has a far
more powerful type system than ASN1.
```zig
// ASN1 has no standard way of tagging unions.
const MyContainer = union(enum) {
    derived: PowerfulZigType,

    const WeakAsn1Type = ...;

    pub fn encodeDer(self: MyContainer, encoder: *der.Encoder) !void {
        try encoder.any(WeakAsn1Type{...});
    }

    pub fn decodeDer(decoder: *der.Decoder) !MyContainer {
        const weak_asn1_type = try decoder.any(WeakAsn1Type);
        return .{ .derived = PowerfulZigType{...} };
    }
};
```
An unfortunate side-effect is that decoding and encoding cannot have
complete complete error sets unless we limit what errors users may
return. Luckily, PKI ASN1 types are NOT recursive so the inferred
error set should be sufficient.

Finally, other encodings are possible, but this patch only implements
a buffered DER encoder and decoder.

In an effort to keep the changeset minimal this PR does not actually
use the DER parser for stdlib PKI, but a tested example of how it may
be used for Certificate is available
[here.](https://github.com/clickingbuttons/asn1/blob/69c5709d/src/Certificate.zig)

Closes ziglang#19775.
@jedisct1
Copy link
Contributor

Looks very nice.

Proper documentation and exemples for all public function would be very useful to understand how to use this.

@jedisct1
Copy link
Contributor

jedisct1 commented May 15, 2024

I'd love to use this to get rid of that horror :)

@clickingbuttons
Copy link
Contributor Author

clickingbuttons commented May 16, 2024

asn1/test.zig is the user's manual.

I've added a few comments and a der.encode and der.decode test.

Let me know where you believe the documentation to be insufficient and I can write some.

Edit: I've also added some lapo.it URLs for two example tests. This web tool is useful for interactively inspecting DER.

@clickingbuttons
Copy link
Contributor Author

@jedisct1 Ping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

std.crypto: Add secure DER parser
2 participants