DEV Community

eimza
eimza

Posted on

Building a PKI Certificate Expiry Monitor for Turkish E-Signature Tokens

Turkish e-signature certificates (NES — Nitelikli Elektronik Sertifika) expire after 1-3 years. When they do, your company can't sign e-invoices, e-ledgers, or court filings. Here's how to build a monitoring system.

The Problem

Monday morning, 09:01 AM:
"Mali Mühür sertifikası süresi dolmuş, e-fatura kesemiyoruz!"

Revenue lost: ₺0 (but compliance violated)
Penalty risk: ₺100,000+ (GİB cezası)
Reputation: Damaged (client invoices delayed)
Enter fullscreen mode Exit fullscreen mode

Certificate Structure

Turkish e-signature certificates follow X.509v3:

Certificate:
    Version: 3
    Serial Number: 4a:2b:3c:...
    Issuer: C=TR, O=Kamu SM, CN=Kamu SM NES
    Validity:
        Not Before: Sep 22 00:00:00 2025 GMT
        Not After:  Sep 22 23:59:59 2026 GMT  ← This is what we monitor
    Subject: CN=ACME LTD ŞTİ, SERIALNUMBER=1234567890
    Subject Public Key Info:
        RSA 2048-bit
    X509v3 Extensions:
        Key Usage: Digital Signature, Non-Repudiation
        QCStatements: id-etsi-qcs-QcCompliance  ← Qualified certificate
Enter fullscreen mode Exit fullscreen mode

JavaScript Certificate Parser

class CertificateMonitor {
    /**
     * Parse a PEM or DER certificate and extract expiry
     * For browser-based monitoring dashboards
     */
    static parsePEM(pemString) {
        // Extract base64 content between headers
        const b64 = pemString
            .replace(/-----BEGIN CERTIFICATE-----/, '')
            .replace(/-----END CERTIFICATE-----/, '')
            .replace(/\s/g, '');

        const binary = atob(b64);
        const bytes = new Uint8Array(binary.length);
        for (let i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i);
        }

        return this.parseDER(bytes);
    }

    static parseDER(bytes) {
        // Simplified ASN.1 parser for X.509 validity dates
        // In production, use a library like pkijs or asn1js

        const hex = Array.from(bytes).map(b => 
            b.toString(16).padStart(2, '0')
        ).join('');

        // Find UTCTime or GeneralizedTime for notAfter
        // UTCTime tag: 0x17, GeneralizedTime tag: 0x18
        const utcTimePattern = /17(\w{2})(\w+)/g;
        const times = [];
        let match;

        while ((match = utcTimePattern.exec(hex)) !== null) {
            const len = parseInt(match[1], 16);
            const timeHex = match[2].substring(0, len * 2);
            const timeStr = this.hexToAscii(timeHex);
            times.push(this.parseUTCTime(timeStr));
        }

        // Second UTCTime is notAfter
        return {
            notBefore: times[0] || null,
            notAfter: times[1] || null,
            daysRemaining: times[1] ? 
                Math.ceil((times[1] - new Date()) / 86400000) : null
        };
    }

    static hexToAscii(hex) {
        let str = '';
        for (let i = 0; i < hex.length; i += 2) {
            str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
        }
        return str;
    }

    static parseUTCTime(str) {
        // Format: YYMMDDHHMMSSZ
        const year = parseInt(str.substr(0, 2));
        const fullYear = year >= 50 ? 1900 + year : 2000 + year;
        return new Date(
            fullYear,
            parseInt(str.substr(2, 2)) - 1,
            parseInt(str.substr(4, 2)),
            parseInt(str.substr(6, 2)),
            parseInt(str.substr(8, 2)),
            parseInt(str.substr(10, 2))
        );
    }
}

// Alert thresholds
function checkCertificates(certs) {
    const alerts = [];
    for (const cert of certs) {
        const days = cert.daysRemaining;
        if (days <= 0) {
            alerts.push({ level: 'CRITICAL', message: `EXPIRED ${Math.abs(days)} days ago`, cert });
        } else if (days <= 7) {
            alerts.push({ level: 'CRITICAL', message: `Expires in ${days} days`, cert });
        } else if (days <= 30) {
            alerts.push({ level: 'WARNING', message: `Expires in ${days} days`, cert });
        } else if (days <= 90) {
            alerts.push({ level: 'INFO', message: `Expires in ${days} days`, cert });
        }
    }
    return alerts;
}
Enter fullscreen mode Exit fullscreen mode

Monitoring Dashboard Architecture

┌──────────────────────────────────────────┐
│           Certificate Monitor            │
│                                          │
│  ┌────────┐  ┌────────┐  ┌────────┐     │
│  │Mali    │  │E-İmza  │  │SSL/TLS │     │
│  │Mühür   │  │NES     │  │Server  │     │
│  │45 gün  │  │120 gün │  │230 gün │     │
│  │ ⚠️     │  │  ✅    │  │  ✅    │     │
│  └────────┘  └────────┘  └────────┘     │
│                                          │
│  Alert Rules:                            │
│  • 90 days → Email to IT                 │
│  • 30 days → Email to CEO + IT           │
│  • 7 days  → SMS + Slack + Email         │
│  • 0 days  → 🚨 EMERGENCY               │
└──────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Our Open-Source Solution

We built mali-muhur-eimza-suresi-kontrol — a Windows tool that scans all connected USB tokens and reports certificate expiry dates.

All tools: github.com/eimza-kep

Top comments (0)