structstd.crypto.Certificate.Bundle[src]

A set of certificates. Typically pre-installed on every operating system, these are "Certificate Authorities" used to validate SSL certificates. This data structure stores certificates in DER-encoded form, all of them concatenated together in the bytes array. The map field contains an index from the DER-encoded subject name to the index of the containing certificate within bytes. The key is the contents slice of the subject.

Fields

map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty

The key is the contents slice of the subject.

bytes: std.ArrayListUnmanaged(u8) = .empty

Error Sets

Error SetVerifyError[src]

Errors

anyerror means the error set is known only at runtime.

CertificateExpired VerifyError
CertificateFieldHasInvalidLength VerifyError
CertificateFieldHasWrongDataType VerifyError
CertificateIssuerMismatch VerifyError
CertificateIssuerNotFound
CertificateNotYetValid VerifyError
CertificatePublicKeyInvalid VerifyError
CertificateSignatureAlgorithmMismatch VerifyError
CertificateSignatureAlgorithmUnsupported VerifyError
CertificateSignatureInvalid VerifyError
CertificateSignatureInvalidLength VerifyError
CertificateSignatureNamedCurveUnsupported VerifyError
CertificateSignatureUnsupportedBitCount VerifyError

Source Code

Source code
pub const VerifyError = Certificate.Parsed.VerifyError || error{
    CertificateIssuerNotFound,
}

Error SetRescanError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference OpenError

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName OpenError

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

CertificateAuthorityBundleTooBig AddCertsFromFileError
CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
DeviceBusy OpenError
EndOfStream RescanMacError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InputOutput ReadError
InvalidCharacter Error
InvalidPadding Error
InvalidUtf8 OpenError

WASI-only; file paths must be valid UTF-8.

InvalidWtf8 OpenError

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

LockViolation ReadError

Unable to read file due to lock.

MissingEndCertificateMarker AddCertsFromFileError
NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound OpenError

On Windows, \\server or \\server\share was not found.

NoDevice OpenError
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

NotOpenForReading ReadError
OperationAborted ReadError
OutOfMemory Error
PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy OpenError
ProcessFdQuotaExceeded OpenError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SharingViolation OpenError
SocketNotConnected ReadError
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError
UnsupportedCertificateVersion ParseVersionError
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const RescanError = RescanLinuxError || RescanMacError || RescanBSDError || RescanWindowsError

Error SetAddCertsFromDirPathError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference OpenError

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName OpenError

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

CertificateAuthorityBundleTooBig AddCertsFromFileError
CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
DeviceBusy OpenError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InputOutput ReadError
InvalidCharacter Error
InvalidPadding Error
InvalidUtf8 OpenError

WASI-only; file paths must be valid UTF-8.

InvalidWtf8 OpenError

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

LockViolation ReadError

Unable to read file due to lock.

MissingEndCertificateMarker AddCertsFromFileError
NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound OpenError

On Windows, \\server or \\server\share was not found.

NoDevice OpenError
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

NotOpenForReading ReadError
OperationAborted ReadError
OutOfMemory Error
PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy OpenError
ProcessFdQuotaExceeded OpenError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SharingViolation OpenError
SocketNotConnected ReadError
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError
UnsupportedCertificateVersion ParseVersionError
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError

Error SetAddCertsFromFilePathError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference OpenError

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName OpenError

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

CertificateAuthorityBundleTooBig AddCertsFromFileError
CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
DeviceBusy OpenError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InputOutput ReadError
InvalidCharacter Error
InvalidPadding Error
InvalidUtf8 OpenError

WASI-only; file paths must be valid UTF-8.

InvalidWtf8 OpenError

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

LockViolation ReadError

Unable to read file due to lock.

MissingEndCertificateMarker AddCertsFromFileError
NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound OpenError

On Windows, \\server or \\server\share was not found.

NoDevice OpenError
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

NotOpenForReading ReadError
OperationAborted ReadError
OutOfMemory Error
PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy OpenError
ProcessFdQuotaExceeded OpenError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SharingViolation OpenError
SocketNotConnected ReadError
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError
UnsupportedCertificateVersion ParseVersionError
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError

Error SetAddCertsFromFilePathError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference OpenError

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName OpenError

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

CertificateAuthorityBundleTooBig AddCertsFromFileError
CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
DeviceBusy OpenError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InputOutput ReadError
InvalidCharacter Error
InvalidPadding Error
InvalidUtf8 OpenError

WASI-only; file paths must be valid UTF-8.

InvalidWtf8 OpenError

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

LockViolation ReadError

Unable to read file due to lock.

MissingEndCertificateMarker AddCertsFromFileError
NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound OpenError

On Windows, \\server or \\server\share was not found.

NoDevice OpenError
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

NotOpenForReading ReadError
OperationAborted ReadError
OutOfMemory Error
PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy OpenError
ProcessFdQuotaExceeded OpenError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SharingViolation OpenError
SocketNotConnected ReadError
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError
UnsupportedCertificateVersion ParseVersionError
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError

Error SetAddCertsFromFileError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied SeekError

In WASI, this error may occur when the file descriptor does not hold the required rights to seek on it.

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

CertificateAuthorityBundleTooBig
CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
InputOutput ReadError
InvalidCharacter Error
InvalidPadding Error
IsDir ReadError
LockViolation ReadError

Unable to read file due to lock.

MissingEndCertificateMarker
NoSpaceLeft Error
NotOpenForReading ReadError
OperationAborted ReadError
OutOfMemory Error
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SocketNotConnected ReadError
SystemResources FStatError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError
UnsupportedCertificateVersion ParseVersionError
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const AddCertsFromFileError = Allocator.Error ||
    fs.File.GetSeekPosError ||
    fs.File.ReadError ||
    ParseCertError ||
    std.base64.Error ||
    error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker }

Error SetParseCertError[src]

Errors

anyerror means the error set is known only at runtime.

CertificateFieldHasInvalidLength ParseError
CertificateFieldHasWrongDataType ParseTimeError
CertificateHasInvalidBitString ParseBitStringError
CertificateHasUnrecognizedObjectId ParseEnumError
CertificateTimeInvalid ParseTimeError
OutOfMemory Error
UnsupportedCertificateVersion ParseVersionError

Source Code

Source code
pub const ParseCertError = Allocator.Error || Certificate.ParseError

Functions

Functionverify[src]

pub fn verify(cb: Bundle, subject: Certificate.Parsed, now_sec: i64) VerifyError!void

Parameters

cb: Bundle
now_sec: i64

Source Code

Source code
pub fn verify(cb: Bundle, subject: Certificate.Parsed, now_sec: i64) VerifyError!void {
    const bytes_index = cb.find(subject.issuer()) orelse return error.CertificateIssuerNotFound;
    const issuer_cert: Certificate = .{
        .buffer = cb.bytes.items,
        .index = bytes_index,
    };
    // Every certificate in the bundle is pre-parsed before adding it, ensuring
    // that parsing will succeed here.
    const issuer = issuer_cert.parse() catch unreachable;
    try subject.verify(issuer, now_sec);
}

Functionfind[src]

pub fn find(cb: Bundle, subject_name: []const u8) ?u32

The returned bytes become invalid after calling any of the rescan functions or add functions.

Parameters

cb: Bundle
subject_name: []const u8

Functions

Functionhash[src]

pub fn hash(ctx: @This(), k: []const u8) u64

Parameters

ctx: @This()
k: []const u8

Source Code

Source code
pub fn hash(ctx: @This(), k: []const u8) u64 {
    _ = ctx;
    return std.hash_map.hashString(k);
}

Functioneql[src]

pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool

Parameters

ctx: @This()
a: []const u8

Source Code

Source code
pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool {
    const b = ctx.cb.bytes.items[b_key.start..b_key.end];
    return mem.eql(u8, a, b);
}

Source Code

Source code
pub fn find(cb: Bundle, subject_name: []const u8) ?u32 {
    const Adapter = struct {
        cb: Bundle,

        pub fn hash(ctx: @This(), k: []const u8) u64 {
            _ = ctx;
            return std.hash_map.hashString(k);
        }

        pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool {
            const b = ctx.cb.bytes.items[b_key.start..b_key.end];
            return mem.eql(u8, a, b);
        }
    };
    return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
}

Functiondeinit[src]

pub fn deinit(cb: *Bundle, gpa: Allocator) void

Parameters

cb: *Bundle

Source Code

Source code
pub fn deinit(cb: *Bundle, gpa: Allocator) void {
    cb.map.deinit(gpa);
    cb.bytes.deinit(gpa);
    cb.* = undefined;
}

Functionrescan[src]

pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void

Clears the set of certificates and then scans the host operating system file system standard locations for certificates. For operating systems that do not have standard CA installations to be found, this function clears the set of certificates.

Parameters

cb: *Bundle

Source Code

Source code
pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void {
    switch (builtin.os.tag) {
        .linux => return rescanLinux(cb, gpa),
        .macos => return rescanMac(cb, gpa),
        .freebsd, .openbsd => return rescanBSD(cb, gpa, "/etc/ssl/cert.pem"),
        .netbsd => return rescanBSD(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),
        .dragonfly => return rescanBSD(cb, gpa, "/usr/local/etc/ssl/cert.pem"),
        .solaris, .illumos => return rescanBSD(cb, gpa, "/etc/ssl/cacert.pem"),
        .windows => return rescanWindows(cb, gpa),
        else => {},
    }
}

FunctionaddCertsFromDirPath[src]

pub fn addCertsFromDirPath( cb: *Bundle, gpa: Allocator, dir: fs.Dir, sub_dir_path: []const u8, ) AddCertsFromDirPathError!void

Parameters

cb: *Bundle
dir: fs.Dir
sub_dir_path: []const u8

Source Code

Source code
pub fn addCertsFromDirPath(
    cb: *Bundle,
    gpa: Allocator,
    dir: fs.Dir,
    sub_dir_path: []const u8,
) AddCertsFromDirPathError!void {
    var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
    defer iterable_dir.close();
    return addCertsFromDir(cb, gpa, iterable_dir);
}

FunctionaddCertsFromDirPathAbsolute[src]

pub fn addCertsFromDirPathAbsolute( cb: *Bundle, gpa: Allocator, abs_dir_path: []const u8, ) AddCertsFromDirPathError!void

Parameters

cb: *Bundle
abs_dir_path: []const u8

Source Code

Source code
pub fn addCertsFromDirPathAbsolute(
    cb: *Bundle,
    gpa: Allocator,
    abs_dir_path: []const u8,
) AddCertsFromDirPathError!void {
    assert(fs.path.isAbsolute(abs_dir_path));
    var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
    defer iterable_dir.close();
    return addCertsFromDir(cb, gpa, iterable_dir);
}

FunctionaddCertsFromDir[src]

pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void

Parameters

cb: *Bundle
iterable_dir: fs.Dir

Source Code

Source code
pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {
    var it = iterable_dir.iterate();
    while (try it.next()) |entry| {
        switch (entry.kind) {
            .file, .sym_link => {},
            else => continue,
        }

        try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);
    }
}

FunctionaddCertsFromFilePathAbsolute[src]

pub fn addCertsFromFilePathAbsolute( cb: *Bundle, gpa: Allocator, abs_file_path: []const u8, ) AddCertsFromFilePathError!void

Parameters

cb: *Bundle
abs_file_path: []const u8

Source Code

Source code
pub fn addCertsFromFilePathAbsolute(
    cb: *Bundle,
    gpa: Allocator,
    abs_file_path: []const u8,
) AddCertsFromFilePathError!void {
    assert(fs.path.isAbsolute(abs_file_path));
    var file = try fs.openFileAbsolute(abs_file_path, .{});
    defer file.close();
    return addCertsFromFile(cb, gpa, file);
}

FunctionaddCertsFromFilePath[src]

pub fn addCertsFromFilePath( cb: *Bundle, gpa: Allocator, dir: fs.Dir, sub_file_path: []const u8, ) AddCertsFromFilePathError!void

Parameters

cb: *Bundle
dir: fs.Dir
sub_file_path: []const u8

Source Code

Source code
pub fn addCertsFromFilePath(
    cb: *Bundle,
    gpa: Allocator,
    dir: fs.Dir,
    sub_file_path: []const u8,
) AddCertsFromFilePathError!void {
    var file = try dir.openFile(sub_file_path, .{});
    defer file.close();
    return addCertsFromFile(cb, gpa, file);
}

FunctionaddCertsFromFile[src]

pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void

Parameters

cb: *Bundle
file: fs.File

Source Code

Source code
pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {
    const size = try file.getEndPos();

    // We borrow `bytes` as a temporary buffer for the base64-encoded data.
    // This is possible by computing the decoded length and reserving the space
    // for the decoded bytes first.
    const decoded_size_upper_bound = size / 4 * 3;
    const needed_capacity = std.math.cast(u32, decoded_size_upper_bound + size) orelse
        return error.CertificateAuthorityBundleTooBig;
    try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
    const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
    const buffer = cb.bytes.allocatedSlice()[end_reserved..];
    const end_index = try file.readAll(buffer);
    const encoded_bytes = buffer[0..end_index];

    const begin_marker = "-----BEGIN CERTIFICATE-----";
    const end_marker = "-----END CERTIFICATE-----";

    const now_sec = std.time.timestamp();

    var start_index: usize = 0;
    while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
        const cert_start = begin_marker_start + begin_marker.len;
        const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
            return error.MissingEndCertificateMarker;
        start_index = cert_end + end_marker.len;
        const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
        const decoded_start: u32 = @intCast(cb.bytes.items.len);
        const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
        cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
        try cb.parseCert(gpa, decoded_start, now_sec);
    }
}

FunctionparseCert[src]

pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) ParseCertError!void

Parameters

cb: *Bundle
decoded_start: u32
now_sec: i64

Source Code

Source code
pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) ParseCertError!void {
    // Even though we could only partially parse the certificate to find
    // the subject name, we pre-parse all of them to make sure and only
    // include in the bundle ones that we know will parse. This way we can
    // use `catch unreachable` later.
    const parsed_cert = Certificate.parse(.{
        .buffer = cb.bytes.items,
        .index = decoded_start,
    }) catch |err| switch (err) {
        error.CertificateHasUnrecognizedObjectId => {
            cb.bytes.items.len = decoded_start;
            return;
        },
        else => |e| return e,
    };
    if (now_sec > parsed_cert.validity.not_after) {
        // Ignore expired cert.
        cb.bytes.items.len = decoded_start;
        return;
    }
    const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
    if (gop.found_existing) {
        cb.bytes.items.len = decoded_start;
    } else {
        gop.value_ptr.* = decoded_start;
    }
}

Source Code

Source code
//! A set of certificates. Typically pre-installed on every operating system,
//! these are "Certificate Authorities" used to validate SSL certificates.
//! This data structure stores certificates in DER-encoded form, all of them
//! concatenated together in the `bytes` array. The `map` field contains an
//! index from the DER-encoded subject name to the index of the containing
//! certificate within `bytes`.

/// The key is the contents slice of the subject.
map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,
bytes: std.ArrayListUnmanaged(u8) = .empty,

pub const VerifyError = Certificate.Parsed.VerifyError || error{
    CertificateIssuerNotFound,
};

pub fn verify(cb: Bundle, subject: Certificate.Parsed, now_sec: i64) VerifyError!void {
    const bytes_index = cb.find(subject.issuer()) orelse return error.CertificateIssuerNotFound;
    const issuer_cert: Certificate = .{
        .buffer = cb.bytes.items,
        .index = bytes_index,
    };
    // Every certificate in the bundle is pre-parsed before adding it, ensuring
    // that parsing will succeed here.
    const issuer = issuer_cert.parse() catch unreachable;
    try subject.verify(issuer, now_sec);
}

/// The returned bytes become invalid after calling any of the rescan functions
/// or add functions.
pub fn find(cb: Bundle, subject_name: []const u8) ?u32 {
    const Adapter = struct {
        cb: Bundle,

        pub fn hash(ctx: @This(), k: []const u8) u64 {
            _ = ctx;
            return std.hash_map.hashString(k);
        }

        pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool {
            const b = ctx.cb.bytes.items[b_key.start..b_key.end];
            return mem.eql(u8, a, b);
        }
    };
    return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
}

pub fn deinit(cb: *Bundle, gpa: Allocator) void {
    cb.map.deinit(gpa);
    cb.bytes.deinit(gpa);
    cb.* = undefined;
}

pub const RescanError = RescanLinuxError || RescanMacError || RescanBSDError || RescanWindowsError;

/// Clears the set of certificates and then scans the host operating system
/// file system standard locations for certificates.
/// For operating systems that do not have standard CA installations to be
/// found, this function clears the set of certificates.
pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void {
    switch (builtin.os.tag) {
        .linux => return rescanLinux(cb, gpa),
        .macos => return rescanMac(cb, gpa),
        .freebsd, .openbsd => return rescanBSD(cb, gpa, "/etc/ssl/cert.pem"),
        .netbsd => return rescanBSD(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),
        .dragonfly => return rescanBSD(cb, gpa, "/usr/local/etc/ssl/cert.pem"),
        .solaris, .illumos => return rescanBSD(cb, gpa, "/etc/ssl/cacert.pem"),
        .windows => return rescanWindows(cb, gpa),
        else => {},
    }
}

const rescanMac = @import("Bundle/macos.zig").rescanMac;
const RescanMacError = @import("Bundle/macos.zig").RescanMacError;

const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;

fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
    // Possible certificate files; stop after finding one.
    const cert_file_paths = [_][]const u8{
        "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
        "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
        "/etc/ssl/ca-bundle.pem", // OpenSUSE
        "/etc/pki/tls/cacert.pem", // OpenELEC
        "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
        "/etc/ssl/cert.pem", // Alpine Linux
    };

    // Possible directories with certificate files; all will be read.
    const cert_dir_paths = [_][]const u8{
        "/etc/ssl/certs", // SLES10/SLES11
        "/etc/pki/tls/certs", // Fedora/RHEL
        "/system/etc/security/cacerts", // Android
    };

    cb.bytes.clearRetainingCapacity();
    cb.map.clearRetainingCapacity();

    scan: {
        for (cert_file_paths) |cert_file_path| {
            if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| {
                break :scan;
            } else |err| switch (err) {
                error.FileNotFound => continue,
                else => |e| return e,
            }
        }

        for (cert_dir_paths) |cert_dir_path| {
            addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) {
                error.FileNotFound => continue,
                else => |e| return e,
            };
        }
    }

    cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
}

const RescanBSDError = AddCertsFromFilePathError;

fn rescanBSD(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanBSDError!void {
    cb.bytes.clearRetainingCapacity();
    cb.map.clearRetainingCapacity();
    try addCertsFromFilePathAbsolute(cb, gpa, cert_file_path);
    cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
}

const RescanWindowsError = Allocator.Error || ParseCertError || std.posix.UnexpectedError || error{FileNotFound};

fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
    cb.bytes.clearRetainingCapacity();
    cb.map.clearRetainingCapacity();

    const w = std.os.windows;
    const GetLastError = w.GetLastError;
    const root = [4:0]u16{ 'R', 'O', 'O', 'T' };
    const store = w.crypt32.CertOpenSystemStoreW(null, &root) orelse switch (GetLastError()) {
        .FILE_NOT_FOUND => return error.FileNotFound,
        else => |err| return w.unexpectedError(err),
    };
    defer _ = w.crypt32.CertCloseStore(store, 0);

    const now_sec = std.time.timestamp();

    var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);
    while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {
        const decoded_start = @as(u32, @intCast(cb.bytes.items.len));
        const encoded_cert = context.pbCertEncoded[0..context.cbCertEncoded];
        try cb.bytes.appendSlice(gpa, encoded_cert);
        try cb.parseCert(gpa, decoded_start, now_sec);
    }
    cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
}

pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;

pub fn addCertsFromDirPath(
    cb: *Bundle,
    gpa: Allocator,
    dir: fs.Dir,
    sub_dir_path: []const u8,
) AddCertsFromDirPathError!void {
    var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
    defer iterable_dir.close();
    return addCertsFromDir(cb, gpa, iterable_dir);
}

pub fn addCertsFromDirPathAbsolute(
    cb: *Bundle,
    gpa: Allocator,
    abs_dir_path: []const u8,
) AddCertsFromDirPathError!void {
    assert(fs.path.isAbsolute(abs_dir_path));
    var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
    defer iterable_dir.close();
    return addCertsFromDir(cb, gpa, iterable_dir);
}

pub const AddCertsFromDirError = AddCertsFromFilePathError;

pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {
    var it = iterable_dir.iterate();
    while (try it.next()) |entry| {
        switch (entry.kind) {
            .file, .sym_link => {},
            else => continue,
        }

        try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);
    }
}

pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError;

pub fn addCertsFromFilePathAbsolute(
    cb: *Bundle,
    gpa: Allocator,
    abs_file_path: []const u8,
) AddCertsFromFilePathError!void {
    assert(fs.path.isAbsolute(abs_file_path));
    var file = try fs.openFileAbsolute(abs_file_path, .{});
    defer file.close();
    return addCertsFromFile(cb, gpa, file);
}

pub fn addCertsFromFilePath(
    cb: *Bundle,
    gpa: Allocator,
    dir: fs.Dir,
    sub_file_path: []const u8,
) AddCertsFromFilePathError!void {
    var file = try dir.openFile(sub_file_path, .{});
    defer file.close();
    return addCertsFromFile(cb, gpa, file);
}

pub const AddCertsFromFileError = Allocator.Error ||
    fs.File.GetSeekPosError ||
    fs.File.ReadError ||
    ParseCertError ||
    std.base64.Error ||
    error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };

pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {
    const size = try file.getEndPos();

    // We borrow `bytes` as a temporary buffer for the base64-encoded data.
    // This is possible by computing the decoded length and reserving the space
    // for the decoded bytes first.
    const decoded_size_upper_bound = size / 4 * 3;
    const needed_capacity = std.math.cast(u32, decoded_size_upper_bound + size) orelse
        return error.CertificateAuthorityBundleTooBig;
    try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
    const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
    const buffer = cb.bytes.allocatedSlice()[end_reserved..];
    const end_index = try file.readAll(buffer);
    const encoded_bytes = buffer[0..end_index];

    const begin_marker = "-----BEGIN CERTIFICATE-----";
    const end_marker = "-----END CERTIFICATE-----";

    const now_sec = std.time.timestamp();

    var start_index: usize = 0;
    while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
        const cert_start = begin_marker_start + begin_marker.len;
        const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
            return error.MissingEndCertificateMarker;
        start_index = cert_end + end_marker.len;
        const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
        const decoded_start: u32 = @intCast(cb.bytes.items.len);
        const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
        cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
        try cb.parseCert(gpa, decoded_start, now_sec);
    }
}

pub const ParseCertError = Allocator.Error || Certificate.ParseError;

pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) ParseCertError!void {
    // Even though we could only partially parse the certificate to find
    // the subject name, we pre-parse all of them to make sure and only
    // include in the bundle ones that we know will parse. This way we can
    // use `catch unreachable` later.
    const parsed_cert = Certificate.parse(.{
        .buffer = cb.bytes.items,
        .index = decoded_start,
    }) catch |err| switch (err) {
        error.CertificateHasUnrecognizedObjectId => {
            cb.bytes.items.len = decoded_start;
            return;
        },
        else => |e| return e,
    };
    if (now_sec > parsed_cert.validity.not_after) {
        // Ignore expired cert.
        cb.bytes.items.len = decoded_start;
        return;
    }
    const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
    if (gop.found_existing) {
        cb.bytes.items.len = decoded_start;
    } else {
        gop.value_ptr.* = decoded_start;
    }
}

const builtin = @import("builtin");
const std = @import("../../std.zig");
const assert = std.debug.assert;
const fs = std.fs;
const mem = std.mem;
const crypto = std.crypto;
const Allocator = std.mem.Allocator;
const Certificate = std.crypto.Certificate;
const der = Certificate.der;
const Bundle = @This();

const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");

const MapContext = struct {
    cb: *const Bundle,

    pub fn hash(ctx: MapContext, k: der.Element.Slice) u64 {
        return std.hash_map.hashString(ctx.cb.bytes.items[k.start..k.end]);
    }

    pub fn eql(ctx: MapContext, a: der.Element.Slice, b: der.Element.Slice) bool {
        const bytes = ctx.cb.bytes.items;
        return mem.eql(
            u8,
            bytes[a.start..a.end],
            bytes[b.start..b.end],
        );
    }
};

test "scan for OS-provided certificates" {
    if (builtin.os.tag == .wasi) return error.SkipZigTest;

    var bundle: Bundle = .{};
    defer bundle.deinit(std.testing.allocator);

    try bundle.rescan(std.testing.allocator);
}