diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1c6de8f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,77 @@ +# Copilot instructions for wolfCOSE + +wolfCOSE is a strictly zero-allocation C library implementing CBOR (RFC 8949) +and COSE (RFC 9052/9053), plus RFC 8230 (RSA keys) and RFC 9964 (ML-DSA), on +top of wolfCrypt. It runs on constrained and FIPS-bounded targets and parses +untrusted, attacker-controlled bytes (CBOR/COSE messages and keys). + +When reviewing a pull request, act as a security and correctness reviewer +performing the primary pre-merge gate, in the spirit of a focused +security-review pass. Prioritize real vulnerabilities and behavioral bugs the +diff introduces. Be precise, high-confidence, and brief. A short list of solid +findings is far more useful than a long list of nits. + +## How to review + +1. Triage the diff by risk. Treat as HIGH RISK anything touching CBOR/COSE + parsing of untrusted input, buffer and length handling, key/secret handling, + algorithm selection and verification, and error paths. Spend your attention + there. +2. For each changed region, reason about the behavioral delta: what did the + code do before, what does it do now, and what new attack surface or weakened + check does the change introduce. If the diff removes a check or a previously + added security fix, call that out as a likely regression. +3. Report a finding only when you can name the concrete failure and, where it + applies, the input that triggers it. Assign a severity (Critical, High, + Medium, Low) and include the relevant CWE. + +## What to look for + +- Memory safety: out-of-bounds reads and writes, missing or wrong length checks + before copies, pointer arithmetic into caller-provided buffers, off-by-one on + bstr/array/map lengths (CWE-120, CWE-125, CWE-787). +- Integer issues: overflow or underflow in size and length math, signed and + unsigned confusion, and truncation when casting lengths to `word32` (CWE-190, + CWE-191). +- NULL and uninitialized use: missing NULL checks on parameters and return + values, use of a buffer before it is populated (CWE-476). +- CBOR and COSE parsing of untrusted input: unchecked element counts, lengths, + or nesting depth; trusting a header or a decoded length without validating it + against the remaining buffer; accepting non-preferred or trailing CBOR. +- Cryptographic correctness and misuse: nonce or IV reuse, algorithm confusion + (dispatching on an unauthenticated `alg`), comparing secrets or MAC tags with + non-constant-time `memcmp`, and unchecked crypto return codes (CWE-327, + CWE-208, CWE-347). +- Zeroization: sensitive data (keys, seeds, shared secrets, plaintext, crypto + intermediates) must be cleared with `wolfCose_ForceZero` (wolfCOSE's secure + scrub helper) on every exit path, including error and early-return paths. Flag + missing or wrong-size scrubs and untracked temporary copies. +- Logic and contracts: inverted conditions, wrong enum or label, copy-paste + errors, missing error checks, error paths that skip cleanup, and API misuse + (wrong call order, use after a failed call). +- Spec conformance: deviations from RFC 9052/9053, RFC 8949 deterministic + encoding, RFC 8230 (RSA key parameters), or RFC 9964 (ML-DSA / AKP) message + and key formats. + +## Do not report + +These are out of scope here. Other tooling owns them, and raising them adds +noise rather than safety. + +- Style, formatting, naming, or comment-density observations. Code style is + enforced by the project's own rules and by cppcheck, clang-tidy, and MISRA + checks in CI. +- Maintainability nits such as "function is too long" or "poorly documented + function". Do not flag comment ratios or function length. +- Any suggestion that introduces dynamic allocation. wolfCOSE is strictly + zero-allocation. Never propose `malloc`, `calloc`, `realloc`, `free`, or the + `XMALLOC`/`XFREE`/`XREALLOC` wrappers. Large objects use scoped or static + storage, never the heap. +- C++ idioms or constructs outside C89/C99. The library targets C99 and its + public headers must also compile as C++. +- `wolfCose_ForceZero` replaced by `memset` or a plain loop (the project + deliberately uses `wolfCose_ForceZero` so the scrub is not optimized away); + do not suggest the reverse. + +Keep comments few, concrete, and security or correctness focused. If you are +not confident a finding is real, leave it out. diff --git a/.github/semgrep-rules.yml b/.github/semgrep-rules.yml new file mode 100644 index 0000000..f382196 --- /dev/null +++ b/.github/semgrep-rules.yml @@ -0,0 +1,66 @@ +# wolfCOSE deterministic security rules (Semgrep). +# +# These encode wolfCOSE invariants that are decidable by pattern and currently +# have zero hits, so they pass today and fail the build on any future +# violation. Run with: semgrep scan --config .github/semgrep-rules.yml --error +# +# Style/maintainability is intentionally out of scope (owned by cppcheck, +# clang-tidy, and MISRA checks). These rules only enforce security invariants. + +rules: + - id: wolfcose-no-dynamic-allocation + languages: [c] + severity: ERROR + message: >- + wolfCOSE is strictly zero-allocation. Dynamic memory + (malloc/calloc/realloc/free, alloca, strdup, or the XMALLOC/XFREE/XREALLOC + wrappers) must never appear in the core library. Use a caller-provided + buffer or scoped/static storage instead. + paths: + include: + - src/ + - include/wolfcose/ + pattern-either: + - pattern: malloc(...) + - pattern: calloc(...) + - pattern: realloc(...) + - pattern: free(...) + - pattern: aligned_alloc(...) + - pattern: alloca(...) + - pattern: strdup(...) + - pattern: XMALLOC(...) + - pattern: XFREE(...) + - pattern: XREALLOC(...) + + - id: wolfcose-unsafe-libc + languages: [c] + severity: ERROR + message: >- + Unbounded C library function. Use a length-bounded alternative + (XMEMCPY/XMEMMOVE with an explicit size, snprintf) so a length cannot run + past the destination buffer. + paths: + include: + - src/ + - include/wolfcose/ + pattern-either: + - pattern: gets(...) + - pattern: strcpy(...) + - pattern: strcat(...) + - pattern: sprintf(...) + - pattern: vsprintf(...) + + - id: wolfcose-non-constant-time-compare + languages: [c] + severity: ERROR + message: >- + memcmp/XMEMCMP is not constant-time. In the core library, comparing a + MAC tag, key, or shared secret with a variable-time compare leaks timing + information (CWE-208). Use ConstantCompare for secret-dependent + comparisons. + paths: + include: + - src/ + pattern-either: + - pattern: memcmp(...) + - pattern: XMEMCMP(...) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..3ab3837 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,71 @@ +name: CodeQL + +on: + push: + branches: [ 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + schedule: + - cron: '0 7 * * 1' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + # Required so CodeQL can upload findings to the Security tab. + security-events: write + +jobs: + analyze: + name: CodeQL (C, security) + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool + + # Full-feature wolfSSL so every wolfCOSE code path is compiled and + # therefore visible to CodeQL. + - name: Build wolfSSL + run: | + cd ~ + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-ecc --enable-ed25519 --enable-ed448 \ + --enable-curve25519 --enable-curve448 \ + --enable-aesgcm --enable-aesccm --enable-aescbc \ + --enable-sha384 --enable-sha512 \ + --enable-keygen --enable-hkdf --enable-aeskeywrap \ + --enable-chacha --enable-poly1305 \ + --enable-mldsa --enable-rsapss \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: c-cpp + build-mode: manual + # security-extended runs only the security query suites. The default + # "security-and-quality" pack also flags maintainability nits (long + # functions, comment density), which we intentionally exclude here. + queries: security-extended + + - name: Build wolfCOSE + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:c-cpp" diff --git a/.github/workflows/house-style.yml b/.github/workflows/house-style.yml new file mode 100644 index 0000000..d3db7c4 --- /dev/null +++ b/.github/workflows/house-style.yml @@ -0,0 +1,24 @@ +name: House Style + +on: + push: + branches: [ 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + house-style: + name: wolfCOSE house-style conventions + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - name: Check conventions (no goto, C-style comments, banner style, no tabs/trailing space) + run: sh scripts/check_house_style.sh diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000..6f08f83 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,34 @@ +name: Semgrep + +on: + push: + branches: [ 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + semgrep: + name: Semgrep (wolfCOSE security rules) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Semgrep + run: pip install --quiet semgrep + + # --error makes the job fail on any finding. The rules self-scope via + # their `paths:` blocks, so scanning the whole tree is fine. + - name: Run wolfCOSE security rules + run: semgrep scan --config .github/semgrep-rules.yml --error --quiet . diff --git a/scripts/check_house_style.sh b/scripts/check_house_style.sh new file mode 100755 index 0000000..2914b22 --- /dev/null +++ b/scripts/check_house_style.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# wolfCOSE house-style check. +# +# Enforces the repo's C conventions across all tracked C sources (the vendored +# lightpanda tree is excluded). Comment and whitespace conventions are text, not +# code structure, so git grep is the right tool here. Run from the repo root: +# sh scripts/check_house_style.sh +# Exits non-zero on any deviation. + +status=0 +PATHS='*.c *.h :(exclude)lightpanda/' +tab=$(printf '\t') + +report() { + matches=$(cat) + if [ -n "$matches" ]; then + printf '\nFAIL: %s\n' "$1" + printf '%s\n' "$matches" + status=1 + fi +} + +# No goto anywhere; new code uses fallthrough cleanup with a single exit. +git grep -nw goto -- $PATHS | report "goto is banned (use fallthrough cleanup gated on 'if (ret == WOLFCOSE_SUCCESS)')" + +# C-style comments only; // line comments are not allowed (URLs in strings/headers excepted). +git grep -nE '//' -- $PATHS | grep -vE 'https?://|s?ftp://|file://' | report "C++ // comments are banned; use /* ... */" + +# Section banners use the /* ----- ... ----- */ style, not ===== / ##### / **** runs. +git grep -nE '/\*[* ]*(={4,}|#{4,}|\*{4,})' -- $PATHS | report "section banners must use the /* ----- ... ----- */ style" + +# Null-check pointers with != NULL, not if (!ptr). +git grep -nE 'if \(![A-Za-z_]' -- $PATHS | report "null-check pointers with '!= NULL', not 'if (!ptr)'" + +# Spaces, no tabs. +git grep -nE "$tab" -- $PATHS | report "tabs are banned in C sources; use spaces" + +# No trailing whitespace. +git grep -nE ' +$' -- $PATHS | report "trailing whitespace" + +if [ "$status" -ne 0 ]; then + printf '\nHouse-style check failed.\n' + exit 1 +fi +printf 'House-style check passed.\n' diff --git a/tests/test_cose.c b/tests/test_cose.c index 63b42d0..8276bab 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -13289,10 +13289,8 @@ static void test_force_failure_crypto(void) } #endif /* WOLFCOSE_FORCE_FAILURE */ -/* ======================================================== - * Negative Test Coverage - Phases 1-10 - * Tests for validation/error handling code paths - * ======================================================== */ +/* ----- Negative Test Coverage - Phases 1-10 + * Tests for validation/error handling code paths ----- */ /* ----- Phase 1: Buffer Too Small Tests ----- */ #ifdef WOLFCOSE_HAVE_ES256 @@ -16491,7 +16489,7 @@ int test_cose(void) test_cose_key_ecc_public_only(); #endif - /* ======== Negative Test Coverage - Phases 1-10 ======== */ + /* ----- Negative Test Coverage - Phases 1-10 ----- */ TEST_LOG("\n--- Negative Test Coverage (Phases 1-10) ---\n"); /* Phase 1: Buffer Too Small Tests */