-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
dsignandverify.go
187 lines (147 loc) · 6.88 KB
/
dsignandverify.go
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package fiskalhrgo
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 L. D. T. d.o.o.
// Copyright (c) contributors for their respective contributions. See https://github.com/l-d-t/fiskalhrgo/graphs/contributors
import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"fmt"
"github.com/beevik/etree"
)
// doc14n applies Exclusive Canonical XML (http://www.w3.org/2001/10/xml-exc-c14n#) to the input XML data
func doc14n(xmlData []byte) ([]byte, error) {
// Parse the input XML string into an etree.Document
doc := etree.NewDocument()
if err := doc.ReadFromBytes(xmlData); err != nil {
return nil, fmt.Errorf("failed to parse XML: %v", err)
}
canonicalizer := MakeC14N10ExclusiveCanonicalizerWithPrefixList("") // No prefix list
canonicalizedXML, err := canonicalizer.Canonicalize(doc.Root())
if err != nil {
return nil, fmt.Errorf("failed to canonicalize the XML: %v", err)
}
return canonicalizedXML, nil
}
func createSignedInfoElement(referenceURI, digestValue string) *etree.Element {
signedInfo := etree.NewElement("SignedInfo")
signedInfo.CreateAttr("xmlns", "http://www.w3.org/2000/09/xmldsig#")
canonicalizationMethod := signedInfo.CreateElement("CanonicalizationMethod")
canonicalizationMethod.CreateAttr("Algorithm", "http://www.w3.org/2001/10/xml-exc-c14n#")
signatureMethod := signedInfo.CreateElement("SignatureMethod")
signatureMethod.CreateAttr("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1")
reference := signedInfo.CreateElement("Reference")
reference.CreateAttr("URI", "#"+referenceURI)
transforms := reference.CreateElement("Transforms")
transform1 := transforms.CreateElement("Transform")
transform1.CreateAttr("Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature")
transform2 := transforms.CreateElement("Transform")
transform2.CreateAttr("Algorithm", "http://www.w3.org/2001/10/xml-exc-c14n#")
digestMethod := reference.CreateElement("DigestMethod")
digestMethod.CreateAttr("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1")
digestValueElement := reference.CreateElement("DigestValue")
digestValueElement.SetText(digestValue)
return signedInfo
}
func createSignatureElement(signedInfoElement *etree.Element, signatureValue string, cert *x509.Certificate) *etree.Element {
signatureElement := etree.NewElement("Signature")
signatureElement.CreateAttr("xmlns", "http://www.w3.org/2000/09/xmldsig#")
// Add the canonicalized SignedInfo element
signatureElement.AddChild(signedInfoElement)
// Add the SignatureValue
signatureValueElement := signatureElement.CreateElement("SignatureValue")
signatureValueElement.SetText(signatureValue)
// Add the KeyInfo
keyInfoElement := signatureElement.CreateElement("KeyInfo")
x509DataElement := keyInfoElement.CreateElement("X509Data")
// Add the X509Certificate
x509CertificateElement := x509DataElement.CreateElement("X509Certificate")
x509CertificateElement.SetText(base64.StdEncoding.EncodeToString(cert.Raw))
// Add the X509IssuerSerial
x509IssuerSerialElement := x509DataElement.CreateElement("X509IssuerSerial")
x509IssuerNameElement := x509IssuerSerialElement.CreateElement("X509IssuerName")
x509IssuerNameElement.SetText(cert.Issuer.String())
x509SerialNumberElement := x509IssuerSerialElement.CreateElement("X509SerialNumber")
x509SerialNumberElement.SetText(cert.SerialNumber.String())
return signatureElement
}
func (fe *FiskalEntity) signXML(xmlRequest []byte) ([]byte, error) {
// Step 1: Parse and Canonicalize the XML document using etree
doc := etree.NewDocument()
if err := doc.ReadFromBytes(xmlRequest); err != nil {
return nil, fmt.Errorf("failed to parse XML document: %v", err)
}
// Step 6: Insert the Signature block before the closing tag of the root element
root := doc.Root()
if root == nil {
return nil, fmt.Errorf("invalid XML: root element not found")
}
referenceID := root.SelectAttrValue("Id", "")
if referenceID == "" {
return nil, fmt.Errorf("no Id attribute found in the root element")
}
// Canonicalize the XML document
xmlCanonical, err := doc14n(xmlRequest)
if err != nil {
return nil, fmt.Errorf("failed to canonicalize XML document: %v", err)
}
// DigestValue calculation using SHA-1
digest := sha1.New()
if _, err := digest.Write([]byte(xmlCanonical)); err != nil {
return nil, fmt.Errorf("failed to calculate digest: %v", err)
}
digestValue := base64.StdEncoding.EncodeToString(digest.Sum(nil))
// Step 2: Create SignedInfo block with DigestValue using etree
signedInfoElement := createSignedInfoElement(referenceID, digestValue)
// Convert the SignedInfo element to a string
signedInfoDocument := etree.NewDocument()
signedInfoDocument.SetRoot(signedInfoElement)
signedInfoString, err := signedInfoDocument.WriteToBytes()
if err != nil {
return nil, fmt.Errorf("failed to serialize SignedInfo: %v", err)
}
// Canonicalize the SignedInfo block
canonicalizedSignedInfo, err := doc14n(signedInfoString)
if err != nil {
return nil, fmt.Errorf("failed to canonicalize SignedInfo: %v", err)
}
// Step 3: Compute hash of canonicalized SignedInfo
hashedSignedInfo := sha1.Sum(canonicalizedSignedInfo)
// Step 4: Generate the SignatureValue using the private key
signature, err := rsa.SignPKCS1v15(nil, fe.cert.privateKey, crypto.SHA1, hashedSignedInfo[:])
if err != nil {
return nil, fmt.Errorf("failed to generate signature: %v", err)
}
signatureValue := base64.StdEncoding.EncodeToString(signature)
// Step 5: Build the Signature block with certificate details using etree
signatureBlock := createSignatureElement(
signedInfoElement,
signatureValue,
fe.cert.publicCert,
)
root.AddChild(signatureBlock)
// Serialize the updated document back to bytes
output, err := doc.WriteToBytes()
if err != nil {
return nil, fmt.Errorf("failed to serialize signed XML: %v", err)
}
return output, nil
}
// verifyXML is currently a placeholder function for verifying signed XML documents.
// It always returns true without performing any actual verification and should not be used in production environments
// until proper XML signature verification is fully implemented.
//
// The primary challenge is the absence of a reliable pure Go implementation for the Canonicalization Method
// (http://www.w3.org/TR/2001/REC-xml-c14n-20010315). Several libraries were evaluated, but all encountered subtle
// issues during implementation. Without a robust xml canonicalization solution, xml signature verification is not possible.
//
// While the library supports Exclusive Canonicalization (http://www.w3.org/2001/10/xml-exc-c14n#), which suffices
// for signing requests, the Croatian CIS system's responses use non-exclusive canonicalization, preventing verification at this time.
//
// This limitation will remain unresolved until a suitable library is found or a custom implementation is built,
// or until fixes are contributed and merged into existing libraries.
func (fe *FiskalEntity) verifyXML(xmlData []byte) (bool, error) {
return true, nil
}