Enforcing Signature Meaning and Non-Repudiation

A signature is only defensible if it says what it means and the signer cannot later claim it was something else. This guide enforces two things at once: that every signature carries an explicit, controlled meaning, and that once made, it is undeniable. It extends the signing authority built in implementing 21 CFR Part 11 electronic signatures with a meaning policy that binds a signer’s role to the meanings they may assert, and a reconstruction routine that reproduces the signed evidence on demand. Together they close the gap between “someone clicked approve” and “this named person, authorized to approve, deliberately approved this record.”

Regulatory hook

21 CFR Part 11 §11.50(a)(2) requires that a signed record contain the meaning associated with the signature — authorship, review, approval, or responsibility — and §11.50(b) requires that meaning to be subject to the same controls as the record and included in any human-readable form. §11.70 requires the signature to be linked so it cannot be repudiated by excision or transfer. Non-repudiation is the practical outcome of enforcing both: when the meaning is a controlled value sealed inside the signature and the signature is bound to the record, a signer cannot argue the meaning was ambiguous or the signature was misapplied. The evidence reconstructs identically every time, which is what makes a challenge answerable with computation rather than testimony.

Meaning and non-repudiation are two faces of the same requirement. A signature with no controlled meaning is repudiable by ambiguity — the signer can claim they were only acknowledging receipt, not approving release — while a signature whose meaning is recorded but not sealed is repudiable by alteration, because the stored meaning could have been changed after the fact. Closing both requires that the meaning be drawn from a fixed vocabulary, authorized against the signer’s role at signing time, and committed cryptographically alongside the identity and the record. Only then does a signature carry a claim that is both unambiguous in what it asserts and undeniable in that it was asserted, which is the standard a regulator applies when a batch-release decision is questioned long after the fact.

Prerequisites

  • Python 3.11 or newer. Enforcement and verification use only the standard library.
bash
pip install pytest    # validation suite only
  • Signing authority: the keyed HMAC signer with two-component authentication already in place.
  • Role model: each signer identity carries a verified role (for example qa_reviewer, qa_release_manager) that determines which meanings they may assert.
  • Meaning vocabulary: a controlled enumeration held under change control, versioned so a policy change is itself an auditable event.
  • Access control: the meaning policy is read-only to the signing path; only the quality system, under change control, revises it.

Step-by-Step Implementation

Step 1 — Define the controlled meaning vocabulary and role policy

Meaning is not free text and not every signer may assert every meaning. Encode the permitted set and the role-to-meaning policy as controlled data, versioned so a change is traceable.

python
from __future__ import annotations

import hashlib
import hmac
import json
from datetime import datetime, timezone
from enum import Enum


class Meaning(str, Enum):
    # §11.50(a)(2): the closed set of meanings a signature may declare.
    AUTHORSHIP = "AUTHORSHIP"
    REVIEW = "REVIEW"
    APPROVAL = "APPROVAL"
    RESPONSIBILITY = "RESPONSIBILITY"


# §11.50(b): the role-to-meaning policy is controlled data. Its version is stamped
# on each signature so a reviewer can prove which policy authorized the meaning.
MEANING_POLICY_VERSION = "policy-2026.2"
ROLE_MEANINGS: dict[str, set[Meaning]] = {
    "qa_reviewer": {Meaning.REVIEW},
    "qa_release_manager": {Meaning.REVIEW, Meaning.APPROVAL, Meaning.RESPONSIBILITY},
    "author_analyst": {Meaning.AUTHORSHIP},
}

Step 2 — Enforce the meaning against the signer’s role

Before signing, confirm the requested meaning is one the signer’s role is permitted to assert. A release-authority meaning from a reviewer-only role is refused, not silently downgraded.

python
class MeaningError(RuntimeError):
    pass


def enforce_meaning(role: str, meaning: Meaning) -> None:
    # §11.50(a)(2) + access control: a signer may only assert meanings their
    # verified role authorizes, so "approval" carries genuine release authority.
    permitted = ROLE_MEANINGS.get(role)
    if permitted is None:
        raise MeaningError(f"unknown signer role: {role}")
    if meaning not in permitted:
        raise MeaningError(f"role {role} may not assert {meaning.value}")

Verify the policy admits and rejects the right combinations:

python
enforce_meaning("qa_release_manager", Meaning.APPROVAL)      # permitted
try:
    enforce_meaning("qa_reviewer", Meaning.APPROVAL)          # reviewer cannot approve
    raise AssertionError("expected MeaningError")
except MeaningError:
    pass

Step 3 — Seal the meaning inside the signature for non-repudiation

The meaning and policy version go inside the signed body, so neither can be altered after the fact without breaking the keyed signature. This is what makes the meaning non-repudiable rather than merely recorded.

python
def _canonical(obj: dict) -> bytes:
    return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")


def sign_with_meaning(
    record_hash: str,
    signer_id: str,
    printed_name: str,
    role: str,
    meaning: Meaning,
    signing_key: bytes,
    key_version: str,
) -> dict:
    enforce_meaning(role, meaning)          # §11.50 gate before any signing
    body = {
        "record_hash": record_hash,          # §11.70 binds to the record
        "signer_id": signer_id,
        "signer_printed_name": printed_name,  # §11.50(a)(1)
        "signer_role": role,
        "signing_meaning": meaning.value,     # §11.50(a)(2) sealed, not appended
        "meaning_policy_version": MEANING_POLICY_VERSION,  # §11.50(b) controlled meaning
        "signed_at": datetime.now(timezone.utc).isoformat(),  # §11.50(a)(3)
        "key_version": key_version,
    }
    # §11.70 non-repudiation: the keyed HMAC over the whole body makes the meaning
    # inseparable from the signature; altering the meaning invalidates it.
    body["signature_value"] = hmac.new(
        signing_key, _canonical(body), hashlib.sha256
    ).hexdigest()
    return body

Step 4 — Reconstruct the evidence to answer a repudiation challenge

Non-repudiation is demonstrated by reconstruction: recompute the signature from the stored fields and show it matches. If it does, the signer’s claim to have signed with that meaning is undeniable; if it does not, the record was altered and that is itself a finding.

python
def reconstruct_and_verify(sig: dict, signing_key: bytes) -> bool:
    # §11.70: reproduce the exact signed body and recompute the HMAC. A match is
    # cryptographic proof the named signer asserted this meaning on this record.
    try:
        body = {k: sig[k] for k in (
            "record_hash", "signer_id", "signer_printed_name", "signer_role",
            "signing_meaning", "meaning_policy_version", "signed_at", "key_version",
        )}
        expected = hmac.new(signing_key, _canonical(body), hashlib.sha256).hexdigest()
    except KeyError:
        return False
    return hmac.compare_digest(expected, sig["signature_value"])  # constant-time

Prove that tampering with the meaning breaks the proof:

python
key = b"k" * 32                              # dev fallback only; use a KMS/HSM key
sig = sign_with_meaning("abc123", "qa.007", "R. Mensah", "qa_release_manager", Meaning.APPROVAL, key, "v3")
assert reconstruct_and_verify(sig, key) is True
sig["signing_meaning"] = "REVIEW"            # attempt to downgrade the meaning
assert reconstruct_and_verify(sig, key) is False

Step 5 — Render the meaning in the human-readable record

§11.50(b) requires the meaning to appear in any human-readable form of the record. A meaning that is enforced and sealed but never displayed satisfies the cryptographic half of the obligation while failing the readability half: an inspector examining a printed disposition must be able to see, without decoding a signature blob, that this named person approved rather than merely reviewed. Emit it alongside the printed name and time so an inspector reads the signer’s claimed role directly.

python
def human_readable_line(sig: dict) -> str:
    # §11.50(b): the meaning appears in the human-readable manifestation.
    return (
        f"{sig['signer_printed_name']} ({sig['signer_role']}) "
        f"— {sig['signing_meaning']}{sig['signed_at']}"
    )

Confirm the meaning is present in the rendered output:

bash
# The rendered disposition must display the signing meaning; grep is a cheap gate.
python -m coldchain.qms.render_signature --sig sig.json | grep -E 'APPROVAL|REVIEW|RESPONSIBILITY|AUTHORSHIP'

Compliance Validation Checklist

Run these as part of computerized-system validation; each is something an auditor can independently confirm for this control.

Troubleshooting

Symptom Root cause Fix
Signer disputes the meaning of a past signature Meaning stored beside, not inside, the signature Seal the meaning in the signed body; reconstruct to prove it
Reviewer signs with approval authority Role-to-meaning policy not enforced Run enforce_meaning against the verified role before signing (§11.50)
Meaning missing from a printed disposition Human-readable renderer omits the field Include the meaning in the rendered line (§11.50(b))
Verification fails after a policy update Policy version not stamped, old signatures re-checked against new set Stamp meaning_policy_version per signature; verify against the stamped version
Two signatures with identical meaning conflated No signer role recorded Record and render signer_role so review and release are distinguishable

For architectural context, this guide sits under implementing 21 CFR Part 11 electronic signatures.