mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-14 20:00:31 +00:00
add limbo
This commit is contained in:
@@ -2,6 +2,7 @@ package x509
|
||||
|
||||
import "core:bytes"
|
||||
import "core:encoding/asn1"
|
||||
import "core:strings"
|
||||
|
||||
// Name-constraint processing (RFC 5280 section 4.2.1.10 + the section 6.1.4
|
||||
// path-validation checks), scoped to the dNSName and iPAddress GeneralName
|
||||
@@ -242,10 +243,10 @@ _nc_dns_match :: proc(nc: []byte, excluded: bool, name: string) -> bool {
|
||||
if excluded {
|
||||
// Fail closed: exclude the wildcard if its subtree overlaps
|
||||
// the constraint at all (constraint covers b, or b covers it).
|
||||
if _dns_in_constraint(c, b) || _dns_in_constraint(b, _strip_dot(c)) {
|
||||
if _dns_in_constraint(c, b) || _dns_in_constraint(b, strings.trim_prefix(c, ".")) {
|
||||
return true
|
||||
}
|
||||
} else if _dns_in_constraint(_strip_dot(c), b) {
|
||||
} else if _dns_in_constraint(strings.trim_prefix(c, "."), b) {
|
||||
// Permitted only if every child of b is inside the subtree.
|
||||
return true
|
||||
}
|
||||
@@ -257,13 +258,6 @@ _nc_dns_match :: proc(nc: []byte, excluded: bool, name: string) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// _strip_dot drops a single leading '.', normalizing a subtree constraint to a
|
||||
// bare domain for the wildcard-overlap comparisons above.
|
||||
@(private)
|
||||
_strip_dot :: proc "contextless" (s: string) -> string {
|
||||
return s[1:] if len(s) > 0 && s[0] == '.' else s
|
||||
}
|
||||
|
||||
// _nc_ip_match reports whether `ip` (a 4- or 16-octet address) matches any
|
||||
// iPAddress subtree in the given section. In NameConstraints an iPAddress
|
||||
// base is the address followed by a mask of equal length (8 octets for IPv4,
|
||||
@@ -332,9 +326,13 @@ _dns_in_constraint :: proc "contextless" (constraint, name: string) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// _ascii_eq_fold / _ascii_suffix_fold are the package's DNS-name comparison
|
||||
// primitives, shared with verify_hostname. dNSNames are IA5String (ASCII) and
|
||||
// IDNs are carried as punycode A-labels. Do not replace with strings.equal_fold.
|
||||
@(private)
|
||||
_ascii_lower :: proc "contextless" (b: byte) -> byte {
|
||||
return b + 0x20 if b >= 'A' && b <= 'Z' else b
|
||||
if b >= 'A' && b <= 'Z' { return b+0x20 }
|
||||
else { return b }
|
||||
}
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -6,6 +6,7 @@ import "core:crypto/ed25519"
|
||||
import "core:crypto/hash"
|
||||
import "core:crypto/rsa"
|
||||
import "core:net"
|
||||
import "core:slice"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
|
||||
@@ -80,8 +81,10 @@ _match_hostname :: proc(pattern, host: string) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DNS comparisons are ASCII case-insensitive (RFC 4343): dNSNames are
|
||||
// IA5String and IDNs travel as punycode A-labels, so `_ascii_eq_fold`is used.
|
||||
if !strings.has_prefix(p, "*.") {
|
||||
return strings.equal_fold(p, host)
|
||||
return _ascii_eq_fold(p, host)
|
||||
}
|
||||
|
||||
// Wildcard: "*." + base. The host's first label is consumed by the wildcard; the
|
||||
@@ -100,7 +103,7 @@ _match_hostname :: proc(pattern, host: string) -> bool {
|
||||
if strings.index_byte(base, '*') >= 0 {
|
||||
return false
|
||||
}
|
||||
return strings.equal_fold(base, rest)
|
||||
return _ascii_eq_fold(base, rest)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -406,7 +409,7 @@ _build_to_anchor :: proc(
|
||||
|
||||
// Otherwise extend through an untrusted intermediate and recurse.
|
||||
for inter in opts.intermediates {
|
||||
if inter == cert || _in_chain(acc, inter) {
|
||||
if inter == cert || slice.contains(acc[:], inter) {
|
||||
continue
|
||||
}
|
||||
if !_is_issuer_of(inter, cert) || !_issuer_usable(inter, opts.current_time, below) {
|
||||
@@ -528,13 +531,3 @@ _permits_eku :: proc(cert: ^Certificate, ask: EKU_Bit) -> bool {
|
||||
}
|
||||
return ask in cert.ext_key_usage
|
||||
}
|
||||
|
||||
@(private)
|
||||
_in_chain :: proc(acc: ^[dynamic]^Certificate, cert: ^Certificate) -> bool {
|
||||
for c in acc {
|
||||
if c == cert {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
1
tests/core/assets/X509-Limbo/.gitignore
vendored
Normal file
1
tests/core/assets/X509-Limbo/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.json
|
||||
46
tests/core/crypto/x509_limbo/README.md
Normal file
46
tests/core/crypto/x509_limbo/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
### x509-limbo — core/crypto/x509 path-validation conformance
|
||||
|
||||
Differential-tests `core:crypto/x509` `verify_chain` against the
|
||||
[x509-limbo][1] corpus (~9.8k adversarial path-validation testcases,
|
||||
~30k certificates). Structured like `tests/core/crypto/wycheproof`: a
|
||||
dedicated package whose corpus lives, gitignored, under
|
||||
`tests/core/assets/`.
|
||||
|
||||
Fetch the corpus:
|
||||
|
||||
```
|
||||
curl -sL https://raw.githubusercontent.com/C2SP/x509-limbo/main/limbo.json \
|
||||
-o tests/core/assets/X509-Limbo/limbo.json
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```
|
||||
odin test tests/core/crypto/x509_limbo -o:speed
|
||||
```
|
||||
|
||||
When the corpus is absent the suite logs a notice and passes, so a plain
|
||||
`odin test` sweep never breaks.
|
||||
|
||||
Each case carries its own `expected_result`, so the harness needs no
|
||||
external oracle. Reviewed, documented divergences are listed by testcase id
|
||||
in `allow.odin`:
|
||||
|
||||
- `ACCEPT_ALLOW` — cases limbo marks FAILURE that we accept. All are policy
|
||||
layers outside RFC 5280 path validation that this package intentionally
|
||||
does not enforce (revocation, CABF Baseline Requirements issuance
|
||||
conformance, per-extension criticality/presence policy, lenient
|
||||
serials/dNSName syntax, name-constraint DoS bailout).
|
||||
- `REJECT_ALLOW` — cases limbo accepts that we reject (the safe, stricter
|
||||
direction), all using a name-constraint form we fail closed on
|
||||
(directoryName / rfc822Name / otherName).
|
||||
|
||||
Any diverging case NOT allow-listed fails the test — the on-change
|
||||
regression gate, in particular for the security-critical "we accept a chain
|
||||
limbo rejects" direction. When bumping the corpus, run once, review each new
|
||||
divergence, and add it to the appropriate list (or fix the finding).
|
||||
|
||||
Covered: RSA (PKCS#1 v1.5 + PSS), ECDSA P-256/P-384, Ed25519, and dNSName /
|
||||
iPAddress name constraints. Chains using P-521 / Ed448 / DSA are skipped.
|
||||
|
||||
[1]: https://github.com/C2SP/x509-limbo
|
||||
84
tests/core/crypto/x509_limbo/allow.odin
Normal file
84
tests/core/crypto/x509_limbo/allow.odin
Normal file
@@ -0,0 +1,84 @@
|
||||
package test_x509_limbo
|
||||
|
||||
// Reviewed & documented divergences from the x509-limbo corpus, keyed by
|
||||
// testcase id. Any diverging case NOT listed here is treated as a REGRESSION
|
||||
// and fails the test (see x509_limbo.odin).
|
||||
//
|
||||
// ACCEPT_ALLOW: cases limbo marks FAILURE that we ACCEPT. Every one is a policy
|
||||
// layer OUTSIDE RFC 5280 path validation that this package intentionally does
|
||||
// not enforce — revocation (crl::*), CABF Baseline Requirements issuance
|
||||
// conformance (webpki::cn/eku/forbidden-rsa/san/aki, ca-as-leaf), per-extension
|
||||
// criticality and presence policy (aki/ski/pc/san, nc must-be-critical), lenient
|
||||
// serials / dNSName syntax, and the name-constraint DoS bailout. None is a
|
||||
// path-validation integrity bug (no accepted bad signature or broken chain).
|
||||
ACCEPT_ALLOW := []string {
|
||||
"crl::crlnumber-critical",
|
||||
"crl::crlnumber-missing",
|
||||
"crl::issuer-missing-crlsign",
|
||||
"crl::revoked-certificate-with-crl",
|
||||
"pathlen::max-chain-depth-0-exhausted",
|
||||
"pathlen::max-chain-depth-1-exhausted",
|
||||
"pathological::nc-dos-1",
|
||||
"pathological::nc-dos-2",
|
||||
"rfc5280::aki::critical-aki",
|
||||
"rfc5280::aki::cross-signed-root-missing-aki",
|
||||
"rfc5280::aki::intermediate-missing-aki",
|
||||
"rfc5280::aki::leaf-missing-aki",
|
||||
"rfc5280::ca-empty-subject",
|
||||
"rfc5280::leaf-ku-keycertsign",
|
||||
"rfc5280::nc::invalid-dnsname-leading-period",
|
||||
"rfc5280::nc::permitted-dns-match-noncritical",
|
||||
"rfc5280::pc::ica-noncritical-pc",
|
||||
"rfc5280::root-inconsistent-ca-extensions",
|
||||
"rfc5280::root-missing-basic-constraints",
|
||||
"rfc5280::root-non-critical-basic-constraints",
|
||||
"rfc5280::san::noncritical-with-empty-subject",
|
||||
"rfc5280::san::underscore-dns",
|
||||
"rfc5280::serial::too-long",
|
||||
"rfc5280::serial::zero",
|
||||
"rfc5280::ski::critical-ski",
|
||||
"rfc5280::ski::intermediate-missing-ski",
|
||||
"rfc5280::ski::root-missing-ski",
|
||||
"webpki::aki::root-with-aki-all-fields",
|
||||
"webpki::aki::root-with-aki-authoritycertissuer",
|
||||
"webpki::aki::root-with-aki-authoritycertserialnumber",
|
||||
"webpki::aki::root-with-aki-missing-keyidentifier",
|
||||
"webpki::aki::root-with-aki-ski-mismatch",
|
||||
"webpki::ca-as-leaf",
|
||||
"webpki::cn::case-mismatch",
|
||||
"webpki::cn::ipv4-hex-mismatch",
|
||||
"webpki::cn::ipv4-leading-zeros-mismatch",
|
||||
"webpki::cn::ipv6-non-rfc5952-mismatch",
|
||||
"webpki::cn::ipv6-uncompressed-mismatch",
|
||||
"webpki::cn::ipv6-uppercase-mismatch",
|
||||
"webpki::cn::not-in-san",
|
||||
"webpki::cn::punycode-not-in-san",
|
||||
"webpki::cn::utf8-vs-punycode-mismatch",
|
||||
"webpki::ee-basicconstraints-ca",
|
||||
"webpki::eku::ee-anyeku",
|
||||
"webpki::eku::ee-critical-eku",
|
||||
"webpki::eku::ee-without-eku",
|
||||
"webpki::eku::root-has-eku",
|
||||
"webpki::forbidden-rsa-key-not-divisible-by-8-in-leaf",
|
||||
"webpki::forbidden-rsa-not-divisible-by-8-in-root",
|
||||
"webpki::forbidden-weak-rsa-in-leaf",
|
||||
"webpki::forbidden-weak-rsa-key-in-root",
|
||||
"webpki::malformed-aia",
|
||||
"webpki::san::public-suffix-multi-label-wildcard-san",
|
||||
"webpki::san::public-suffix-private-namespace-wildcard-san",
|
||||
"webpki::san::public-suffix-wildcard-san",
|
||||
"webpki::san::san-critical-with-nonempty-subject",
|
||||
}
|
||||
|
||||
// REJECT_ALLOW: cases limbo ACCEPTS that we REJECT.
|
||||
// All use a name-constraint GeneralName form we fail closed on:
|
||||
// directoryName, rfc822Name (email), or otherName (These types are not implemented)
|
||||
REJECT_ALLOW := []string {
|
||||
"rfc5280::nc::nc-forbids-othername-noop",
|
||||
"rfc5280::nc::nc-permits-email-domain",
|
||||
"rfc5280::nc::nc-permits-email-exact",
|
||||
"rfc5280::nc::nc-permits-email-literal-asterisk-exact-match",
|
||||
"rfc5280::nc::nc-permits-email-literal-double-asterisk",
|
||||
"rfc5280::nc::nc-permits-email-literal-mid-asterisk",
|
||||
"rfc5280::nc::permitted-dn-match",
|
||||
}
|
||||
39
tests/core/crypto/x509_limbo/schemas.odin
Normal file
39
tests/core/crypto/x509_limbo/schemas.odin
Normal file
@@ -0,0 +1,39 @@
|
||||
package test_x509_limbo
|
||||
|
||||
import "core:encoding/json"
|
||||
import "core:os"
|
||||
|
||||
// The subset of the C2SP x509-limbo limbo.json schema this harness consumes.
|
||||
// See https://github.com/C2SP/x509-limbo.
|
||||
|
||||
Limbo :: struct {
|
||||
testcases: []Testcase `json:"testcases"`,
|
||||
}
|
||||
|
||||
Testcase :: struct {
|
||||
id: string `json:"id"`,
|
||||
expected_result: string `json:"expected_result"`, // "SUCCESS" | "FAILURE"
|
||||
validation_kind: string `json:"validation_kind"`, // "SERVER" | "CLIENT" | ...
|
||||
validation_time: string `json:"validation_time"`, // RFC 3339, or "" when null
|
||||
peer_certificate: string `json:"peer_certificate"`, // the leaf, PEM
|
||||
untrusted_intermediates: []string `json:"untrusted_intermediates"`, // PEM
|
||||
trusted_certs: []string `json:"trusted_certs"`, // the roots, PEM
|
||||
expected_peer_name: Peer_Name `json:"expected_peer_name"`,
|
||||
}
|
||||
|
||||
Peer_Name :: struct {
|
||||
kind: string `json:"kind"`, // "DNS" | "IP" | ...
|
||||
value: string `json:"value"`,
|
||||
}
|
||||
|
||||
// load reads and parses limbo.json at `path` into `dst`, allocating into the
|
||||
// current context allocator. Returns false when the file is absent (the caller
|
||||
// then skips the suite) or does not parse.
|
||||
load :: proc(dst: ^Limbo, path: string) -> bool {
|
||||
raw, err := os.read_entire_file_from_path(path, context.allocator)
|
||||
if err != os.ERROR_NONE {
|
||||
return false
|
||||
}
|
||||
defer delete(raw)
|
||||
return json.unmarshal(raw, dst) == nil
|
||||
}
|
||||
234
tests/core/crypto/x509_limbo/x509_limbo.odin
Normal file
234
tests/core/crypto/x509_limbo/x509_limbo.odin
Normal file
@@ -0,0 +1,234 @@
|
||||
package test_x509_limbo
|
||||
|
||||
// Differential conformance harness for core:crypto/x509 verify_chain against the
|
||||
// C2SP x509-limbo corpus.
|
||||
//
|
||||
// Fetch the corpus (once):
|
||||
// curl -sL https://raw.githubusercontent.com/C2SP/x509-limbo/main/limbo.json \
|
||||
// -o tests/core/assets/X509-Limbo/limbo.json
|
||||
// Run:
|
||||
// odin test tests/core/crypto/x509_limbo -o:speed
|
||||
//
|
||||
// When the corpus is absent the suite logs a notice and passes, so a plain
|
||||
// `odin test` does not break. Reviewed divergences are in allow.odin.
|
||||
// Any test case failure not in the allow.odin should be considered a regression.
|
||||
|
||||
import "core:log"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:slice"
|
||||
import "core:testing"
|
||||
import "core:time"
|
||||
|
||||
import "core:crypto/x509"
|
||||
import "core:encoding/pem"
|
||||
|
||||
BASE_PATH :: ODIN_ROOT + "tests/core/assets/X509-Limbo"
|
||||
|
||||
// 2024-01-01T00:00:00Z, x509-limbo's default validation instant for cases whose
|
||||
// validation_time is null.
|
||||
DEFAULT_TIME :: i64(1_704_067_200)
|
||||
|
||||
// Arena for the parsed 41 MB corpus (strings + case structs).
|
||||
CORPUS_ARENA_SIZE :: 5 * 41 * (1024 * 1024)
|
||||
|
||||
Bucket :: enum {
|
||||
Agree, // our verdict matches expected_result
|
||||
Skip, // the chain uses an algorithm we do not verify (P-521 / Ed448 / DSA)
|
||||
We_Accept_They_Reject, // DANGEROUS: expected FAILURE, we accepted
|
||||
We_Reject_They_Accept, // safe/stricter: expected SUCCESS, we rejected
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_x509_limbo :: proc(t: ^testing.T) {
|
||||
arena: mem.Arena
|
||||
backing, berr := make([]byte, CORPUS_ARENA_SIZE)
|
||||
if berr != nil {
|
||||
log.errorf("x509-limbo: could not reserve %d bytes", CORPUS_ARENA_SIZE)
|
||||
return
|
||||
}
|
||||
defer delete(backing)
|
||||
mem.arena_init(&arena, backing)
|
||||
context.allocator = mem.arena_allocator(&arena)
|
||||
|
||||
path, _ := os.join_path([]string{BASE_PATH, "limbo.json"}, context.allocator)
|
||||
|
||||
corpus: Limbo
|
||||
if !load(&corpus, path) {
|
||||
log.infof("x509-limbo corpus not found at %s; skipping (fetch from C2SP/x509-limbo)", path)
|
||||
return
|
||||
}
|
||||
|
||||
counts: [Bucket]int
|
||||
// These outlive the per-case temp resets, so back them with the corpus arena.
|
||||
unexpected_accept := make([dynamic]string)
|
||||
unexpected_reject := make([dynamic]string)
|
||||
|
||||
for &tc in corpus.testcases {
|
||||
bucket := run_case(&tc)
|
||||
counts[bucket] += 1
|
||||
#partial switch bucket {
|
||||
case .We_Accept_They_Reject:
|
||||
if !slice.contains(ACCEPT_ALLOW, tc.id) {
|
||||
append(&unexpected_accept, tc.id)
|
||||
}
|
||||
case .We_Reject_They_Accept:
|
||||
if !slice.contains(REJECT_ALLOW, tc.id) {
|
||||
append(&unexpected_reject, tc.id)
|
||||
}
|
||||
}
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
log.infof(
|
||||
"x509-limbo: %d cases | agree %d | skip %d | we-accept/they-reject %d | we-reject/they-accept %d",
|
||||
len(corpus.testcases),
|
||||
counts[.Agree],
|
||||
counts[.Skip],
|
||||
counts[.We_Accept_They_Reject],
|
||||
counts[.We_Reject_They_Accept],
|
||||
)
|
||||
|
||||
// Security-critical gate: no un-reviewed chain we accept that limbo rejects.
|
||||
for id in unexpected_accept {
|
||||
testing.expectf(t, false, "x509-limbo REGRESSION (we accept, limbo rejects): %s", id)
|
||||
}
|
||||
// Over-rejection gate (safe direction, but still a reviewed-set change).
|
||||
for id in unexpected_reject {
|
||||
testing.expectf(t, false, "x509-limbo over-rejection (we reject, limbo accepts): %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
// run_case parses a testcase's certificates, runs verify_chain, and buckets the
|
||||
// verdict against the case's expected_result. All per-case allocation uses the
|
||||
// temp allocator, reset by the caller after each case.
|
||||
run_case :: proc(tc: ^Testcase) -> Bucket {
|
||||
expect_ok := tc.expected_result == "SUCCESS"
|
||||
|
||||
// Fixed backing keeps ^Certificate addresses stable; chains are tiny.
|
||||
store: [64]x509.Certificate
|
||||
n := 0
|
||||
|
||||
leaf: ^x509.Certificate
|
||||
inters:= make([dynamic]^x509.Certificate,context.temp_allocator)
|
||||
roots:= make([dynamic]^x509.Certificate,context.temp_allocator)
|
||||
|
||||
// A cert that fails to parse (or overflows the backing) means we reject.
|
||||
if lp := _add_pems(store[:], &n, tc.peer_certificate, nil); lp == nil {
|
||||
return _verdict(false, expect_ok)
|
||||
} else {
|
||||
leaf = lp
|
||||
}
|
||||
for s in tc.untrusted_intermediates {
|
||||
if _add_pems(store[:], &n, s, &inters) == nil {
|
||||
return _verdict(false, expect_ok)
|
||||
}
|
||||
}
|
||||
for s in tc.trusted_certs {
|
||||
if _add_pems(store[:], &n, s, &roots) == nil {
|
||||
return _verdict(false, expect_ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip chains that use an algorithm the verifier cannot check; verify_chain
|
||||
// would report Unsupported_Algorithm, which is neither accept nor reject.
|
||||
for &c in store[:n] {
|
||||
if !_supported_key(c.public_key_algorithm) {
|
||||
return .Skip
|
||||
}
|
||||
}
|
||||
if !_supported_sig(leaf.signature_algorithm) {
|
||||
return .Skip
|
||||
}
|
||||
for c in inters {
|
||||
if !_supported_sig(c.signature_algorithm) {
|
||||
return .Skip
|
||||
}
|
||||
}
|
||||
|
||||
now := DEFAULT_TIME
|
||||
if tc.validation_time != "" {
|
||||
if tm, consumed := time.rfc3339_to_time_utc(tc.validation_time); consumed > 0 {
|
||||
now = time.to_unix_seconds(tm)
|
||||
}
|
||||
}
|
||||
|
||||
opts := x509.Verify_Options {
|
||||
roots = roots[:],
|
||||
intermediates = inters[:],
|
||||
current_time = time.unix(now, 0),
|
||||
}
|
||||
if tc.expected_peer_name.kind != "" && tc.expected_peer_name.value != "" {
|
||||
opts.dns_name = tc.expected_peer_name.value
|
||||
}
|
||||
// limbo's SERVER profile implies the serverAuth EKU (webpki / CABF).
|
||||
if tc.validation_kind == "SERVER" {
|
||||
opts.required_eku = .Server_Auth
|
||||
}
|
||||
|
||||
chain, verr := x509.verify_chain(leaf, opts, context.temp_allocator)
|
||||
return _verdict(verr == .None, expect_ok)
|
||||
}
|
||||
|
||||
// _add_pems decodes every PEM block in `s` into `store` (bumping `n`), appending
|
||||
// each parsed cert's pointer to `out` when non-nil, and returns the first cert's
|
||||
// pointer (nil on any parse failure or backing overflow). A single limbo field
|
||||
// may hold a bundle of concatenated certificates.
|
||||
_add_pems :: proc(
|
||||
store: []x509.Certificate,
|
||||
n: ^int,
|
||||
s: string,
|
||||
out: ^[dynamic]^x509.Certificate,
|
||||
) -> ^x509.Certificate {
|
||||
first: ^x509.Certificate
|
||||
data := transmute([]byte)s
|
||||
for len(data) > 0 {
|
||||
blk, rest, derr := pem.decode(data, context.temp_allocator)
|
||||
if derr != nil || blk == nil {
|
||||
break // no more blocks
|
||||
}
|
||||
data = rest
|
||||
if n^ >= len(store) {
|
||||
return nil
|
||||
}
|
||||
c, perr := x509.parse(pem.block_bytes(blk), context.temp_allocator)
|
||||
if perr != .None {
|
||||
return nil
|
||||
}
|
||||
store[n^] = c
|
||||
p := &store[n^]
|
||||
n^ += 1
|
||||
if first == nil {
|
||||
first = p
|
||||
}
|
||||
if out != nil {
|
||||
append(out, p)
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
_verdict :: proc(our_ok, expect_ok: bool) -> Bucket {
|
||||
switch {
|
||||
case our_ok == expect_ok:
|
||||
return .Agree
|
||||
case our_ok && !expect_ok:
|
||||
return .We_Accept_They_Reject
|
||||
case:
|
||||
return .We_Reject_They_Accept
|
||||
}
|
||||
}
|
||||
|
||||
_supported_key :: proc(a: x509.Public_Key_Algorithm) -> bool {
|
||||
return a == .RSA || a == .ECDSA_P256 || a == .ECDSA_P384 || a == .Ed25519
|
||||
}
|
||||
|
||||
_supported_sig :: proc(a: x509.Signature_Algorithm) -> bool {
|
||||
#partial switch a {
|
||||
case .RSA_SHA1, .RSA_SHA256, .RSA_SHA384, .RSA_SHA512, .RSA_PSS:
|
||||
return true
|
||||
case .ECDSA_SHA256, .ECDSA_SHA384, .ECDSA_SHA512, .Ed25519:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user