+
Skip to content
Merged
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,36 @@ fmt.Println("Подписанный XML в формате WSSE", signedXML)
fmt.Println("Ошибка", err)
```

### Получение времени когда был подписан документ 2 подписантом

```go
signTime, err := c.GetTimeFromSig(cmsFileBody, 1)

fmt.Println("Время подписи", signTime)
fmt.Println("Ошибка", err)
```

### Получение значение поля из сертификата
Внимание! Сертификат должен быть в формате PEM.

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

fmt.Println("Значение", value)
fmt.Println("Ошибка", err)
```

### Получение сводной информации о сертификате
Внимание! Сертификат должен быть в формате PEM.

```go
info, err := c.X509CertificateGetSummary(certBody)

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


## Бенчмарки

Команда запуска бенчмарка:
Expand Down
214 changes: 214 additions & 0 deletions cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package gokalkan

import (
"github.com/gokalkan/gokalkan/ckalkan"
"strings"
"time"
)

// see: https://adilet.zan.kz/rus/docs/V2000021440
const (
oidSubjectIndividual = "1.2.398.3.3.4.1.1"
oidSubjectRoleCEO = "1.2.398.3.3.4.1.2.1"
oidSubjectRoleSign = "1.2.398.3.3.4.1.2.2"
oidSubjectRoleSignFinance = "1.2.398.3.3.4.1.2.3"
oidSubjectRoleHR = "1.2.398.3.3.4.1.2.4"
oidSubjectRoleEmployee = "1.2.398.3.3.4.1.2.5"
)

const timeLayout = "02.01.2006 15:04:05 -07:00"

func (cli *Client) X509CertificateGetInfo(cert string, prop ckalkan.CertProp) (string, error) {
return cli.kc.X509CertificateGetInfo(cert, prop)
}

// X509CertificateGetSummary возвращает информацию о сертификате.
// Используйте только после Verify, если используется для проверки подписей
func (cli *Client) X509CertificateGetSummary(cert string) (*Summary, error) {
var (
summary = Summary{}
err error
)

if summary.Subject.CommonName, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectCommonName,
); err != nil {
return nil, err
}

summary.Subject.CommonName = cleanupValue(summary.Subject.CommonName, "=")

if summary.Subject.LastName, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectGivenName,
); err != nil {
return nil, err
}

summary.Subject.LastName = cleanupValue(summary.Subject.LastName, "=")

if summary.Subject.Country, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectCountryName,
); err != nil {
return nil, err
}

summary.Subject.Country = cleanupValue(summary.Subject.Country, "=")

if summary.Subject.IIN, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectSerialNumber,
); err != nil {
return nil, err
}

summary.Subject.IIN = cleanupValue(summary.Subject.IIN, "IIN")

if summary.Subject.DN, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectDN,
); err != nil {
return nil, err
}

ekum := make(map[string]bool)
extKeyUsage := ""

if extKeyUsage, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropExtKeyUsage,
); err != nil {
return nil, err
}

entries := strings.Split(extKeyUsage, ";")

for _, entry := range entries {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}

if value, found := extractValueInBrackets(entry); found {
ekum[value] = true
}
}

if _, found := ekum[oidSubjectIndividual]; found {
summary.Type = CertTypeIndividual
} else {
summary.Type = CertTypeOrganization
}

if summary.Type == CertTypeOrganization {
summary.Organization = &CertOrganization{}

if summary.Organization.Name, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectOrgName,
); err != nil {
return nil, err
}

summary.Organization.Name = cleanupValue(summary.Organization.Name, "=")

if summary.Organization.BIN, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropSubjectOrgUnitName,
); err != nil {
return nil, err
}

summary.Organization.BIN = cleanupValue(summary.Organization.BIN, "BIN")

if _, exists := ekum[oidSubjectRoleCEO]; exists {
summary.Organization.SubjectRole = CertSubjectRoleCEO
} else if _, exists := ekum[oidSubjectRoleSign]; exists {
summary.Organization.SubjectRole = CertSubjectRoleSign
} else if _, exists := ekum[oidSubjectRoleSignFinance]; exists {
summary.Organization.SubjectRole = CertSubjectRoleSignFinance
} else if _, exists := ekum[oidSubjectRoleHR]; exists {
summary.Organization.SubjectRole = CertSubjectRoleHR
} else if _, exists := ekum[oidSubjectRoleEmployee]; exists {
summary.Organization.SubjectRole = CertSubjectRoleEmployee
}
}

if summary.Issuer.CommonName, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropIssuerCommonName,
); err != nil {
return nil, err
}

summary.Issuer.CommonName = cleanupValue(summary.Issuer.CommonName, "=")

if summary.Issuer.Country, err = cli.kc.X509CertificateGetInfo(
cert, ckalkan.CertPropIssuerCountryName,
); err != nil {
return nil, err
}

summary.Issuer.Country = cleanupValue(summary.Issuer.Country, "=")

if summary.Issuer.DN, err = cli.kc.X509CertificateGetInfo(
cert,
ckalkan.CertPropIssuerDN,
); err != nil {
return nil, err
}

if summary.PublicKey, err = cli.kc.X509CertificateGetInfo(
cert,
ckalkan.CertPropPubKey,
); err != nil {
return nil, err
}

if summary.SerialNumber, err = cli.kc.X509CertificateGetInfo(
cert,
ckalkan.CertPropCertCN,
); err != nil {
return nil, err
}

if parts := strings.Split(summary.SerialNumber, "="); len(parts) == 2 {
summary.SerialNumber = parts[1]
}

if notAfter, err := cli.kc.X509CertificateGetInfo(
cert,
ckalkan.CertPropNotAfter,
); err != nil {
return nil, err
} else if part := strings.Split(notAfter, "="); len(part) == 2 {
if summary.NotAfter, err = time.Parse(timeLayout, part[1]); err != nil {
return nil, err
}
}

if notBefore, err := cli.kc.X509CertificateGetInfo(
cert,
ckalkan.CertPropNotBefore,
); err != nil {
return nil, err
} else if part := strings.Split(notBefore, "="); len(part) == 2 {
if summary.NotBefore, err = time.Parse(timeLayout, part[1]); err != nil {
return nil, err
}
}

return &summary, nil
}

func extractValueInBrackets(entry string) (string, bool) {
start := strings.Index(entry, "(")
end := strings.LastIndex(entry, ")")

if start != -1 && end != -1 && start < end {
return strings.TrimSpace(entry[start+1 : end]), true
}

return "", false
}

func cleanupValue(value string, s string) string {
if parts := strings.Split(value, s); len(parts) == 2 {
return strings.TrimSpace(parts[1])
}

return value
}
49 changes: 49 additions & 0 deletions cert_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package gokalkan

import "time"

const (
CertTypeIndividual = "individual"
CertTypeOrganization = "organization"
)

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

type (
Summary struct {
Type CertType
Subject CertSubject
Organization *CertOrganization
Issuer CertIssuer
PublicKey string
SerialNumber string
NotAfter time.Time
NotBefore time.Time
}
CertType string
CertSubjectRole int
CertSubject struct {
CommonName string
LastName string
Country string
IIN string
DN string
}
CertOrganization struct {
Name string
BIN string
SubjectRole CertSubjectRole
}
CertIssuer struct {
CommonName string
Country string
DN string
}
)
58 changes: 58 additions & 0 deletions ckalkan/get_time_from_sig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ckalkan

// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
// #include "KalkanCrypt.h"
//
// unsigned long GetTimeFromSig(char *inData, int inDataLength, int flags, int inSigId, time_t *outDateTime) {
// return kc_funcs->KC_GetTimeFromSig(inData, inDataLength, flags, inSigId, outDateTime);
// }
import "C"
import (
"fmt"
"time"
"unsafe"
)

func (cli *Client) GetTimeFromSig(cms string, sigID int, flag Flag) (
timestamp time.Time,
err error,
) {
defer func() {
if r := recover(); r != nil {
if err != nil {
err = fmt.Errorf("%w: panic: %s", err, r)
return
}

err = fmt.Errorf("%w: %s", ErrPanic, r)
}
}()

cli.mu.Lock()
defer cli.mu.Unlock()

cCMS := C.CString(cms)
defer C.free(unsafe.Pointer(cCMS))

outDateTime := C.time_t(0)

rc := int(
C.GetTimeFromSig(
cCMS,
C.int(len(cms)),
C.int(int(flag)),
C.int(sigID),
(*C.time_t)(unsafe.Pointer(&outDateTime)),
),
)

err = cli.wrapError(rc)
if err != nil {
return time.Time{}, err
}

timestamp = time.Unix(int64(outDateTime), 0)

return timestamp, nil
}
13 changes: 13 additions & 0 deletions tsp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gokalkan

import (
"encoding/base64"
"github.com/gokalkan/gokalkan/ckalkan"
"time"
)

func (cli *Client) GetTimeFromSig(signature []byte, signID int) (time.Time, error) {
signatureB64 := base64.StdEncoding.EncodeToString(signature)

return cli.kc.GetTimeFromSig(signatureB64, signID, ckalkan.FlagInBase64)
}
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载