forked from gokalkan/gokalkan
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsign.go
More file actions
141 lines (114 loc) · 5.1 KB
/
Copy pathsign.go
File metadata and controls
141 lines (114 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package gokalkan
import (
"encoding/base64"
"encoding/xml"
"regexp"
"strings"
"github.com/ncanode-kz/gokalkan/ckalkan"
)
// Sign подписывает данные и возвращает CMS с подписью.
func (cli *Client) Sign(data []byte, isDetached, withTSP bool) (signature []byte, err error) {
dataB64 := base64.StdEncoding.EncodeToString(data)
flags := ckalkan.FlagSignCMS | ckalkan.FlagInBase64 | ckalkan.FlagOutBase64
if withTSP {
flags |= ckalkan.FlagWithTimestamp
}
if isDetached {
flags |= ckalkan.FlagDetachedData
}
signatureB64, err := cli.kc.SignData("", dataB64, "", flags)
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(signatureB64)
}
// AddSigner дописывает ещё одну подпись в уже существующий CMS (со-подпись).
// Для этого перед вызовом обычно нужно загрузить другой ключ через LoadKeyStore.
// Существующие подписи (и их метки времени) не затрагиваются - TSP (если withTSP)
// прикладывается только к новой подписи.
func (cli *Client) AddSigner(cms, data []byte, isDetached, withTSP bool) (signature []byte, err error) {
cmsB64 := base64.StdEncoding.EncodeToString(cms)
dataB64 := base64.StdEncoding.EncodeToString(data)
flags := ckalkan.FlagSignCMS | ckalkan.FlagInBase64 | ckalkan.FlagIn2Base64 | ckalkan.FlagOutBase64
if withTSP {
flags |= ckalkan.FlagWithTimestamp
}
if isDetached {
flags |= ckalkan.FlagDetachedData
}
signatureB64, err := cli.kc.SignData(cmsB64, dataB64, "", flags)
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(signatureB64)
}
// SignXML подписывает данные в формате XML.
func (cli *Client) SignXML(xmlData string) (string, error) {
return cli.kc.SignXML(xmlData, "", 0, "", "", "")
}
// SignWSSE подписывает документ в формате WS-Security (SmartBridge). xmlData -
// содержимое soap:Body (не полный конверт, конверт строится автоматически).
func (cli *Client) SignWSSE(xmlData, id string) (string, error) {
soapEnvelope := WrapWithWSSESoapEnvelope(xmlData, id)
signed, err := cli.kc.SignWSSE(soapEnvelope, "", 0, id)
if err != nil {
return "", err
}
return fixWSSENativeQuirks(signed), nil
}
//nolint:gochecknoglobals
var wsseKeyInfoIDRe = regexp.MustCompile(`(<ds:KeyInfo) Id="[^"]*"`)
// fixWSSENativeQuirks исправляет две особенности вывода нативной SignWSSE,
// которые не влияют на корректность самой подписи (Reference покрывает только
// Body, см. URI="#id"), но ломают строгий разбор конверта в некоторых
// WS-Security стеках (например SAAJ, как в эталонной Java-реализации
// NCANode - без этой правки verify падает с NOT_FOUND_ERR):
//
// - жёстко захардкоженный префикс "SOAP-ENV" у вставленного библиотекой
// <Header> переписывается на "soap" - тот же, что в конверте (см.
// WrapWithWSSESoapEnvelope);
// - атрибут Id="ki-..." на <ds:KeyInfo> убирается: судя по всему, SAAJ
// трактует произвольный "Id"-атрибут как ID-тип и путает разрешение
// узлов при поиске wsse:SecurityTokenReference, если такой атрибут
// присутствует на KeyInfo (найдено эмпирически при сверке с оракулом).
func fixWSSENativeQuirks(xml string) string {
xml = strings.Replace(xml, `<SOAP-ENV:Header xmlns:SOAP-ENV="`+xmlnsSOAP+`">`, "<soap:Header>", 1)
xml = strings.Replace(xml, "</SOAP-ENV:Header>", "</soap:Header>", 1)
xml = wsseKeyInfoIDRe.ReplaceAllString(xml, "$1")
return xml
}
const (
xmlnsSOAP = "http://schemas.xmlsoap.org/soap/envelope/"
xmlnsWSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
replaceKey = "replace-this"
)
// soapEnvelope представляет soap:Envelope
type soapEnvelope struct {
XMLName xml.Name `xml:"soap:Envelope"`
SOAP string `xml:"xmlns:soap,attr"`
WSU string `xml:"xmlns:wsu,attr"`
Body soapBody `xml:"soap:Body"`
}
// soapBody представляет soap:Body
type soapBody struct {
ID string `xml:"wsu:Id,attr"`
Content string `xml:",chardata"`
}
// WrapWithWSSESoapEnvelope оборачивает XML документ в SOAP формат, а точнее записывает
// содержимое под тегом soap:Body
func WrapWithWSSESoapEnvelope(dataXML, id string) (result string) {
envelope := soapEnvelope{
SOAP: xmlnsSOAP,
WSU: xmlnsWSU,
Body: soapBody{
ID: id,
Content: replaceKey,
},
}
b, err := xml.Marshal(envelope)
if err != nil {
return result
}
result = strings.Replace(string(b), replaceKey, dataXML, 1)
return result
}