pseb

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 22 Imported by: 0

README

pseb

Go Badge golangci-lint integrity

Read and verify Pakistan Software Export Board (PSEB) registration certificates.

Overview

The Pakistan Software Export Board (PSEB) registers IT and IT-enabled services companies and freelancers as software exporters.

Every PSEB registration certificate is issued as a PDF that carries a QR code.

That QR code encodes a verification URL whose final path segment is a signed JSON Web Token (JWT). The JWT's claims contain the certificate's core data:

  • the registration number
  • the registration type (company or individual/freelancer)
  • issued-at
  • expires-at

This library lets you:

  • Extract the QR code from a certificate PDF and read out the verification URL, the JWT, and its decoded claims.
  • Verify a certificate JWT against the PSEB portal, returning the registered entity's name and whether the certificate is currently valid.
What's inside the QR code

The QR encodes a URL of the form https://portal.techdestination.com/verify-certificate/<jwt>. The JWT's claims include:

  • registrationNo — the PSEB registration number (e.g. Z-25-17156/25)
  • type — the registration type (company or individual/freelancer)
  • iat — the issued-at timestamp
  • exp — the expiry timestamp

[!NOTE] PSEB certificates are signed with HS256, a symmetric (shared-secret) algorithm. Because the signing secret is held by PSEB and not published, the signature cannot be verified offline by third parties. This library uses the JWT to read claims locally and delegates authoritative validity to the PSEB portal via Verify.

Installation

go get github.com/mistermoe/pseb

Usage

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/mistermoe/pseb"
)

func main() {
	pdf, err := os.ReadFile("pseb_cert.pdf")
	if err != nil {
		log.Fatalf("failed to read certificate: %v", err)
	}

	// Extract the QR code and decode the JWT it carries (offline).
	cert, err := pseb.ExtractCertificate(pdf)
	if err != nil {
		log.Fatalf("failed to extract certificate: %v", err)
	}

	fmt.Printf("Registration: %s\n", cert.RegistrationNumber)
	fmt.Printf("Type:         %s\n", cert.Type)
	fmt.Printf("Expires:      %s\n", cert.ExpiresAt.Format("2006-01-02"))

	// Verify the certificate against the PSEB portal.
	client := pseb.New()
	result, err := client.Verify(context.Background(), cert.JWT)
	if err != nil {
		log.Fatalf("failed to verify certificate: %v", err)
	}

	fmt.Printf("Registered to: %s (valid: %t)\n", result.Name, result.IsValid)
}

API

ExtractCertificate(pdf []byte) (*Certificate, error)

Reads a PSEB certificate PDF, decodes the QR code printed on it, and returns the Certificate it encodes. It extracts the images embedded in the PDF, decodes the first one that is a readable QR code, parses the verification URL to recover the JWT, and decodes the JWT's claims. Extraction is fully offline; the JWT signature is not checked here.

Returns ErrNoQRCode if no QR code can be decoded, or ErrNoJWT if the decoded QR code does not contain a JWT.

ExtractCNIC(pdf []byte) (string, error)

Reads a PSEB certificate PDF and returns the holder's 13-digit CNIC (the Pakistani national identity number printed on freelancer certificates). Unlike the data from ExtractCertificate, the CNIC is not in the QR code, the JWT, or the verification response — it appears only as printed text, so it's recovered from the PDF's text layer. Matching prefers a value following a CNIC label (accepting both 3520122720739 and 35201-2272073-9 forms, normalized to 13 digits) and falls back to any standalone run of exactly 13 digits.

Returns ErrNoCNIC if no CNIC can be found. This relies on the certificate having a real text layer (as current PSEB certificates do); it does not OCR image-only scans.

New(opts ...httpr.ClientOption) *Client

Creates a client that talks to the PSEB portal verification API. Defaults to the PSEB portal host; pass httpr options (e.g. httpr.Timeout(...), httpr.HTTPClient(...)) to customize HTTP behavior.

(c *Client) Verify(ctx context.Context, token string) (*VerificationResult, error)

Submits a certificate JWT to the PSEB portal and returns the certificate data the portal reports for it, including the registered entity's name and an authoritative IsValid flag. Requires network access.

Types
type Certificate struct {
	PSEBHostedVerificationURL string          `json:"pseb_hosted_verification_url"`
	JWT                       string          `json:"jwt"`
	RegistrationNumber        string          `json:"registration_number"`
	Type                      CertificateType `json:"type"`
	IssuedAt                  time.Time       `json:"issued_at"`
	ExpiresAt                 time.Time       `json:"expires_at"`
}

type VerificationResult struct {
	RegistrationNumber string          `json:"registration_number"`
	Type               CertificateType `json:"type"`
	Name               string          `json:"name"`
	IssuedAt           time.Time       `json:"issued_at"`
	ExpiresAt          time.Time       `json:"expires_at"`
	IsValid            bool            `json:"is_valid"`
}

Development

This repo uses hermit for tooling and just as a command runner.

just test   # run tests
just lint   # run golangci-lint
just docs   # run the docs site locally
Testing

Tests use go-vcr to record and replay HTTP interactions. Recorded cassettes live in fixtures/, so the suite runs offline by default. To re-record against the live PSEB endpoint, flip testMode to vcr.Record in the relevant test, run it once, then flip it back to vcr.Replay.

Documentation

Overview

Package pseb reads and verifies Pakistan Software Export Board (PSEB) registration certificates.

PSEB is the Pakistani government body that registers IT and IT-enabled services companies and freelancers as software exporters. Every PSEB registration certificate is issued as a PDF that carries a QR code. That QR code does not just link to a web page: it encodes a verification URL whose final path segment is a signed JSON Web Token (JWT). The JWT's claims contain the certificate's core data: the registration number, the registration type (company or freelancer), and issued-at / expiry timestamps.

This package provides three things:

  • ExtractCertificate reads the QR code out of a certificate PDF and returns the encoded verification URL, the raw JWT, and the claims decoded from it.
  • ExtractCNIC reads the holder's 13-digit CNIC from a certificate PDF's text layer. The CNIC is printed on freelancer certificates but is not carried in the QR code, the JWT claims, or the verification response.
  • Client.Verify submits a JWT to the PSEB portal's verification endpoint and returns the certificate data the portal reports for it, including the registered entity's name and whether the certificate is currently valid.

Trust model

PSEB certificates are signed with HS256, a symmetric (shared-secret) algorithm. Because the signing secret is held by PSEB and not published, the signature cannot be verified offline by third parties. This package therefore uses the JWT only to read claims locally; authoritative validity comes from Client.Verify, which calls the PSEB portal. (If PSEB ever switches to an asymmetric scheme such as EdDSA/ES256 and publishes a public key, offline verification would become possible.)

Extracting from a PDF

pdf, _ := os.ReadFile("pseb_cert.pdf")
cert, err := pseb.ExtractCertificate(pdf)
if err != nil {
	// no QR code, no JWT, or malformed token
}
fmt.Println(cert.RegistrationNumber, cert.Type, cert.JWTExpiresAt)

Reading the CNIC

pdf, _ := os.ReadFile("pseb_freelancer_cert.pdf")
cnic, err := pseb.ExtractCNIC(pdf)
if err != nil {
	// no CNIC in the PDF's text layer
}
fmt.Println(cnic) // 3520122720739

Verifying against the PSEB portal

client := pseb.New()
result, err := client.Verify(ctx, cert.JWT)
if err != nil {
	// transport error or the portal rejected the certificate
}
fmt.Println(result.Name, result.IsValid)

Index

Constants

View Source
const DefaultBaseURL = "https://api.techdestination.com"

DefaultBaseURL is the base URL of the PSEB portal API used to verify certificates. It is the default host a Client created with New targets.

Variables

View Source
var (
	// ErrNoQRCode is returned by [ExtractCertificate] when the PDF contains no
	// image that decodes as a QR code.
	ErrNoQRCode = errors.New("no QR code found in PDF")
	// ErrNoJWT is returned by [ExtractCertificate] when the QR code was decoded
	// but does not contain a JWT-shaped token.
	ErrNoJWT = errors.New("no JWT found in QR code")
	// ErrNoCNIC is returned by [ExtractCNIC] when no CNIC can be found in the
	// PDF's text layer.
	ErrNoCNIC = errors.New("no CNIC found in PDF")
)
View Source
var ErrCertificateInvalid = errors.New("PSEB reports certificate is not valid")

ErrCertificateInvalid is returned by Client.Verify when the PSEB portal reports that the certificate is not valid (its "isValid" flag is false). The accompanying VerificationResult is still returned and populated so callers can inspect the details PSEB reported.

Functions

func ExtractCNIC

func ExtractCNIC(pdf []byte) (string, error)

ExtractCNIC reads a PSEB certificate PDF and returns the holder's 13-digit CNIC (the Pakistani national identity number printed on freelancer certificates).

Unlike the data returned by ExtractCertificate, the CNIC is not encoded in the QR code, the JWT claims, or the PSEB verification response - it appears only as text printed on the certificate. It is therefore recovered from the PDF's text layer: each page's content streams are parsed for their text-showing operators (Tj / TJ) to reconstruct the visible text, and the CNIC is located within it.

Matching prefers a value that follows a "CNIC" label, accepting both the bare (3520122720739) and dash-separated (35201-2272073-9) forms and normalizing to 13 digits. If the label cannot be located it falls back to any standalone run of exactly 13 digits, which is unambiguous on a PSEB certificate (the registration number, phone, and fax never form 13 consecutive digits).

It returns ErrNoCNIC if no CNIC can be found. This relies on the certificate having a real text layer (as current PSEB certificates do); it does not perform OCR on image-only (scanned) PDFs.

Types

type Certificate

type Certificate struct {
	// PSEBHostedVerificationURL is the URL encoded in the certificate's QR code.
	// It points at the PSEB portal's public verification page for this
	// certificate and embeds the JWT as its final path segment, e.g.
	// https://portal.techdestination.com/verify-certificate/<jwt>.
	PSEBHostedVerificationURL string `json:"pseb_hosted_verification_url"`

	// JWT is the raw, compact JSON Web Token taken from the verification URL.
	// It is the signed credential that encodes the certificate's data and is the
	// value to pass to [Client.Verify].
	JWT string `json:"jwt"`

	// RegistrationNumber is the PSEB registration number assigned to the holder,
	// e.g. "Z-25-17156/25". This is the identifier printed on the certificate
	// that uniquely identifies the registered software exporter.
	RegistrationNumber string `json:"registration_number"`

	// Type is the registration type (company or freelancer).
	Type CertificateType `json:"type"`

	// IssuedAt is the JWT "iat" (issued-at) claim, normalized to UTC. It is when
	// the certificate's verification token was issued.
	IssuedAt time.Time `json:"issued_at"`

	// JWTExpiresAt is the JWT "exp" (expiry) claim, normalized to UTC. This is
	// the expiry of the verification token itself, which PSEB issues with a
	// short (~90-day) lifetime, and is NOT the end of the registration's
	// validity period. The registration's actual expiry is only reported by the
	// portal (see [VerificationResult.RegistrationExpiresAt]) and is not
	// available offline from the JWT alone.
	JWTExpiresAt time.Time `json:"jwt_expires_at"`
}

Certificate is a PSEB registration certificate as read from the QR code printed on the certificate PDF. The fields below come from the QR's verification URL and the claims of the signed JWT it carries; they are not independently verified (see Client.Verify for that).

func ExtractCertificate

func ExtractCertificate(pdf []byte) (*Certificate, error)

ExtractCertificate reads a PSEB certificate PDF, decodes the QR code printed on it, and returns the Certificate it encodes.

It extracts the images embedded in the PDF, decodes the first one that is a readable QR code, parses the verification URL to capture the JWT, and decodes the JWT's claims to populate the registration number, type, and timestamps. The JWT signature is not checked here; use Client.Verify to confirm a certificate's authenticity and current validity with PSEB.

PSEB embeds the QR as a very small, full-bleed raster (roughly two pixels per module, with no surrounding quiet zone). Off-the-shelf QR readers cannot lock onto a symbol that small at any scale, so decoding falls back to reconstructing the module grid directly; see [decodeQRFromImage] for details.

It returns ErrNoQRCode if no QR code can be decoded from the PDF, or ErrNoJWT if the decoded QR code does not contain a JWT.

type CertificateType

type CertificateType string

CertificateType identifies the kind of PSEB registration a certificate represents. PSEB registers two kinds of software exporters: companies and freelancers. The value mirrors the "type" claim in the certificate JWT verbatim, so it is safe to compare against the constants below but may hold an unrecognized value if PSEB introduces new types.

const (
	// CertificateTypeCompany is a PSEB registration issued to a company
	// (a registered legal entity, e.g. a "(PRIVATE) LIMITED" company).
	CertificateTypeCompany CertificateType = "company"
	// CertificateTypeFreelancer is a PSEB registration issued to an individual
	// software exporter (a freelancer).
	CertificateTypeFreelancer CertificateType = "freelancer"
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client verifies PSEB certificates against the PSEB portal's verification endpoint. It is safe for concurrent use and should be reused across calls.

func New

func New(opts ...httpr.ClientOption) *Client

New creates a Client that talks to the PSEB portal verification API.

By default it targets DefaultBaseURL. Pass httpr client options to customize the underlying HTTP behavior, for example httpr.BaseURL(...) to point at a different host or httpr.HTTPClient(...) to supply a custom *http.Client (e.g. one with a timeout, or a recording transport in tests).

func (*Client) Verify

func (c *Client) Verify(ctx context.Context, token string) (*VerificationResult, error)

Verify submits a PSEB certificate JWT to the PSEB portal and returns the certificate data the portal reports for it.

The token is the compact JWT extracted from a certificate's QR code (see Certificate.JWT). Verify first checks that the token is a well-formed JWT, then POSTs it to the portal's verification endpoint. Because PSEB certificates are signed with a secret only PSEB holds, this network call is what authoritatively establishes a certificate's authenticity and validity; the returned VerificationResult.IsValid reflects PSEB's own determination.

It returns an error if the token is not a valid JWT, if the request fails, or if the portal responds with a non-2xx status. If the portal responds successfully but reports the certificate as not valid, Verify returns the populated result together with ErrCertificateInvalid; match it with errors.Is to distinguish an invalid certificate from a transport failure.

type VerificationResult

type VerificationResult struct {
	// RegistrationNumber is the PSEB registration number of the holder,
	// e.g. "Z-25-17156/25".
	RegistrationNumber string `json:"registration_number"`

	// Type is the registration type (company or freelancer).
	Type CertificateType `json:"type"`

	// Name is the registered name of the software exporter as recorded by PSEB,
	// e.g. the legal company name "OUTSENTIA (PRIVATE) LIMITED". This is not
	// present in the certificate JWT and is only available via verification.
	Name string `json:"name"`

	// IssuedAt is the JWT "iat" (issued-at) timestamp, normalized to UTC. Note
	// this is when the certificate's token was issued, which may differ from the
	// start of the registration's validity window (see ValidFrom).
	IssuedAt time.Time `json:"issued_at"`

	// JWTExpiresAt is the JWT "exp" timestamp, normalized to UTC. This is the
	// expiry of the certificate's verification token (PSEB issues it with a
	// short, ~90-day lifetime), NOT the end of the registration's validity
	// period. For the registration's actual expiry use RegistrationExpiresAt.
	JWTExpiresAt time.Time `json:"jwt_expires_at"`

	// ValidFrom is the start of the registration's validity window as reported
	// by PSEB, a coarse human-readable label such as "May 2026". It is empty if
	// the portal did not report it.
	ValidFrom string `json:"valid_from"`

	// ValidTill is the end of the registration's validity window as reported by
	// PSEB, a coarse human-readable label such as "Apr 2027". This is the
	// certificate's real expiry (distinct from the token's JWTExpiresAt) and is
	// empty if the portal did not report it.
	ValidTill string `json:"valid_till"`

	// RegistrationExpiresAt is ValidTill parsed into a timestamp, normalized to
	// UTC. PSEB only reports month granularity, so this is set to the first
	// instant of the reported month (e.g. "Apr 2027" becomes 2027-04-01
	// 00:00:00 UTC). It is the zero time if ValidTill is empty or unparseable.
	RegistrationExpiresAt time.Time `json:"registration_expires_at"`

	// IsValid reports whether PSEB considers the certificate currently valid.
	// This is the authoritative validity signal: it reflects PSEB's own check
	// (signature and expiry) rather than any local inspection of the JWT.
	IsValid bool `json:"is_valid"`
}

VerificationResult is the certificate data the PSEB portal returns when a certificate JWT is verified. Unlike the claims read locally from the JWT in Certificate, these values are reported by PSEB and include the registered entity's name and an authoritative validity flag.

Directories

Path Synopsis
cmd
gensample command
Command gensample generates synthetic PSEB certificate PDFs used as test fixtures.
Command gensample generates synthetic PSEB certificate PDFs used as test fixtures.
pseb command
Command pseb reads a PSEB registration certificate PDF, prints the data encoded in its QR code, and verifies it against the PSEB portal.
Command pseb reads a PSEB registration certificate PDF, prints the data encoded in its QR code, and verifies it against the PSEB portal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL