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_1throughEND_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
CAflag - To sign CA-level certificates: signer must have
INTERMEDIATE_CAflag
- 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
0on encode; ignore on decode). - Implementations must not modify the
Flagsfield 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_CAand/orCAflags (see signing rules below), not onROOT_CAalone.
- 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, orCAflags 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, orCAflags), 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_CAdoes not by itself grant signing capability; it only asserts root identity and must be self‑signed. CombiningROOT_CAwith the rows above does not change the ✓/✗ outcomes. - A root certificate with only
CAcannot sign CA‑level certificates; it must also includeINTERMEDIATE_CAto 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_CAmust be self‑signed, but it may also carryINTERMEDIATE_CAorCA(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_CAcertificates 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
KeyIdasSHA-256(PubKey)[0..15]and verify it matches the embedded value. - Verify that all certificates in the chain have unique
KeyIdvalues. No two certificates in the same chain may share the sameKeyId. - Build a path from leaf to a trusted root by matching
SignKeyIdto 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_CAmust 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,UserDescCountblock, 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
Magicor 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
SignKeyIdof 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,CArequirement 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_CAflag 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.