Metadata-Version: 2.4
Name: pysapsso2
Version: 1.1.1
Summary: Decode, verify and generate SAP Logon / Assertion tickets (MYSAPSSO2 cookie). Pure-Python alternative to SAPSSOEXT.
Author: BougeBouge Consulting
License-Expression: MIT
Project-URL: Homepage, https://github.com/bougebouge/pysapsso2
Project-URL: Repository, https://github.com/bougebouge/pysapsso2
Project-URL: Issues, https://github.com/bougebouge/pysapsso2/issues
Keywords: sap,sso,single-sign-on,mysapsso2,sapssoext,decoder,logon-ticket,assertion-ticket,netweaver
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: asn1crypto>=1.5.1
Requires-Dist: pycryptodome>=3.19.0
Dynamic: license-file

# pysapsso2 — decode, verify and generate SAP SSO tickets (MYSAPSSO2)

A pure Python library for SAP Logon and Assertion tickets — the single sign-on
tokens carried in the `MYSAPSSO2` cookie and in `.sap` shortcut files. It
decodes, verifies and generates them. The tickets are accepted by:
 - SAP NetWeaver ABAP (SAP GUI, Webdynpro/Fiori, Web Services)
 - SAP NetWeaver Java (Web)

This library is an open-source alternative to SAPSSOEXT ([304450 - Single-Sign-On with SAP logon tickets in non-SAP systems](https://me.sap.com/notes/304450))

## Installation

```
pip install pysapsso2
```

## Generate a SAP shortcut (`.sap` file)
cf. [examples/generate_shortcut.py](examples/generate_shortcut.py)

## Decode a MYSAPSSO2 cookie

```python
from pysapsso2 import SapTicket

ticket = SapTicket.from_b64(cookie_value)
print(ticket.user, ticket.source_sid, ticket.source_client, ticket.expires_at)
```

## Verify a MYSAPSSO2 ticket signature

```python
from pysapsso2 import SapTicket, SapTicketHandler

ticket = SapTicket.from_b64(cookie_value)
handler = SapTicketHandler("SSO", "000", certificate=trusted_cert_pem)
handler.verify(ticket)  # raises SignatureError / CertificateError
```

`verify()` checks, in order:

1. the payload digest against the `message_digest` signed attribute,
2. that the ticket names the supplied certificate as its signer,
3. that the certificate is within its validity period (`check_certificate_validity=False` to skip, `at_time=` to pin the moment),
4. the signature over the signed attributes.

**The certificate must come from the caller.** A ticket may embed a certificate,
but anybody can embed one they hold the key for, so it is never used as a trust
anchor. `pysapsso2.crypto.get_dsa_key()` exposes the embedded key for inspection
only.

## Signing algorithms

| Key | Key sizes | Digest |
|---|---|---|
| DSA | 1024 – 4096 | `sha1`, `sha256` |
| RSA (PKCS#1 v1.5) | 1024 – 4096 | `sha1`, `sha256` |
| ECDSA | P-256, P-384, P-521 | `sha1`, `sha256` |

Every combination in the table was accepted by a live SAP NetWeaver ABAP system.

DSA with `sha1` is the default, because that is what SAPSSOEXT emits. Use it if
you need a ticket that SAPSSOEXT can also read. Otherwise prefer RSA or ECDSA
with `sha256`.

Pick the algorithm on the handler:

```python
from Crypto.PublicKey import RSA
from pysapsso2 import SapTicketHandler

handler = SapTicketHandler(
    "SSO",
    "000",
    private_key=RSA.import_key(key_pem),
    certificate=cert_pem,
    digest_algorithm="sha256",
)
ticket = handler.new("DEMOUSER")
```

`digest_algorithm` defaults to `"sha1"`. The handler picks the signer from the
key type. You can also build the signer yourself with
`pysapsso2.DsaSigner`, `pysapsso2.RsaSigner` or `pysapsso2.signer_for_key()` and
pass it as `private_key`; the handler's `digest_algorithm` is then ignored,
because the signer carries its own.

Verification needs no configuration. `verify()` reads the digest algorithm from
the ticket and the key algorithm from the certificate.

## Development
This project uses [uv](https://docs.astral.sh/uv/).

```
# Create the virtualenv and install the project with its dev dependencies
uv sync

# Run the tests / linter
uv run pytest
uv run ruff check

# Run an example
uv run examples/generate_shortcut.py

# Build the sdist and wheel
uv build
```

`tests/keys/` holds throwaway self-signed keys, one pair per row of the
algorithm matrix. They are committed, so a clone runs the suite straight away.
To replace them, or to add a key size or curve, run
`pwsh tools/make_test_keys.ps1 -Force`. It needs `openssl`; on Windows it falls
back to the one shipped with Git.

`sapsso2.key` and `sapsso2.crt` are the exception: the script never touches
them. That key signed the SAPSSOEXT reference ticket pinned in
`tests/test_sapticket.py`, so a regenerated key would not verify that
signature.

## Further reading

- [docs/sapssoext.md](docs/sapssoext.md) — developer notes: signing a ticket
  with SAPSSOEXT itself, and running the interop suite
  (`uv run pytest -m oracle`) against it.
- [docs/interop.md](docs/interop.md) records what SAP actually does with a
  ticket, measured rather than quoted: which keys and digests each consumer
  signs and validates, the DSA subgroup rule, the error pairs, and the validity
  and base64 behaviour.
