# cf-pipeline-cert

**Cogniflow Step Package Certification Toolkit**

A standalone tool for validating, certifying, and verifying Cogniflow pipeline step packages. Works with step packages from any source - PyPI, GitHub, local development.

## Installation

```bash
pip install cf-pipeline-cert
```

## Quick Start

### Verify an Installed Package

```bash
# Check if a step package has a valid certificate
cf pipeline cert verify path/to/cf_basic_signal

# Output:
# Plugin: cf-basic-signal v1.0.0
# Certified at: 2024-01-15T10:30:00Z
# Format: cf-pipeline-cert-v2
# Signed: Yes
#
# Verification Results:
#   Hash matches: Yes
#   Signature valid: Yes
#   Overall: VALID
```

### Validate a Package

```bash
# Run contract validation (checks steps.jsonld metadata)
cf pipeline cert validate path/to/my_plugin

# Output shows validation status and operations
```

### Full CLI Commands

```bash
# Generate a new keypair (for setting up CI)
cf pipeline cert keygen

# Compute distribution hash for a package
cf pipeline cert hash path/to/my_plugin

# Create a certification request (unsigned certificate)
cf pipeline cert request path/to/my_plugin -o my_plugin.cert-request.json

# Sign a certification request (certification authority)
cf pipeline cert sign my_plugin.cert-request.json -k $SIGNING_KEY

# Import a signed certificate into the package
cf pipeline cert import my_plugin.cert.json path/to/my_plugin

# One-step certification (validate + sign + embed)
cf pipeline cert certify path/to/my_plugin -k $SIGNING_KEY
```

## Windows Publish Runbook

`cf-pipeline-cert` publishing in this repository is handled by the Windows-only workflow:

- `.github/workflows/cf_pipeline_cert_windows_publish.yml`

Release tags:

- PyPI: `cf-pipeline-cert-v<version>` (example: `cf-pipeline-cert-v0.1.0`)
- TestPyPI: `cf-pipeline-cert-v<version>-test` (example: `cf-pipeline-cert-v0.1.0-test`)

### 1. Local preflight mimic (required)

Requires Python 3.13 to be installed locally (`py -3.13` or equivalent).

```powershell
powershell -ExecutionPolicy Bypass -File scripts/mimic_windows_python_publish_workflow.ps1 `
  -WorkflowFile .github/workflows/cf_pipeline_cert_windows_publish.yml `
  -PackageDir sandcastle/cf_pipeline/cf_pipeline_cert `
  -PythonExe py `
  -PythonVersion 3.13
```

The scripts under `scripts/` are parameterized so the same Windows publish-preflight and queue flow can be reused for other Python modules in this repository by changing `-WorkflowFile` and `-PackageDir`.

### 2. Queue workflow (manual dispatch)

The GitHub publish jobs use PyPI/TestPyPI trusted publishing via GitHub OIDC on Windows. No `PYPI_API_TOKEN` or `TEST_PYPI_API_TOKEN` repository secrets are required when the publisher is configured on the package index side.

Dry run (prints exact `gh workflow run` command without dispatch):

```powershell
powershell -ExecutionPolicy Bypass -File scripts/queue_windows_python_publish_workflow.ps1 `
  -WorkflowFile .github/workflows/cf_pipeline_cert_windows_publish.yml `
  -PackageDir sandcastle/cf_pipeline/cf_pipeline_cert `
  -PublishTarget testpypi `
  -Ref main `
  -RequireLocalPass `
  -DryRun
```

Queue TestPyPI publish:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/queue_windows_python_publish_workflow.ps1 `
  -WorkflowFile .github/workflows/cf_pipeline_cert_windows_publish.yml `
  -PackageDir sandcastle/cf_pipeline/cf_pipeline_cert `
  -PublishTarget testpypi `
  -Ref main `
  -RequireLocalPass
```

Queue PyPI publish:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/queue_windows_python_publish_workflow.ps1 `
  -WorkflowFile .github/workflows/cf_pipeline_cert_windows_publish.yml `
  -PackageDir sandcastle/cf_pipeline/cf_pipeline_cert `
  -PublishTarget pypi `
  -Ref cf-pipeline-cert-v0.1.0 `
  -RequireLocalPass
```

### 3. Monitor queued run

```powershell
gh run list --workflow cf_pipeline_cert_windows_publish.yml --limit 10
gh run watch --exit-status
```

If the trusted publisher is configured with repository `odea-project/cogniflow-playground` and workflow `cf_pipeline_cert_windows_publish.yml`, the publish job should mint its upload token automatically during the GitHub run.

## Trust Model

### Decentralized Certification

Step packages can live on any GitHub or PyPI - they don't need to be in a central repository. The certification authority (CA) provides:

1. **Public key embedded in this package** - for verification without network access
2. **Signing service** - via GitHub Actions or manual signing

### Certificate Verification Checks

1. **Signature** - Ed25519 cryptographic signature from known CA
2. **Hash** - SHA256 of all source files (.py, .cpp, .hpp) matches certificate
3. **Format** - Certificate format version is recognized

### Security Guarantees

| What's Verified | Attack Prevented |
|----------------|------------------|
| Signature valid | Certificate tampering, fake certificates |
| Hash matches | Code injection, backdoors |
| Format version | Downgrade attacks |

## For Step Package Developers

### 1. Create `steps.jsonld`

```json
{
  "@context": [
    "../../../cf_ontology/src/cf_ontology/ontology/cf_context.jsonld"
  ],
  "@graph": [
    {
      "@id": "pkg:MyPackage",
      "@type": "cf:StepPackage",
      "cf:packageId": "my-step-package",
      "cf:packageVersion": "1.0.0",
      "rdfs:label": "My Step Package",
      "cf:description": "Steps for my package"
    },
    {
      "@id": "pkg:MyPlugin",
      "@type": "cf:PluginArtifact",
      "cf:pluginName": "my-step-package",
      "cf:pluginVersion": "1.0.0",
      "cf:artifactPath": "bin",
      "cf:pluginListSymbol": "cf_list_steps",
      "cf:pluginResolveSymbol": "cf_resolve_step"
    },
    {
      "@id": "pkg:MyImpl",
      "@type": "cf:StepImplementation",
      "cf:implementationArtifact": {"@id": "pkg:MyPlugin"},
      "cf:operationName": "my_operation",
      "cf:operationContract": {
        "kind": "example"
      }
    }
  ]
}
```

### 2. Validate Locally

```bash
cf pipeline cert validate my_package/
```

### 3. Get Certified

**Option A: Self-service via GitHub Actions**

Add this workflow to your step package repository:

```yaml
# .github/workflows/certify.yml
name: Certify Step Package
on:
  release:
    types: [published]

jobs:
  certify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      
      - name: Install certification toolkit
        run: pip install cf-pipeline-cert
      
      - name: Validate package
        run: cf pipeline cert validate my_package/
      
      - name: Create certification request
        run: cf pipeline cert request my_package/ -o cert-request.json
      
      - name: Upload request for CA signing
        uses: actions/upload-artifact@v4
        with:
          name: cert-request
          path: cert-request.json
```

**Option B: Request Certification from CA**

1. Create a certification request:
   ```bash
   cf pipeline cert request my_package/ -o cert-request.json
   ```

2. Submit the request file via:
   - GitHub Issue on the CogniFlow repository
   - Email to the certification authority
   - Automated API (when available)

3. Receive the signed certificate and import it:
   ```bash
   cf pipeline cert import my_package.cert.json my_package/
   ```

### 4. Distribute

The certificate is embedded in your `steps.jsonld` under `cf:pluginCertificate` on the plugin artifact node:

```
my_package/
|-- __init__.py
|-- ops.py
|-- steps.jsonld  <- Contains signed certificate
`-- ...
```

Users can verify the package after installation:

```bash
cf pipeline cert verify $(python -c "import my_package; print(my_package.__path__[0])")
```

## Environment Variables

| Variable | Description |
|----------|-------------|
| `CF_CERT_SIGNING_KEY_B64` | Private key for signing (CA use only) |
| `CF_CERT_VERIFICATION_KEY_B64` | Public key for verification (optional, embedded in package) |

## Programmatic Usage

```python
from pathlib import Path
from cf_pipeline_cert import (
    validate_step_package,
    verify_certificate,
    generate_keypair,
)

# Validate a step package
result = validate_step_package(Path("my_package"))
if result.valid:
    print(f"Plugin: {result.plugin_name} v{result.plugin_version}")
    print(f"Operations: {len(result.operations)}")
    print(f"Hash: {result.distribution_hash}")

# Verify certificate
verification = verify_certificate(
    Path("my_package"),
    public_key_b64="..."  # Optional, uses embedded key if not provided
)
if verification["valid"]:
    print("Certificate is valid")
else:
    for error in verification["errors"]:
        print(f"Error: {error}")
```

## Security

- **Ed25519 signatures** - Fast, secure, modern elliptic curve cryptography
- **SHA256 hashes** - Tamper-evident code fingerprints
- **No network required** - Verification works completely offline
- **Deterministic hashing** - Same code always produces same hash

### What's Included in the Hash

- All `.py` files (except in `__pycache__`, `build`)
- All `.cpp` and `.hpp` files (for native extensions)
- File paths are included (protects against file renaming attacks)
- `steps.jsonld` is **excluded** (to avoid chicken-and-egg problem)

## License

Apache-2.0
