Certificate Chain of Trust (PHP)
✨ Key Features
- 🔐 Ed25519 Cryptography: Fast, secure elliptic curve signatures via libsodium
- 📜 Flexible Certificate System: Generic end-entity flags for maximum reusability
- 🏗️ Hierarchical Trust: Root CAs, intermediate CAs, and end-entity certificates
- 🔍 Strict Validation: Comprehensive security checks and flag inheritance validation
- 💾 Binary Format: Efficient serialization for storage and transmission
- 🛡️ Security-First: Unique KeyId validation, proper certificate chain verification
- 🚀 Modern PHP: Built for PHP 8.4+ with readonly classes and strict typing
📦 Installation
Install with Composer:
composer require kduma/cert-chain
Requirements:
- PHP 8.4+
- Extensions:
ext-sodium
,ext-hash
,ext-mbstring
🚀 Quick Start
Create a Root Certificate Authority
<?php
use KDuma\CertificateChainOfTrust\Certificate;
use KDuma\CertificateChainOfTrust\Crypto\Ed25519;
use KDuma\CertificateChainOfTrust\DTO\{
CertificateFlag,
CertificateFlagsCollection,
DescriptorType,
Signature,
UserDescriptor
};
// Generate a key pair for the root CA
$rootKeyPair = Ed25519::makeKeyPair();
// Create and self-sign the root certificate
$rootCA = new Certificate(
key: $rootKeyPair->toPublicKey(),
description: 'My Root Certificate Authority',
userDescriptors: [
new UserDescriptor(DescriptorType::DOMAIN, 'ca.example.com'),
new UserDescriptor(DescriptorType::EMAIL, 'admin@example.com'),
],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::ROOT_CA, // Self-signed root
CertificateFlag::INTERMEDIATE_CA, // Can sign other CAs
CertificateFlag::CA, // Can sign end-entity certs
CertificateFlag::END_ENTITY_FLAG_1, // Generic capability 1
CertificateFlag::END_ENTITY_FLAG_2, // Generic capability 2
]),
signatures: []
);
// Self-sign the certificate
$rootCA = $rootCA->with(
signatures: [Signature::make($rootCA->toBinaryForSigning(), $rootKeyPair)]
);
Create and Sign an End-Entity Certificate
// Generate key pair for end-entity certificate
$leafKeyPair = Ed25519::makeKeyPair();
$leafCert = new Certificate(
key: $leafKeyPair->toPublicKey(),
description: 'Document Signing Certificate',
userDescriptors: [
new UserDescriptor(DescriptorType::USERNAME, 'john.doe'),
new UserDescriptor(DescriptorType::EMAIL, 'john.doe@example.com'),
],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::END_ENTITY_FLAG_1, // Must be subset of signer's flags
]),
signatures: []
);
// Sign with the root CA
$leafCert = $leafCert->with(
signatures: [Signature::make($leafCert->toBinaryForSigning(), $rootKeyPair)]
);
Validate Certificate Chains
use KDuma\CertificateChainOfTrust\{Chain, TrustStore, Validator};
// Create certificate chain (leaf to root)
$chain = new Chain([$leafCert, $rootCA]);
// Create trust store with trusted root CAs
$trustStore = new TrustStore([$rootCA]);
// Validate the chain
$result = Validator::validateChain($chain, $trustStore);
if ($result->isValid) {
echo "✅ Certificate chain is valid!\n";
echo "Validated " . count($result->validatedChain) . " certificates\n";
} else {
echo "❌ Validation failed:\n";
foreach ($result->getErrorMessages() as $error) {
echo "- $error\n";
}
}
Sign and Verify Messages
use KDuma\BinaryTools\BinaryString;
// Sign a message
$message = BinaryString::fromString('Important document content');
$signature = Signature::make($message, $leafKeyPair);
// Verify the signature
$isValid = $signature->validate($message, $leafCert->key);
echo $isValid ? "✅ Signature valid" : "❌ Signature invalid";
🏗️ Certificate Flag System
The library uses a flexible flag system for maximum reusability:
CA-Level Flags
ROOT_CA
(0x0001): Self-signed root certificate authorityINTERMEDIATE_CA
(0x0002): Can sign CA-level certificatesCA
(0x0004): Can sign end-entity certificates
Generic End-Entity Flags
END_ENTITY_FLAG_1
throughEND_ENTITY_FLAG_8
(0x0100-0x8000)- Use these for any purpose: document signing, code signing, email encryption, etc.
- Flag Inheritance: Certificate flags must be a subset of the signer's flags
🔐 Security Model
- Unique KeyIds: All certificates in a chain must have unique identifiers
- Flag Inheritance: End-entity flags are inherited down the chain (strict subset rule)
- Proper Authority: CA flags determine what types of certificates can be signed
- Cryptographic Verification: Ed25519 signatures with full chain validation
- Trust Anchors: Only certificates in the TrustStore are trusted
🔧 Advanced Features
Binary Serialization
// Serialize for storage/transmission
$binaryData = $certificate->toBinary();
$chainData = $chain->toBinary();
$trustStoreData = $trustStore->toBinary();
// Load from binary
$loadedCert = Certificate::fromBinary($binaryData);
$loadedChain = Chain::fromBinary($chainData);
Complex Hierarchies
// Multi-level certificate hierarchies
$rootCA = createRootCA();
$policyCA = createPolicyCA($rootCA);
$issuingCA = createIssuingCA($policyCA);
$endEntity = createEndEntity($issuingCA);
$chain = new Chain([$endEntity, $issuingCA, $policyCA, $rootCA]);
Batch Operations
// Efficient validation of multiple certificates
foreach ($certificates as $cert) {
$result = Validator::validateChain(
new Chain([$cert, $intermediateCA, $rootCA]),
$trustStore
);
// Process result...
}
PHP Certificate Chain of Trust - Complete Documentation
Table of Contents
- Introduction
- Installation
- Quick Start
- Core Concepts
- API Reference
- Advanced Usage
- Security Model
- Error Handling
- Best Practices
- Binary Format
Introduction
PHP Certificate Chain of Trust is a modern library for creating, managing, and validating certificate chains using Ed25519 cryptography. Built on top of libsodium, it provides a secure and efficient way to implement certificate authorities, digital signatures, and trust relationships.
Key Features
- Ed25519 Cryptography: Fast, secure elliptic curve signatures
- Flexible Certificate Flags: 8 generic end-entity flags plus CA-level flags
- Hierarchical Trust: Support for root CAs, intermediate CAs, and end-entity certificates
- Binary Serialization: Efficient storage and transmission format
- Strict Validation: Comprehensive security checks and flag inheritance validation
- Modern PHP: Built for PHP 8.4+ with strict typing and readonly classes
Architecture Overview
The library follows a layered architecture:
┌─────────────────┐
│ Applications │ ← Your code using the library
├─────────────────┤
│ Validation │ ← Certificate chain validation logic
├─────────────────┤
│ Certificate │ ← Certificate, Chain, TrustStore classes
├─────────────────┤
│ Cryptography │ ← Ed25519 key generation and signing
├─────────────────┤
│ Binary Format │ ← Serialization and parsing
└─────────────────┘
Installation
Requirements
- PHP: 8.4 or higher
- Extensions:
ext-hash
,ext-mbstring
,ext-sodium
- Dependencies:
kduma/binary-tools
,paragonie/sodium_compat
Install with Composer
composer require kduma/cert-chain
Development Dependencies
For development and testing:
composer require --dev kduma/cert-chain
composer run test # Run tests
composer run test-coverage # Generate coverage report
composer run lint # Check code style
composer run fix # Fix code style
Quick Start
Creating Your First Certificate Authority
<?php
use KDuma\CertificateChainOfTrust\Certificate;
use KDuma\CertificateChainOfTrust\Crypto\Ed25519;
use KDuma\CertificateChainOfTrust\DTO\{
CertificateFlag,
CertificateFlagsCollection,
DescriptorType,
Signature,
UserDescriptor
};
// Generate a key pair for the root CA
$rootKeyPair = Ed25519::makeKeyPair();
// Create the root certificate
$rootCert = new Certificate(
key: $rootKeyPair->toPublicKey(),
description: 'My Root Certificate Authority',
userDescriptors: [
new UserDescriptor(DescriptorType::DOMAIN, 'ca.example.com'),
new UserDescriptor(DescriptorType::EMAIL, 'admin@example.com'),
],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::ROOT_CA,
CertificateFlag::INTERMEDIATE_CA,
CertificateFlag::CA,
CertificateFlag::END_ENTITY_FLAG_1, // Generic capability 1
]),
signatures: []
);
// Self-sign the root certificate
$rootCert = $rootCert->with(
signatures: [Signature::make($rootCert->toBinaryForSigning(), $rootKeyPair)]
);
Creating a Signed Certificate
// Generate a key pair for an end-entity certificate
$leafKeyPair = Ed25519::makeKeyPair();
$leafCert = new Certificate(
key: $leafKeyPair->toPublicKey(),
description: 'Document Signing Certificate',
userDescriptors: [
new UserDescriptor(DescriptorType::USERNAME, 'john.doe'),
new UserDescriptor(DescriptorType::EMAIL, 'john.doe@example.com'),
],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::END_ENTITY_FLAG_1, // Must be subset of signer's flags
]),
signatures: []
);
// Sign with the root CA
$leafCert = $leafCert->with(
signatures: [Signature::make($leafCert->toBinaryForSigning(), $rootKeyPair)]
);
Validating Certificate Chains
use KDuma\CertificateChainOfTrust\{Chain, TrustStore, Validator};
// Create a certificate chain (leaf to root)
$chain = new Chain([$leafCert, $rootCert]);
// Create a trust store with trusted root CAs
$trustStore = new TrustStore([$rootCert]);
// Validate the chain
$result = Validator::validateChain($chain, $trustStore);
if ($result->isValid) {
echo "Certificate chain is valid!\n";
echo "Validated chain has " . count($result->validatedChain) . " certificates\n";
} else {
echo "Validation failed:\n";
foreach ($result->getErrorMessages() as $error) {
echo "- $error\n";
}
}
Core Concepts
Certificates
Certificates are immutable objects that contain:
- Public Key: Ed25519 public key (32 bytes)
- Key ID: First 16 bytes of SHA-256 hash of the public key
- Description: Human-readable UTF-8 description
- User Descriptors: Optional identity information (username, email, domain)
- Flags: Permissions and capabilities
- Signatures: One or more cryptographic signatures
Certificate Flags
The library uses a hierarchical flag system:
CA-Level Flags
ROOT_CA
(0x0001): Self-signed root certificate authorityINTERMEDIATE_CA
(0x0002): Can sign CA-level certificatesCA
(0x0004): Can sign end-entity certificates
End-Entity Flags
END_ENTITY_FLAG_1
(0x0100): Generic capability 1END_ENTITY_FLAG_2
(0x0200): Generic capability 2END_ENTITY_FLAG_3
(0x0400): Generic capability 3END_ENTITY_FLAG_4
(0x0800): Generic capability 4END_ENTITY_FLAG_5
(0x1000): Generic capability 5END_ENTITY_FLAG_6
(0x2000): Generic capability 6END_ENTITY_FLAG_7
(0x4000): Generic capability 7END_ENTITY_FLAG_8
(0x8000): Generic capability 8
Flag Inheritance Rules
- Signing Authority:
- To sign non-CA certificates: signer must have
CA
flag - To sign CA-level certificates: signer must have
INTERMEDIATE_CA
flag
- To sign non-CA certificates: signer must have
- End-Entity Inheritance:
- Certificate's end-entity flags must be a subset of signer's end-entity flags
- Example: If signer has
FLAG_1 | FLAG_2
, certificate can haveFLAG_1
,FLAG_2
, or both, but notFLAG_3
Certificate Chains
A chain represents a path from an end-entity certificate to a trusted root:
[End Entity] → [Intermediate CA] → [Root CA]
Key requirements:
- Each certificate must have unique KeyId
- Each certificate must be properly signed by the next certificate in the chain
- Flag inheritance must be respected throughout the chain
- Chain must terminate with a self-signed ROOT_CA certificate
Trust Stores
Trust stores contain trusted root CA certificates:
- Only self-signed ROOT_CA certificates allowed
- All certificates must have unique KeyIds
- Used as trust anchors during validation
API Reference
Core Classes
Certificate
readonly class Certificate
{
public function __construct(
public PublicKey $key,
public string $description,
public array $userDescriptors, // UserDescriptor[]
public CertificateFlagsCollection $flags,
public array $signatures // Signature[]
);
}
Key Methods:
// Create a new certificate with updated signatures
public function with(array $signatures): Certificate
// Check if certificate is a root CA
public function isRootCA(): bool
// Check if certificate is self-signed
public function isSelfSigned(): bool
// Get signature by signer's KeyId
public function getSignatureByKeyId(KeyId $keyId): ?Signature
// Get self-signature (if exists)
public function getSelfSignature(): ?Signature
// Serialize for signing (TBS - To Be Signed data)
public function toBinaryForSigning(): BinaryString
// Full binary serialization
public function toBinary(): BinaryString
public static function fromBinary(BinaryString $data): Certificate
Example Usage:
// Check certificate properties
if ($certificate->isRootCA()) {
echo "This is a root CA certificate\n";
}
if ($certificate->flags->hasCA()) {
echo "Certificate can sign other certificates\n";
}
// Binary serialization
$binaryData = $certificate->toBinary();
$restored = Certificate::fromBinary($binaryData);
Chain
readonly class Chain extends CertificatesContainer
{
public function __construct(array $certificates = []);
}
Key Methods:
// Get the first certificate (typically the end-entity)
public function getFirstCertificate(): ?Certificate
// Build all possible paths from a certificate to root CAs
public function buildPaths(?Certificate $leaf = null): array
// Inherited from CertificatesContainer
public function getById(KeyId $keyId): ?Certificate
public function toBinary(): BinaryString
public static function fromBinary(BinaryString $data): Chain
Example Usage:
// Create a chain
$chain = new Chain([$endEntity, $intermediate, $rootCA]);
// Find all valid paths to root CAs
$paths = $chain->buildPaths($endEntity);
echo "Found " . count($paths) . " possible certification paths\n";
TrustStore
readonly class TrustStore extends CertificatesContainer
{
public function __construct(array $certificates = []);
}
Key Methods:
// Inherited from CertificatesContainer
public function getById(KeyId $keyId): ?Certificate
public function toBinary(): BinaryString
public static function fromBinary(BinaryString $data): TrustStore
Validation:
- Only ROOT_CA certificates allowed
- All certificates must have unique KeyIds
- All certificates must be self-signed
Example Usage:
try {
$trustStore = new TrustStore([$rootCA1, $rootCA2]);
echo "Trust store created with " . count($trustStore->certificates) . " root CAs\n";
} catch (InvalidArgumentException $e) {
echo "Invalid certificate for trust store: " . $e->getMessage() . "\n";
}
Validator
class Validator
{
public static function validateChain(Chain $chain, TrustStore $store): ValidationResult;
}
Validation Process:
- Structure and signature presence validation
- KeyId computation and verification
- Unique KeyId validation within chain
- Path building from leaf to trusted root
- Certificate authority validation
- End-entity flag inheritance validation
- Cryptographic signature verification
Example Usage:
$result = Validator::validateChain($chain, $trustStore);
if (!$result->isValid) {
echo "Validation failed with " . count($result->errors) . " errors:\n";
foreach ($result->getErrorMessages() as $error) {
echo "- $error\n";
}
}
if (!empty($result->warnings)) {
echo "Warnings:\n";
foreach ($result->getWarningMessages() as $warning) {
echo "- $warning\n";
}
}
Cryptography Classes
Ed25519
class Ed25519
{
public static function makeKeyPair(): PrivateKeyPair;
}
Example Usage:
$keyPair = Ed25519::makeKeyPair();
echo "Generated key pair with KeyId: " . $keyPair->keyId->toString() . "\n";
PrivateKeyPair
readonly class PrivateKeyPair
{
public function __construct(
public KeyId $keyId,
public BinaryString $publicKey,
public BinaryString $secretKey
);
}
Key Methods:
public function toPublicKey(): PublicKey
public function toBinary(): BinaryString
public static function fromBinary(BinaryString $data): PrivateKeyPair
PublicKey
readonly class PublicKey
{
public function __construct(
public KeyId $id,
public BinaryString $publicKey
);
}
KeyId
readonly class KeyId
{
public static function fromPublicKey(BinaryString $publicKey): KeyId;
public function toString(): string;
public function equals(KeyId $other): bool;
}
DTO Classes
CertificateFlag
enum CertificateFlag: int
{
case ROOT_CA = 0x0001;
case INTERMEDIATE_CA = 0x0002;
case CA = 0x0004;
case END_ENTITY_FLAG_1 = 0x0100;
// ... through END_ENTITY_FLAG_8 = 0x8000;
}
Methods:
public function toString(): string;
public static function fromByte(int $byte): self;
CertificateFlagsCollection
class CertificateFlagsCollection
{
public static function fromList(array $flags): self;
public static function fromInt(int $value): self;
public static function EndEntityFlags(): self;
public static function CAFlags(): self;
}
Key Methods:
public function has(CertificateFlag $flag): bool;
public function hasRootCA(): bool;
public function hasIntermediateCA(): bool;
public function hasCA(): bool;
public function isCA(): bool;
public function getEndEntityFlags(): CertificateFlagsCollection;
public function getCAFlags(): CertificateFlagsCollection;
public function isSubsetOf(CertificateFlagsCollection $other): bool;
public function toString(): string;
Example Usage:
$flags = CertificateFlagsCollection::fromList([
CertificateFlag::CA,
CertificateFlag::END_ENTITY_FLAG_1,
CertificateFlag::END_ENTITY_FLAG_2
]);
if ($flags->hasCA()) {
echo "Certificate can sign other certificates\n";
}
$endEntityFlags = $flags->getEndEntityFlags();
echo "End-entity flags: " . $endEntityFlags->toString() . "\n";
UserDescriptor
readonly class UserDescriptor
{
public function __construct(
public DescriptorType $type,
public string $value
);
}
DescriptorType
enum DescriptorType: int
{
case USERNAME = 0x01;
case EMAIL = 0x02;
case DOMAIN = 0x03;
}
Signature
readonly class Signature
{
public static function make(BinaryString $data, PrivateKeyPair $keyPair): self;
public function validate(BinaryString $data, PublicKey $publicKey): bool;
}
ValidationResult
readonly class ValidationResult
{
public function __construct(
public array $errors = [], // ValidationError[]
public array $warnings = [], // ValidationWarning[]
public array $validatedChain = [], // Certificate[]
public bool $isValid = true
);
public function getErrorMessages(): array;
public function getWarningMessages(): array;
}
Advanced Usage
Complex Certificate Hierarchies
// Create a multi-level hierarchy
$rootCA = createRootCA();
$policyCA = createPolicyCA($rootCA); // Specialized intermediate CA
$issuingCA = createIssuingCA($policyCA); // Final issuing authority
$endEntity = createEndEntity($issuingCA);
$chain = new Chain([$endEntity, $issuingCA, $policyCA, $rootCA]);
$trustStore = new TrustStore([$rootCA]);
$result = Validator::validateChain($chain, $trustStore);
Working with Multiple End-Entity Flags
// Certificate with multiple capabilities
$multiCapabilityCert = new Certificate(
key: $keyPair->toPublicKey(),
description: 'Multi-purpose Certificate',
userDescriptors: [new UserDescriptor(DescriptorType::EMAIL, 'service@example.com')],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::END_ENTITY_FLAG_1, // e.g., Document signing
CertificateFlag::END_ENTITY_FLAG_2, // e.g., Code signing
CertificateFlag::END_ENTITY_FLAG_3, // e.g., Email encryption
]),
signatures: []
);
// Specialized certificate with subset of capabilities
$specializedCert = new Certificate(
key: $specializedKeyPair->toPublicKey(),
description: 'Document-only Certificate',
userDescriptors: [new UserDescriptor(DescriptorType::USERNAME, 'document-signer')],
flags: CertificateFlagsCollection::fromList([
CertificateFlag::END_ENTITY_FLAG_1, // Only document signing
]),
signatures: []
);
Binary Serialization and Storage
// Serialize certificates for storage
$certificateData = $certificate->toBinary();
file_put_contents('certificate.bin', $certificateData->value);
// Serialize entire chains
$chainData = $chain->toBinary();
file_put_contents('chain.bin', $chainData->value);
// Serialize trust stores
$trustStoreData = $trustStore->toBinary();
file_put_contents('truststore.bin', $trustStoreData->value);
// Load from storage
$loadedCert = Certificate::fromBinary(
BinaryString::fromString(file_get_contents('certificate.bin'))
);
$loadedChain = Chain::fromBinary(
BinaryString::fromString(file_get_contents('chain.bin'))
);
$loadedTrustStore = TrustStore::fromBinary(
BinaryString::fromString(file_get_contents('truststore.bin'))
);
Custom Validation Logic
function validateCertificateForPurpose(Certificate $cert, string $purpose): bool {
// Check if certificate has appropriate flags for the purpose
$flags = $cert->flags;
return match ($purpose) {
'document-signing' => $flags->has(CertificateFlag::END_ENTITY_FLAG_1),
'code-signing' => $flags->has(CertificateFlag::END_ENTITY_FLAG_2),
'email-encryption' => $flags->has(CertificateFlag::END_ENTITY_FLAG_3),
'server-auth' => $flags->has(CertificateFlag::END_ENTITY_FLAG_4),
default => false
};
}
// Use in your application
if (validateCertificateForPurpose($certificate, 'document-signing')) {
// Proceed with document signing
$signature = Signature::make($documentData, $privateKey);
}
Message Signing and Verification
use KDuma\BinaryTools\BinaryString;
// Sign a message
$message = BinaryString::fromString('Important document content');
$signature = Signature::make($message, $signerKeyPair);
// Create a signed message structure
$signedMessage = [
'message' => base64_encode($message->value),
'signature' => base64_encode($signature->toBinary()->value),
'signer_keyid' => $signerCertificate->key->id->toString(),
'certificate_chain' => base64_encode($certificateChain->toBinary()->value),
];
// Verification process
function verifySignedMessage(array $signedMessage, TrustStore $trustStore): bool {
// Reconstruct components
$message = BinaryString::fromString(base64_decode($signedMessage['message']));
$signature = Signature::fromBinary(BinaryString::fromString(base64_decode($signedMessage['signature'])));
$chain = Chain::fromBinary(BinaryString::fromString(base64_decode($signedMessage['certificate_chain'])));
// Validate certificate chain
$chainResult = Validator::validateChain($chain, $trustStore);
if (!$chainResult->isValid) {
return false;
}
// Find signer certificate
$signerKeyId = KeyId::fromString($signedMessage['signer_keyid']);
$signerCert = $chain->getById($signerKeyId);
if (!$signerCert) {
return false;
}
// Verify signature
return $signature->validate($message, $signerCert->key);
}
Security Model
Trust Validation
The library implements a strict trust model:
- Root of Trust: Only certificates in the TrustStore are trusted
- Chain of Trust: Each certificate must be signed by the next certificate in the chain
- Unique Identity: All certificates in a chain must have unique KeyIds
- Proper Authority: Signers must have appropriate flags to sign certificates
- Flag Inheritance: End-entity flags must be a subset of the signer's flags
Flag Inheritance Validation
Root CA (FLAGS: 1,2,3,4)
↓ signs
Intermediate CA (FLAGS: 1,2,3) ← Valid: subset of root's flags
↓ signs
End Entity (FLAGS: 1,2) ← Valid: subset of intermediate's flags
End Entity (FLAGS: 1,5) ← INVALID: flag 5 not in intermediate's flags
Unique KeyId Requirement
Every certificate in a chain must have a unique KeyId to prevent:
- Certificate confusion attacks
- Bypassing of validation rules
- Circular signing relationships
Cryptographic Security
- Ed25519: Provides 128-bit security level
- KeyId: SHA-256 hash prevents collision attacks
- Signatures: Each signature is bound to specific certificate data
- Self-Signing: Root CAs must be self-signed to be valid
Error Handling
Validation Errors
The library provides detailed error messages for validation failures:
$result = Validator::validateChain($chain, $trustStore);
foreach ($result->errors as $error) {
echo "Error: " . $error->getMessage() . "\n";
echo "Context: " . $error->getContext() . "\n";
if ($error->getCertificate()) {
echo "Certificate: " . $error->getCertificate()->description . "\n";
}
}
Common Error Types
- Structure Errors:
- Invalid binary format
- Missing required fields
- Invalid lengths
- Cryptographic Errors:
- Invalid signatures
- KeyId mismatch with public key
- Signature verification failure
- Authority Errors:
- Insufficient signing authority
- Missing CA flags
- Invalid flag combinations
- Inheritance Errors:
- End-entity flags not subset of signer
- Duplicate KeyIds in chain
- Invalid certificate hierarchy
- Trust Errors:
- Root CA not in trust store
- Chain doesn't terminate in root CA
- Self-signing validation failure
Exception Handling
try {
$certificate = new Certificate($key, $desc, $descriptors, $flags, $signatures);
} catch (InvalidArgumentException $e) {
echo "Certificate creation failed: " . $e->getMessage() . "\n";
}
try {
$trustStore = new TrustStore([$invalidCert]);
} catch (InvalidArgumentException $e) {
echo "Trust store validation failed: " . $e->getMessage() . "\n";
}
try {
$chain = Chain::fromBinary($corruptedData);
} catch (Exception $e) {
echo "Binary parsing failed: " . $e->getMessage() . "\n";
}
Best Practices
Security Best Practices
- Key Management:
// Generate fresh keys for each certificate $keyPair = Ed25519::makeKeyPair(); // Clear sensitive data when done sodium_memzero($keyPair->secretKey->value);
- Certificate Validation:
// Always validate chains before trusting certificates $result = Validator::validateChain($chain, $trustStore); if (!$result->isValid) { throw new SecurityException('Untrusted certificate chain'); }
- Flag Assignment:
// Use principle of least privilege $flags = CertificateFlagsCollection::fromList([ CertificateFlag::END_ENTITY_FLAG_1 // Only what's needed ]);
- Trust Store Management:
// Keep trust stores minimal and up-to-date $trustStore = new TrustStore($onlyTrustedRootCAs); // Regularly audit trust store contents foreach ($trustStore->certificates as $cert) { if (isCertificateExpiredOrRevoked($cert)) { // Remove from trust store } }
Performance Best Practices
- Efficient Validation:
// Cache validation results for identical chains $cacheKey = hash('sha256', $chain->toBinary()->value); if (!isset($validationCache[$cacheKey])) { $validationCache[$cacheKey] = Validator::validateChain($chain, $trustStore); }
- Binary Serialization:
// Use binary format for storage and transmission $binaryData = $certificate->toBinary(); // Much more efficient than JSON or XML
- Batch Operations:
// Process multiple certificates efficiently foreach ($certificates as $cert) { $results[] = Validator::validateChain(new Chain([$cert, ...$commonChain]), $trustStore); }
Development Best Practices
- Error Handling:
function createSecureCertificate(...): Certificate { try { return new Certificate(...); } catch (InvalidArgumentException $e) { logger->error('Certificate creation failed', ['error' => $e->getMessage()]); throw new CertificateCreationException('Failed to create certificate', 0, $e); } }
- Testing:
// Test all certificate scenarios public function testInvalidFlagInheritance() { $this->expectException(ValidationException::class); // Test code that should fail validation }
- Documentation:
/** * Creates a certificate for document signing * * @param PrivateKeyPair $keyPair Signing key pair * @param string $description Human-readable certificate description * @return Certificate Signed certificate with document signing capability */ function createDocumentSigningCert(PrivateKeyPair $keyPair, string $description): Certificate { // Implementation }
Binary Format
Certificate Binary Structure
The library uses a custom binary format optimized for Ed25519:
Offset | Size | Field | Description
-------|------|-------|------------
0 | 3 | Magic | 0x084453 ("CERT" in base64)
3 | 1 | AlgVer| 0x01 for Ed25519
4 | 16 | KeyId | SHA-256(PubKey)[0..15]
20 | 32 | PubKey| Raw Ed25519 public key
52 | 1 | DescLen| Description length (0-255)
53 | N | Desc | UTF-8 description
53+N | 1 | UserDescCount| Number of user descriptors
54+N | ... | UserDescs| User descriptor entries
... | 2 | Flags | Certificate flags (big-endian)
... | 1 | SigCount| Number of signatures
... | ... | Sigs | Signature entries
Chain Binary Structure
Chains are stored as concatenated certificates:
[Certificate 1][Certificate 2][Certificate 3]...
Trust Store Binary Structure
Offset | Size | Field | Description
-------|------|-------|------------
0 | 6 | Magic | 0x4EBBAEB5E74A (TrustStore identifier)
6 | ... | Certs | Concatenated certificates
Working with Binary Data
// Low-level binary operations
$cert = Certificate::fromBinary($binaryData);
$serialized = $cert->toBinary();
// Base64 encoding for text storage
$base64 = base64_encode($serialized->value);
$restored = Certificate::fromBinary(
BinaryString::fromString(base64_decode($base64))
);
// Hexadecimal encoding
$hex = bin2hex($serialized->value);
$restored = Certificate::fromBinary(
BinaryString::fromString(hex2bin($hex))
);
This documentation covers PHP Certificate Chain of Trust library. For the latest updates, see the project repository.
Simple Certificate Specification (Complete)
Common rules
- Container: Standard Base64 (no line breaks).
- Endianness: All multi-byte integers are big-endian.
- Strings: UTF-8, no NUL terminator.
- Counts: Single byte counts.
- TBS (to-be-signed) region: From Magic through Flags (inclusive).
Binary layout
Shared header
# | Field | Size | Notes |
---|---|---|---|
1 | Magic | 3 | Fixed 08 44 53 ("CERT" when Base64). |
2 | AlgVer | 1 | 0x01 = Ed25519 v1 . Others reserved. |
The following structure applies to AlgVer = 0x01
(Ed25519 v1 — fixed sizes, no length fields).
# | Field | Size | Notes |
---|---|---|---|
3 | KeyId | 16 | Must be SHA-256(PubKey)[0..15] . |
4 | PubKey | 32 | Raw Ed25519 public key. |
5 | DescLen | 1 | 0–255. Description is required (policy may enforce ≥1). |
6 | Desc | DescLen | UTF-8. |
7 | UserDescCount | 1 | N descriptors (0–255). |
8 | For i in 1..N: Type | 1 | See enum below. |
9 | For i: ValLen | 2 | UTF-8 value length (UINT16BE). |
10 | For i: Value | ValLen | UTF-8. |
11 | Flags | 2 | Permission bitmask (see table). TBS ends here. |
12 | SigCount | 1 | Number of signatures. May be 0 ; such certificates must be rejected during validation. |
13 | For j in 1..M: SignKeyId | 16 | Signer’s KeyId (same 16-byte rule). No length field. |
14 | For j: Signature | 64 | Raw Ed25519 signature. No length field. |
Descriptor Type (1 byte)
0x01
— Username0x02
— Email0x03
— Domain
Descriptors are optional. Multiple entries (even of same type) allowed.
Flags (2 bytes, bitmask)
0x0001
— Root CA0x0002
— Intermediate CA0x0004
— CA0x0100
— End Entity Flag 10x0200
— End Entity Flag 20x0400
— End Entity Flag 30x0800
— End Entity Flag 40x1000
— End Entity Flag 50x2000
— End Entity Flag 60x4000
— End Entity Flag 70x8000
— End Entity Flag 8- Other bits reserved (must be
0
on encode; ignore on decode). - Implementations must not modify the
Flags
field when re‑emitting a certificate. Any reserved bits present in input data must be preserved exactly to avoid altering signed bytes.
Flag semantics and policy
Roles and combinations
- Root CA (
0x0001
)- Must be self‑signed.
- May also carry
INTERMEDIATE_CA (0x0002)
and/orCA (0x0004)
. - The ability to sign depends on the presence of
INTERMEDIATE_CA
and/orCA
flags (see signing rules below), not onROOT_CA
alone.
- Intermediate CA (
0x0002
)- This flag alone grants authority to sign CA-level certificates.
- When combined with
CA
, allows signing of both CA-level and non-CA certificates.
- CA (
0x0004
)- Required to sign non-CA certificates.
- Without
INTERMEDIATE_CA
, may sign only non‑CA certificates (noROOT_CA
,INTERMEDIATE_CA
, orCA
flags on the subject).
- Combined
INTERMEDIATE_CA | CA
- Authorized to sign both CA‑level certificates (because of
INTERMEDIATE_CA
) and non‑CA certificates (because ofCA
).
- Authorized to sign both CA‑level certificates (because of
- No CA flags
- Cannot sign any certificates.
End‑entity flags (non‑CA) inheritance
- End‑entity flags are the non‑CA bits (e.g., flags 0x0100 through 0x8000).
- A subject’s end‑entity flags must be a subset of its issuer’s end‑entity flags:
Subject.EndEntityFlags ⊆ Issuer.EndEntityFlags
.
Signing rules matrix
- To sign non-CA certificates, the issuer must have
CA
. - To sign CA-level certificates (with
ROOT_CA
,INTERMEDIATE_CA
, orCA
flags), the issuer must haveINTERMEDIATE_CA
.
Quick reference:
Issuer flags | Sign non‑CA subject | Sign CA‑level subject |
---|---|---|
None | ✗ | ✗ |
CA | ✓ | ✗ |
INTERMEDIATE_CA | ✗ | ✓ |
INTERMEDIATE_CA | CA | ✓ |
Notes:
- Presence of
ROOT_CA
does not by itself grant signing capability; it only asserts root identity and must be self‑signed. CombiningROOT_CA
with the rows above does not change the ✓/✗ outcomes. - A root certificate with only
CA
cannot sign CA‑level certificates; it must also includeINTERMEDIATE_CA
to do so. - End‑entity flags must obey subset inheritance:
Subject.EndEntity ⊆ Issuer.EndEntity
.
End‑Entity Inheritance Matrix
Only illustrates the subset rule for end‑entity flags. CA‑level signing capability (issuer must have CA
for non‑CA subjects, INTERMEDIATE_CA
for CA‑level subjects) still applies separately.
Legend: Flag1 = 0x0100
, Flag2 = 0x0200
, etc.
Issuer end‑entity flags | Subject: None | Subject: Flag1 | Subject: Flag2 | Subject: Flag1+Flag2 |
---|---|---|---|---|
None | ✓ | ✗ | ✗ | ✗ |
Flag1 | ✓ | ✓ | ✗ | ✗ |
Flag2 | ✓ | ✗ | ✓ | ✗ |
Flag1+Flag2 | ✓ | ✓ | ✓ | ✓ |
Reminder: This matrix validates only the end‑entity subset requirement. The issuer must still have the appropriate CA‑level flag to sign the subject at all (see the Signing rules matrix above).
Notes
- A certificate with
ROOT_CA
must be self‑signed, but it may also carryINTERMEDIATE_CA
orCA
(or both). - The presence of CA‑level flags does not prevent a certificate from also carrying end‑entity flags; those end‑entity bits govern what end‑entity flags it may delegate to subjects, not necessarily whether it acts as an end‑entity itself.
- Certificate uniqueness: Each certificate in a chain must have a unique
KeyId
. Self‑signedROOT_CA
certificates can only appear as the final (root) certificate in a chain, never in the middle. - End‑entity inheritance: The subset rule (
Child.EndEntity ⊆ Issuer.EndEntity
) applies to all certificate pairs in a chain without exception, including self‑signed certificates.
Chain validation algorithm
- Verify structure and lengths.
- Ensure each certificate has at least one signature.
- Compute
KeyId
asSHA-256(PubKey)[0..15]
and verify it matches the embedded value. - Verify that all certificates in the chain have unique
KeyId
values. No two certificates in the same chain may share the sameKeyId
. - Build a path from leaf to a trusted root by matching
SignKeyId
to parentKeyId
. - For each child/parent pair (issuer = parent):
- For non-CA children: Issuer must have
CA
. - For CA-level children (has any of
ROOT_CA
,INTERMEDIATE_CA
,CA
): issuer must haveINTERMEDIATE_CA
. - End‑entity inheritance: For each end‑entity bit (0x0100 through 0x8000), if child has it, issuer must also have it (
Child.EndEntity ⊆ Issuer.EndEntity
). This validation applies to all certificate pairs without exception.
- For non-CA children: Issuer must have
- A certificate with
ROOT_CA
must be self‑signed and present in the trust store.
Ed25519 details (AlgVer = 0x01)
- Public key: 32 raw bytes (RFC 8032).
- Signature: 64 raw bytes (RFC 8032).
- KeyId: first 16 bytes of SHA-256 over the 32-byte public key.
- Signature input: exactly the TBS bytes (no pre-hash).
Chain packaging (concatenation format)
To allow distributing an entire trust path as one Base64 string, a certificate may be followed immediately by another full certificate structure (starting again at Magic). You can therefore concatenate the whole trust tree (leaf → … → root) into one binary blob and Base64‑encode the entire concatenation as a single string (no separators).
Encoding rules
- Each element is a complete Certificate as specified for the chosen
AlgVer
. - Concatenate certificates in order from leaf to root. The final element SHOULD be a Root CA (
0x0001
) and MUST be self‑signed. - After concatenation, Base64‑encode the entire byte sequence (standard Base64, no line breaks).
- This container is purely a packaging convenience; cryptographic validity is still per‑certificate.
Parsing rules
- Decode Base64 once to obtain the binary blob.
- Starting at offset 0, parse a Certificate. Its length is determinable from its internal fields (notably
DescLen
,UserDescCount
block, andSigCount
). - After finishing one certificate, if there are remaining bytes, the next byte MUST be
Magic
(08 44 53
), and parsing continues for the next certificate. - Continue until the end of the byte array. Reject if trailing bytes remain that do not begin with
Magic
or if any certificate is malformed.
Validation
- Build chains using the SignKeyId → KeyId linkage between adjacent certificates. When a concatenated parent is present, it must match the
SignKeyId
of the child and validate per signature and policy rules. - If a required issuer is not present in the concatenation, the validator MAY resolve it from a local trust store; however, when present, the concatenated parent MUST be used and must validate.
- All existing policy rules apply (self‑signed roots for
0x0001
,CA
requirement to issue, subset‑of‑flags, end‑entity authorization, etc.).
ABNF update
CertificateChain = 1*Certificate ; one or more Certificates back-to-back
; Each Certificate is defined as previously for AlgVer = 0x01
; The entire CertificateChain is Base64-encoded when transported as text.
TrustStore binary format
A TrustStore is a container for trusted root CA certificates used during chain validation. It has its own binary serialization format for storage and transport.
Binary layout
# | Field | Size | Notes |
---|---|---|---|
1 | Magic | 6 | Fixed 4e bb ac b5 e7 4a (TrustStore identifier). |
2 | Certificates | Variable | Zero or more complete Certificate structures concatenated back-to-back. |
Parsing rules
- Decode the magic bytes to identify this as a TrustStore.
- Parse certificates sequentially using the standard Certificate parsing rules until end of data.
- Each certificate's length is determined from its internal structure (no explicit count or length fields).
Validation rules
- Only root CA certificates: All certificates in a TrustStore must have the
ROOT_CA
flag and be self-signed. - Unique KeyIds: All certificates must have unique KeyIds within the TrustStore.
- Self-signing validation: Each certificate's self-signature must be cryptographically valid.
Encoding rules
- The entire TrustStore binary structure is Base64-encoded for text transport.
- Each Certificate follows the standard certificate binary format defined above.
- Certificates are stored in the order they were added to the TrustStore.
Usage
TrustStores are used by the validator to determine which root certificates are trusted during chain validation. A certificate chain is only considered valid if it terminates in a root CA certificate present in the provided TrustStore.