gokalkan

package module
v0.0.0-...-f513524 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 26 Imported by: 0

README

gokalkan

pkg-img reportcard-img

Пакет gokalkan является оберткой над нативными методами библиотеки KalkanCrypt.

KalkanCrypt является основной библиотекой для работы с ЭЦП ключами от pki.gov.kz и позволяет подписывать, верифицировать, вытаскивать данные и много чего другого связанного с электронными подписями, цифровыми сертификатами и ключами.

Особенности пакета gokalkan:

  • Поддержка мультипоточности
  • Без зависимостей
  • Чистый код
  • Напрямую вызывает нативные методы KalkanCrypt

Перед использованием

Чтобы использовать библиотеку требуется провести подготовку:

1. Получить SDK

Обратиться в pki.gov.kz чтобы получить SDK. SDK представляет из себя набор библиотек для Java и C.

2. Установить CA сертификаты

Сертификаты лежат по пути SDK/C/Linux/ca-certs/Ubuntu. В папке находятся два типа сертификатов - production и test. Для их установки приготовлены скрипты в той же директории. При запуске понадобятся sudo права.

3. Скопировать .so файлы

Файлы лежат в директории SDK 2.0/C/Linux/C/libs/v2.0.4. Команда для копирования:

sudo cp -f libkalkancryptwr-64.so.2.0.4 /usr/lib/libkalkancryptwr-64.so

Установка

Версия Go 1.17+

go get github.com/gokalkan/gokalkan

Загрузка хранилища PKCS12

Загрузка хранилища с ключом и сертификатом (например ЭЦП ключ, который начинается с RSA...):

package main

import (
	"fmt"
	"log"

	"github.com/gokalkan/gokalkan"
)

var (
	certPath = "test_cert/GOSTKNCA.p12" // путь к хранилищу

	certPassword = "Qwerty12" // пароль
	// P.S. никогда не храните пароли в коде
)

func main() {
	// для теста
	opts := gokalkan.OptsTest

	// для прода
	// opts := gokalkan.OptsProd

	cli, err := gokalkan.NewClient(opts...)
	if err != nil {
		log.Fatal("new kalkan client create error", err)
	}
	// Обязательно закрывайте клиент, иначе приведет к утечкам ресурсов
	defer cli.Close()

	// Подгружаем хранилище с паролем
	err = cli.LoadKeyStore(certPath, certPassword)
	if err != nil {
		log.Fatal("load key store error", err)
	}
}

Следует отметить, что при инициализации gokalkan клиента нужно указывать опцию. Есть две опции - OptsTest и OptsProd. Нужно выбрать одну из них в зависимости от окружения.

Подпись XML документа

Для того чтобы подписать XML документ, нужно передать документ в виде строки:

signedXML, err := cli.SignXML("<root>GoKalkan</root>")

fmt.Println("Подписанный XML", signedXML)
fmt.Println("Ошибка", err)
Проверка подписи на XML документе

Проверка подписи документа вернет ошибку, если документ подписан неверно либо срок у сертификата с которым подписан истёк.

serial, err := cli.VerifyXML(signedXML)

fmt.Println("Серийный номер", serial)
fmt.Println("Ошибка", err)
Подпись XML документа для SmartBridge

Для того чтобы подписать XML документ в формате SignWSSE, нужно передать документ в виде строки и id для SOAP Body. Функция обернет документ в soap:Envelope и запишет внутри soap:Body.

signedXML, err := cli.SignWSSE("<root>gokalkan</root>", "12345")

fmt.Println("Подписанный XML в формате WSSE", signedXML)
fmt.Println("Ошибка", err)
Получение времени когда был подписан документ 2 подписантом
signTime, err := c.GetTimeFromSig(cmsFileBody, 1)

fmt.Println("Время подписи", signTime)
fmt.Println("Ошибка", err)
Получение значение поля из сертификата

Внимание! Сертификат должен быть в формате PEM.

value, err := c.X509CertificateGetInfo(certBody, ckalkan.CertPropSubjectCommonName)

fmt.Println("Значение", value)
fmt.Println("Ошибка", err)
Получение сводной информации о сертификате

Внимание! Сертификат должен быть в формате PEM.

info, err := c.X509CertificateGetSummary(certBody)

fmt.Println("Информация", info)
fmt.Println("Ошибка", err)

Бенчмарки

Команда запуска бенчмарка:

go test -bench SignXML -run=^$ -benchmem

Характеристики хост машины:

  • goos: linux
  • goarch: amd64
  • cpu: Intel(R) Core(TM) i5-8500 CPU @ 3.00GHz
Бенчмарк Кол-во циклов Средн. время выполнения Средн. потребление ОЗУ Средн. кол-во аллокаций
BenchmarkSignXML-6 2809 422310 ns/op 2792 B/op 8 allocs/op

Контрибьютеры ✨


Zulbukharov Abylaikhan

Tlekbai Ali

Kilibayev Azat

Лицензия

The MIT License (MIT) 2021 - Abylaikhan Zulbukharov.

Please have a look at the LICENSE.md for more details.

Documentation

Index

Constants

View Source
const (
	CertTypeIndividual   = "individual"
	CertTypeOrganization = "organization"
)
View Source
const (
	RevocationTypeOCSP RevocationType = "OCSP"
	RevocationTypeCRL  RevocationType = "CRL"

	KeyUsageUnknown KeyUsage = "UNKNOWN"
	KeyUsageAuth    KeyUsage = "AUTH"
	KeyUsageSign    KeyUsage = "SIGN"
)

Variables

View Source
var (
	OptsProd = []Option{
		WithLogger(defaultLogger),
		WithTSP(prodTSP),
		WithOCSP(prodOCSP),
	}
	OptsTest = []Option{
		WithLogger(defaultLogger),
		WithTSP(testTSP),
		WithOCSP(testOCSP),
	}
)
View Source
var (
	ErrInit    = errors.New("unable to refer to KC_GetFunctionList")
	ErrHTTPCli = errors.New("http cli error")
)
View Source
var ErrInvalidJWT = errors.New("invalid jwt format")

ErrInvalidJWT возвращается, если строка токена не соответствует формату JWT (header.payload.signature).

View Source
var ErrLoadKey = errors.New("load key error")
View Source
var ErrNoPDFSignatures = errors.New("gokalkan: pdf document contains no digital signatures")

ErrNoPDFSignatures возвращается VerifyPDF, если в документе нет ни одной подписи (аналог NoSignaturesFoundException в NCANode).

Functions

func TrimXML

func TrimXML(xmlData string) (string, error)

TrimXML удаляет текстовые узлы, состоящие только из пробельных символов (аналог NCANode XmlService.removeWhitespace). Экспортировано отдельно от SignXMLWithOptions.TrimXML для использующих SignWSSE - тот принимает не полный документ, а фрагмент (см. SignWSSE), и оборачивает его в SOAP конверт сам, поэтому подрезать пробелы нужно до вызова SignWSSE.

func WrapWithWSSESoapEnvelope

func WrapWithWSSESoapEnvelope(dataXML, id string) (result string)

WrapWithWSSESoapEnvelope оборачивает XML документ в SOAP формат, а точнее записывает содержимое под тегом soap:Body

Types

type CertIssuer

type CertIssuer struct {
	CommonName string
	Country    string
	DN         string
}

type CertOrganization

type CertOrganization struct {
	Name        string
	BIN         string
	SubjectRole CertSubjectRole
}

type CertSubject

type CertSubject struct {
	CommonName string
	LastName   string
	Country    string
	IIN        string
	DN         string
}

type CertSubjectRole

type CertSubjectRole int
const (
	CertSubjectRoleUndefined CertSubjectRole = iota
	CertSubjectRoleCEO
	CertSubjectRoleSign
	CertSubjectRoleSignFinance
	CertSubjectRoleHR
	CertSubjectRoleEmployee
)

type CertType

type CertType string

type CertificateInfo

type CertificateInfo struct {
	Valid       bool
	Revocations []Revocation

	NotBefore time.Time
	NotAfter  time.Time

	KeyUsage KeyUsage
	// KeyUser - набор меток назначения сертификата: "INDIVIDUAL" или "ORGANIZATION",
	// и для организации дополнительно роль подписанта ("CEO", "SIGN", "SIGN_FINANCE",
	// "HR", "EMPLOYEE"). Кроме "INDIVIDUAL"/"ORGANIZATION"/"CEO" (сверено с NCANode)
	// остальные названия ролей - по аналогии, не сверялись с эталоном напрямую.
	KeyUser []string

	SerialNumber string
	SignAlg      string
	PublicKey    string
	// Signature - "сырое" значение подписи сертификата (BIT STRING из
	// Certificate.signatureValue), в base64. Извлекается обычным ASN.1-разбором
	// внешней структуры X.509 (не зависит от алгоритма подписи, GOST в том числе).
	Signature string

	Subject      CertSubject
	Organization *CertOrganization
	Issuer       CertIssuer
}

CertificateInfo - расширенная информация о сертификате, аналог CertificateInfo в NCANode (Java): помимо разбора полей (см. Summary/X509CertificateGetSummary) содержит результат проверки отозванности и агрегированную валидность.

type Client

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

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient возвращает клиента для работы с KC.

func (*Client) AddSigner

func (cli *Client) AddSigner(cms, data []byte, isDetached, withTSP bool) (signature []byte, err error)

AddSigner дописывает ещё одну подпись в уже существующий CMS (со-подпись). Для этого перед вызовом обычно нужно загрузить другой ключ через LoadKeyStore. Существующие подписи (и их метки времени) не затрагиваются - TSP (если withTSP) прикладывается только к новой подписи.

func (*Client) Close

func (cli *Client) Close() (err error)

func (*Client) ExtractCMS

func (cli *Client) ExtractCMS(cms []byte) ([]byte, error)

ExtractCMS возвращает данные, вложенные в attached (не detached) CMS, без проверки подписи по отдельному источнику данных.

func (*Client) GetCertFromCMS

func (cli *Client) GetCertFromCMS(cms []byte, signID int) (string, error)

GetCertFromCMS обеспечивает получение сертификата из CMS.

func (*Client) GetCertFromXML

func (cli *Client) GetCertFromXML(xml string, signID int) ([]byte, error)

GetCertFromXML обеспечивает получение сертификата из XML.

func (*Client) GetCertificateInfo

func (cli *Client) GetCertificateInfo(cert string, checkOCSP bool, crlPath string) (*CertificateInfo, error)

GetCertificateInfo возвращает расширенную информацию о сертификате, включая результат проверки отозванности. checkOCSP включает проверку через OCSP (URL берётся из опций клиента, см. WithOCSP/OptsTest/OptsProd). crlPath, если не пустой, включает проверку по локальному файлу CRL (см. ValidateCertCRL).

func (*Client) GetTimeFromSig

func (cli *Client) GetTimeFromSig(signature []byte, signID int) (time.Time, error)

func (*Client) HashGOST95

func (cli *Client) HashGOST95(data []byte) (hashed []byte, err error)

func (*Client) HashSHA256

func (cli *Client) HashSHA256(data []byte) (hashed []byte, err error)

func (*Client) ListCertificateAliases

func (cli *Client) ListCertificateAliases() ([]string, error)

ListCertificateAliases возвращает alias'ы сертификатов/ключей, загруженных текущим вызовом LoadKeyStore/LoadKeyStoreFromBytes (см. KC_GetCertificatesList).

Для типичного PKCS12 с одним ключом возвращает один alias. Поддержка нескольких alias'ов внутри одного PKCS12 не подтверждена вживую (в SDK-примере KC_GetCertificatesList используется только для аппаратных токенов), но сама функция не привязана к типу хранилища - ожидаем, что она корректно перечислит и несколько ключей в PKCS12, если LoadKeyStore (alias="") их все загружает.

func (*Client) LoadCertFromBytes

func (cli *Client) LoadCertFromBytes(cert []byte, certType ckalkan.CertType) (err error)

func (*Client) LoadCerts

func (cli *Client) LoadCerts() error

func (*Client) LoadKeyStore

func (cli *Client) LoadKeyStore(path, password string, alias ...string) error

LoadKeyStore загружает PKCS12. alias - опциональный alias ключа/сертификата внутри контейнера (для PKCS12 с несколькими ключами, см. ListCertificateAliases); при отсутствии передаётся "" - тот же дефолт, что был раньше.

func (*Client) LoadKeyStoreFromBytes

func (cli *Client) LoadKeyStoreFromBytes(key []byte, password string, alias ...string) (err error)

LoadKeyStoreFromBytes загружает PKCS12. alias - см. LoadKeyStore.

func (*Client) Options

func (cli *Client) Options() Options

func (*Client) Sign

func (cli *Client) Sign(data []byte, isDetached, withTSP bool) (signature []byte, err error)

Sign подписывает данные и возвращает CMS с подписью.

func (*Client) SignJWT

func (cli *Client) SignJWT(claims map[string]any, alg string) (string, error)

SignJWT формирует и подписывает JWT текущим загруженным ключом (см. LoadKeyStore/LoadKeyStoreFromBytes). alg - идентификатор алгоритма подписи в терминах NCANode/kz.gov.pki java-jwt: "GG2015"/"GG2004" для GOST-ключей, "RS256"/"RS384"/"RS512"/"ES256"/"ES384"/"ES512" для остальных - должен соответствовать алгоритму загруженного ключа.

func (*Client) SignPDF

func (cli *Client) SignPDF(pdfData []byte, opts PDFSignOptions) ([]byte, error)

SignPDF подписывает PDF документ (CAdES-detached, incremental update). Текущий загруженный ключ (см. LoadKeyStore/LoadKeyStoreFromBytes) используется как подписант.

func (*Client) SignRaw

func (cli *Client) SignRaw(data []byte) (signature []byte, err error)

SignRaw подписывает произвольные данные текущим загруженным ключом (см. LoadKeyStore/LoadKeyStoreFromBytes) и возвращает "сырую" подпись - без CMS-обёртки, в отличие от Sign. Аналог /x509/sign в NCANode.

func (*Client) SignWSSE

func (cli *Client) SignWSSE(xmlData, id string) (string, error)

SignWSSE подписывает документ в формате WS-Security (SmartBridge). xmlData - содержимое soap:Body (не полный конверт, конверт строится автоматически).

func (*Client) SignXML

func (cli *Client) SignXML(xmlData string) (string, error)

SignXML подписывает данные в формате XML.

func (*Client) SignXMLWithOptions

func (cli *Client) SignXMLWithOptions(xmlData string, opts XMLSignOptions) (string, error)

SignXMLWithOptions - расширенная версия SignXML с поддержкой опций, аналогичных NCANode (/xml/sign): referenceUri, clearSignatures, trimXml. Для нескольких подписантов вызывайте последовательно (загружая между вызовами очередной ключ через LoadKeyStore) - каждый новый вызов добавляет ещё один ds:Signature, не трогая предыдущие.

func (*Client) Underlying

func (cli *Client) Underlying() *ckalkan.Client

Underlying возвращает текущий ckalkan.Client, используемый клиентом gokalkan.

func (*Client) ValidateCert

func (cli *Client) ValidateCert(cert string) (string, error)

func (*Client) ValidateCertCRL

func (cli *Client) ValidateCertCRL(cert, crlPath string) (string, error)

ValidateCertCRL проверяет сертификат по списку отозванных сертификатов (CRL). crlPath - путь к локальному файлу CRL (нативная библиотека не умеет скачивать CRL самостоятельно, файл нужно предварительно загрузить, см. например test.pki.gov.kz/crl/nca_gost2022_test.crl для тестового окружения).

func (*Client) ValidateCertOCSP

func (cli *Client) ValidateCertOCSP(cert string, url ...string) (string, error)

func (*Client) Verify

func (cli *Client) Verify(signature []byte) (string, error)

Verify обеспечивает проверку подписи CMS в base64.

func (*Client) VerifyDetached

func (cli *Client) VerifyDetached(signature, data []byte) (string, error)

VerifyDetached обеспечивает проверку отделенной подписи CMS (detached signature) в base64.

func (*Client) VerifyJWT

func (cli *Client) VerifyJWT(token, cert string) (claims map[string]any, err error)

VerifyJWT проверяет подпись JWT по сертификату cert (PEM) и возвращает разобранные claims. Не проверяет стандартные временные claims (exp/nbf) - это ответственность вызывающего кода.

func (*Client) VerifyPDF

func (cli *Client) VerifyPDF(pdfData []byte) (allValid bool, signers []PDFSignerInfo, err error)

VerifyPDF проверяет все подписи в PDF документе (по /ByteRange и /Contents каждого поля подписи). Возвращает ErrNoPDFSignatures, если подписей нет.

func (*Client) VerifyRaw

func (cli *Client) VerifyRaw(data, signature []byte, cert string) (result string, err error)

VerifyRaw проверяет "сырую" подпись (см. SignRaw) данных по сертификату cert (PEM). Аналог /x509/verify в NCANode.

func (*Client) VerifyWSSE

func (cli *Client) VerifyWSSE(signedXML string) (string, error)

VerifyWSSE проверяет подпись SOAP-конверта, подписанного SignWSSE. Нативная проверка XML-подписи (VerifyXML) сама резолвит сертификат подписанта через wsse:KeyIdentifier в KeyInfo, поэтому отдельной реализации не требуется - это тонкий алиас для симметрии с SignWSSE/Java NCANode (/wsse/verify).

func (*Client) VerifyXML

func (cli *Client) VerifyXML(signedXML string) (result string, err error)

VerifyXML обеспечивает проверку подписи данных в формате XML.

func (*Client) X509CertificateGetInfo

func (cli *Client) X509CertificateGetInfo(cert string, prop ckalkan.CertProp) (string, error)

func (*Client) X509CertificateGetSummary

func (cli *Client) X509CertificateGetSummary(cert string) (*Summary, error)

X509CertificateGetSummary возвращает информацию о сертификате. Используйте только после Verify, если используется для проверки подписей

func (*Client) X509ExportCertificateFromStore

func (cli *Client) X509ExportCertificateFromStore(alias string) (string, error)

X509ExportCertificateFromStore экспортирует сертификат текущего загруженного (через LoadKeyStore/LoadKeyStoreFromBytes) ключа. alias обычно "" - тот же, что передавался при загрузке хранилища.

type Kalkan

type Kalkan interface {
	LoadKeyStore(path, password string, alias ...string) (err error)
	LoadKeyStoreFromBytes(key []byte, password string, alias ...string) (err error)
	ListCertificateAliases() ([]string, error)

	Sign(data []byte, isDetached, withTSP bool) (signature []byte, err error)
	AddSigner(cms, data []byte, isDetached, withTSP bool) (signature []byte, err error)
	SignRaw(data []byte) (signature []byte, err error)
	VerifyRaw(data, signature []byte, cert string) (string, error)

	SignJWT(claims map[string]any, alg string) (string, error)
	VerifyJWT(token, cert string) (claims map[string]any, err error)

	SignPDF(pdfData []byte, opts PDFSignOptions) ([]byte, error)
	VerifyPDF(pdfData []byte) (allValid bool, signers []PDFSignerInfo, err error)
	SignXML(xml string) (signedXML string, err error)
	SignXMLWithOptions(xmlData string, opts XMLSignOptions) (string, error)
	SignWSSE(xml, id string) (signedXML string, err error)

	Verify(signature []byte) (string, error)
	VerifyXML(signedXML string) (string, error)
	VerifyWSSE(signedXML string) (string, error)
	VerifyDetached(signature, data []byte) (string, error)
	ExtractCMS(cms []byte) ([]byte, error)

	GetCertFromCMS(cms []byte, signID int) (string, error)
	GetCertFromXML(xml string, signID int) ([]byte, error)

	ValidateCert(cert string) (string, error)
	ValidateCertOCSP(cert string, url ...string) (string, error)
	ValidateCertCRL(cert, crlPath string) (string, error)

	X509CertificateGetInfo(inCert string, prop ckalkan.CertProp) (string, error)
	X509ExportCertificateFromStore(alias string) (string, error)

	HashSHA256(data []byte) ([]byte, error)
	HashGOST95(data []byte) ([]byte, error)
	Close() error
}

Kalkan - это обертка над методами KalkanCrypt.

type KeyUsage

type KeyUsage string

KeyUsage - упрощённое (в терминах NCA) назначение ключа сертификата. Это НЕ побитовая семантика стандартного X.509 KeyUsage, а бизнес-классификация, которую использует kz.gov.pki-экосистема: сертификат либо для аутентификации, либо для подписи.

type Logger

type Logger interface {
	Debug(args ...interface{})
	Error(args ...interface{})
}

type Option

type Option func(o *Options)

func WithCert

func WithCert(cert *x509.Certificate, typ ckalkan.CertType) Option

func WithCerts

func WithCerts(c []OptionsCert) Option

func WithLogger

func WithLogger(logger Logger) Option

func WithOCSP

func WithOCSP(u string) Option

func WithRemoteProdCerts

func WithRemoteProdCerts(ctx context.Context) Option

func WithRemoteTestCerts

func WithRemoteTestCerts(ctx context.Context) Option

func WithTSP

func WithTSP(u string) Option

type Options

type Options struct {
	TSP       string        `json:"tsp"`   // URL метки времени
	OCSP      string        `json:"ocsp"`  // URL сервиса онлайн проверки статуса сертификата
	Certs     []OptionsCert `json:"certs"` // Корневые сертификатам
	LoadCerts bool          `json:"load_certs"`
	// contains filtered or unexported fields
}

type OptionsCert

type OptionsCert struct {
	Cert *x509.Certificate
	Type ckalkan.CertType
}

type PDFSignOptions

type PDFSignOptions struct {
	Location    string
	Reason      string
	ContactInfo string
	WithTSP     bool
}

PDFSignOptions - опции подписи PDF, аналог PdfSignRequest.PdfSigner в NCANode.

type PDFSignerInfo

type PDFSignerInfo struct {
	Valid bool
	// Reason/Location/ContactInfo - nil, если соответствующий ключ подписи
	// отсутствует в PDF (не то же самое, что пустая строка - см. IsNull() у
	// pdf.Value), указатель на "" - если ключ присутствует, но пуст.
	Reason      *string
	Location    *string
	ContactInfo *string
	SignDate    time.Time
	// VerifyInfo - человекочитаемый отчёт нативной проверки (см. Verify/
	// VerifyDetached), пусто если Valid == false из-за ошибки более
	// раннего этапа (например, битые /ByteRange).
	VerifyInfo string
	// SubFilter - значение /SubFilter словаря подписи (например
	// "ETSI.CAdES.detached") - формат самой подписи на уровне PDF.
	SubFilter string
	// CMS - сырые байты подписи (значение /Contents, detached CMS) - для
	// извлечения сертификата подписанта см. Client.GetCertFromCMS.
	CMS []byte
}

PDFSignerInfo - информация об одной подписи в PDF документе.

type Revocation

type Revocation struct {
	Type    RevocationType
	Revoked bool
	Reason  string
	// RevokedAt - точное время отзыва, если revoked=true и его удалось
	// извлечь из текстового отчёта нативной библиотеки (X509ValidateCertificate
	// возвращает секцию "Revocation Time: DD.MM.YYYY HH:MM:SS +HH:MM" при
	// проверке через OCSP - см. revokedAtRe). Zero value, если не найдено.
	RevokedAt time.Time
}

Revocation - результат одной проверки отозванности (OCSP или CRL).

type RevocationType

type RevocationType string

RevocationType - способ проверки отозванности сертификата.

type Summary

type Summary struct {
	Type         CertType
	Subject      CertSubject
	Organization *CertOrganization
	Issuer       CertIssuer
	PublicKey    string
	SerialNumber string
	NotAfter     time.Time
	NotBefore    time.Time
}

type XMLSignOptions

type XMLSignOptions struct {
	// ReferenceURI - id тэга, который нужно подписать (Reference URI="#id").
	// Пусто - подписывается документ целиком (Reference URI="").
	ReferenceURI string
	// ClearSignatures - удалить существующие ds:Signature перед подписью.
	ClearSignatures bool
	// TrimXML - удалить текстовые узлы, состоящие только из пробельных
	// символов, перед подписью (влияет на канонизированные байты).
	TrimXML bool
}

XMLSignOptions - опции подписи XML, аналог полей XmlSignRequest в NCANode.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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