Packages · supply-chain

k2gl/sshsig

Sign and verify with the SSH signature format (SSHSIG) in PHP.

Use the same SSHSIG format as `ssh-keygen -Y` to sign and verify blobs from PHP.

k2gl/sshsig version on Packagist k2gl/sshsig total downloads k2gl/sshsig license

Install

composer require k2gl/sshsig
PHP
>=8.1

Reach for it when

  • You sign or verify with SSH keys in the SSHSIG format (ssh-keygen -Y compatible).
  • You want signing that interoperates with existing SSH key infrastructure.

Look elsewhere when

  • You want Sigstore/transparency-log-backed signing — use sigstore-sign / sigstore-verify.

k2gl/sshsig

Sign and verify files with SSH keys in PHP, in the OpenSSH SSHSIG format that ssh-keygen -Y sign/verify produces (the signatures Git uses for commit and tag signing). Verifying checks the signer against an allowed_signers file; signing emits bytes ssh-keygen -Y verify accepts. No dependencies.

Fail-closed by design: anything malformed, unsupported, cryptographically invalid, or unauthorized throws — nothing is ever silently treated as verified.

Install

composer require k2gl/sshsig

Requires PHP 8.1+ with ext-sodium (Ed25519) and ext-openssl (RSA and ECDSA) — both are bundled with most PHP builds.

Usage

Verify a signature against an allowed_signers file

This is the equivalent of ssh-keygen -Y verify -f allowed_signers -I <identity> -n <namespace>.

use K2gl\Sshsig\AllowedSigners;
use K2gl\Sshsig\Exception\SshsigException;
use K2gl\Sshsig\SshsigVerifier;

$verifier = new SshsigVerifier;

try {
    $result = $verifier->verify(
        message: file_get_contents('release.tar.gz'),
        armoredSignature: file_get_contents('release.tar.gz.sig'),
        allowedSigners: AllowedSigners::fromFile('allowed_signers'),
        identity: 'alice@example.com',
        namespace: 'file',
    );

    echo "Verified: {$result->publicKey->fingerprint()}\n";
} catch (SshsigException $e) {
    // not verified — malformed, unsupported, bad signature, or unauthorized signer
    echo "Rejected: {$e->getMessage()}\n";
}

Check a signature without authorizing the signer

The equivalent of ssh-keygen -Y check-novalidate -n <namespace>: confirm the signature is structurally sound and cryptographically valid under its own embedded key, then inspect it.

$signature = $verifier->checkNoValidate($message, $armoredSignature, namespace: 'git');

echo $signature->signatureAlgorithm;          // e.g. "ssh-ed25519"
echo $signature->publicKey->fingerprint();    // "SHA256:…"

Sign a message

Produce an SSHSIG signature (the ssh-keygen -Y sign operation). Provide the signing key — Ed25519 (a 64-byte libsodium secret key) or an OpenSSL RSA/ECDSA private key:

use K2gl\Sshsig\Ed25519SigningKey;
use K2gl\Sshsig\OpensshPrivateKey;
use K2gl\Sshsig\OpensslSigningKey;
use K2gl\Sshsig\SshsigSigner;

// Ed25519
$keypair = sodium_crypto_sign_keypair();
$signer = new SshsigSigner(new Ed25519SigningKey(sodium_crypto_sign_secretkey($keypair)));

// …or RSA / ECDSA from an OpenSSL private key (PEM)
$signer = new SshsigSigner(OpensslSigningKey::fromPem(file_get_contents('id_rsa')));

// …or an unencrypted ssh-ed25519 key straight off disk, in the format ssh-keygen writes
$signer = new SshsigSigner(OpensshPrivateKey::fromFile('/home/you/.ssh/id_ed25519'));

$armored = $signer->sign($message, namespace: 'file');          // sha512 by default
file_put_contents('message.sig', $armored);

// the resulting signature verifies with `ssh-keygen -Y verify` and with this library

$signer->publicKey() returns the matching SshPublicKey (e.g. to build an allowed_signers line). The default hash is sha512; pass hashAlgorithm: 'sha256' to switch.

Inspect or build allowed_signers in memory

$allowed = AllowedSigners::fromString(<<<TXT
    # comments and blank lines are ignored
    alice@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…
    *@example.com namespaces="git,file" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA…
    TXT);

Design

  • Fail-closed. Every failure path throws a K2gl\Sshsig\Exception\SshsigException (InvalidSignatureException, UnsupportedAlgorithmException, SignatureVerificationFailed, SignerNotAllowedException). A returned value always means verified.
  • Spec-accurate. Implements OpenSSH PROTOCOL.sshsig: the SSHSIG magic preamble, version 1, the to-be-signed blob (namespace + reserved + hash algorithm + message hash), and the armor.
  • Algorithms. ssh-ed25519, rsa-sha2-256, rsa-sha2-512, ecdsa-sha2-nistp256/384/521; sha256 and sha512 message hashing. The legacy SHA-1 ssh-rsa signature algorithm is refused.
  • allowed_signers. Principals pattern-lists, namespaces=, valid-after/valid-before validity windows, and cert-authority entries (parsed and recorded; certificate-chain verification is not yet performed).
  • Signing. SshsigSigner over a pluggable SigningKey (Ed25519 via ext-sodium; RSA and ECDSA via ext-openssl), producing armor byte-compatible with ssh-keygen -Y verify. OpensshPrivateKey::fromFile()/::fromString() load an unencrypted ssh-ed25519 key straight from the openssh-key-v1 container ssh-keygen writes by default; encrypted containers and RSA/ECDSA containers are refused (fail-closed) rather than partially read.
  • Zero dependencies. Pure PHP over ext-sodium and ext-openssl; no phpseclib.
  • Verified against OpenSSH. The test suite verifies real ssh-keygen -Y sign output for every supported algorithm, plus tampered, wrong-namespace, unauthorized, and malformed cases.

License

MIT. See LICENSE.

API

Public classes and methods, generated from the source.

K2gl\Sshsig\AllowedSigner class

  • __construct( public readonly string $principals, public readonly SshPublicKey $key, public readonly bool $certAuthority = false, public readonly ?string $namespaces = null, public readonly ?DateTimeImmutable $validAfter = null, public readonly ?DateTimeImmutable $validBefore = null, )

K2gl\Sshsig\AllowedSigners class

  • __construct(private readonly array $signers)
  • fromString(string $contents): self
  • fromFile(string $path): self
  • all(): array
  • findMatch(string $identity, string $namespace, SshPublicKey $key, DateTimeImmutable $time): ?AllowedSigner

K2gl\Sshsig\Ed25519SigningKey class

  • __construct(private readonly string $secretKey)
  • publicKey(): SshPublicKey
  • signTosign(string $tosign, string $hashAlgorithm): array

K2gl\Sshsig\Exception\InvalidSignatureException class

K2gl\Sshsig\Exception\SignatureVerificationFailed class

K2gl\Sshsig\Exception\SignerNotAllowedException class

K2gl\Sshsig\Exception\SigningException class

K2gl\Sshsig\Exception\SshsigException interface

K2gl\Sshsig\Exception\UnsupportedAlgorithmException class

K2gl\Sshsig\OpensshPrivateKey class

  • fromFile(string $path): SigningKey
  • fromString(string $armored): SigningKey

K2gl\Sshsig\OpensslSigningKey class

  • fromKey(OpenSSLAsymmetricKey $key): self
  • fromPem(string $pem, ?string $passphrase = null): self
  • publicKey(): SshPublicKey
  • signTosign(string $tosign, string $hashAlgorithm): array

K2gl\Sshsig\SigningKey interface

  • publicKey(): SshPublicKey
  • signTosign(string $tosign, string $hashAlgorithm): array

K2gl\Sshsig\SshPublicKey class

  • fromBlob(string $blob): self
  • fromOpensshLine(string $line): self
  • fingerprint(): string
  • equals(self $other): bool

K2gl\Sshsig\SshSignature class

  • fromArmored(string $armored): self
  • signedData(string $message): string

K2gl\Sshsig\SshsigSigner class

  • __construct(private readonly SigningKey $key)
  • publicKey(): SshPublicKey
  • sign(string $message, string $namespace, string $hashAlgorithm = 'sha512'): string

K2gl\Sshsig\SshsigVerifier class

  • verify( string $message, string $armoredSignature, AllowedSigners $allowedSigners, string $identity, string $namespace, ?DateTimeImmutable $verifyTime = null, ): VerifiedSignature
  • checkNoValidate(string $message, string $armoredSignature, string $namespace): SshSignature

K2gl\Sshsig\VerifiedSignature class

  • __construct( public readonly string $identity, public readonly string $namespace, public readonly SshPublicKey $publicKey, public readonly string $principals, )