forked from amdonov/xmlsig
/
xmlsig.go
238 lines (215 loc) · 6.68 KB
/
xmlsig.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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Package xmlsig supports add XML Digital Signatures to Go structs marshalled to XML.
package xmlsig
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
// import supported crypto hash function
_ "crypto/sha1"
_ "crypto/sha256"
"crypto/x509"
"encoding/base64"
)
const (
SignatureAlgorithmDsigRSASHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
SignatureAlgorithmDsigRSASHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
)
const (
DigestAlgorithmDsigRSASHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
DigestAlgorithmDsigSHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
)
// Signer is used to create a Signature for the provided object.
type Signer interface {
Sign([]byte) (string, error)
CreateSignature(interface{}) (*Signature, error)
Algorithm() string
}
type Verifier interface {
Verify([]byte, *Signature) (bool, error)
VerifySignature(interface{}, *Signature) (bool, error)
Algorithm() string
}
type signer struct {
cert *x509.Certificate
key *rsa.PrivateKey
sigAlg *algorithm
digestAlg *algorithm
}
type verifier struct {
cert *x509.Certificate
sigAlg *algorithm
digestAlg *algorithm
}
type algorithm struct {
name string
hash crypto.Hash
}
type SignerOptions struct {
SignatureAlgorithm string
DigestAlgorithm string
}
type VerifierOptions struct {
SignatureAlgorithm string
DigestAlgorithm string
// If specified, is compared against the certificate data for equality
X509Data string
}
var Canonicalize = canonicalize
func pickSignatureAlgorithm(certType x509.PublicKeyAlgorithm, alg string) (*algorithm, error) {
var hash crypto.Hash
switch certType {
case x509.RSA:
switch alg {
case "":
alg = SignatureAlgorithmDsigRSASHA1
hash = crypto.SHA1
case SignatureAlgorithmDsigRSASHA1:
hash = crypto.SHA1
case SignatureAlgorithmDsigRSASHA256:
hash = crypto.SHA256
default:
return nil, errors.New("xmlsig does not currently the specfied algorithm for RSA certificates")
}
default:
return nil, errors.New("xmlsig needs some work to support your certificate")
}
return &algorithm{alg, hash}, nil
}
func pickDigestAlgorithm(alg string) (*algorithm, error) {
switch alg {
case "":
fallthrough
case DigestAlgorithmDsigRSASHA1:
return &algorithm{"http://www.w3.org/2000/09/xmldsig#sha1", crypto.SHA1}, nil
case DigestAlgorithmDsigSHA256:
return &algorithm{"http://www.w3.org/2001/04/xmlenc#sha256", crypto.SHA256}, nil
}
return nil, errors.New("xmlsig does not support the specified digest algorithm")
}
// NewSigner creates a new Signer with the certificate and options
func NewSigner(cert *x509.Certificate, key *rsa.PrivateKey, options ...SignerOptions) (Signer, error) {
opts := SignerOptions{}
if len(options) > 0 {
opts = options[0]
}
sigAlg, err := pickSignatureAlgorithm(cert.PublicKeyAlgorithm, opts.SignatureAlgorithm)
if err != nil {
return nil, err
}
digestAlg, err := pickDigestAlgorithm(opts.DigestAlgorithm)
if err != nil {
return nil, err
}
return &signer{cert, key, sigAlg, digestAlg}, nil
}
func (s *signer) Algorithm() string {
return s.sigAlg.name
}
func (s *signer) CreateSignature(data interface{}) (*Signature, error) {
signature := newSignature()
signature.SignedInfo.SignatureMethod.Algorithm = s.sigAlg.name
signature.SignedInfo.Reference.DigestMethod.Algorithm = s.digestAlg.name
// canonicalize the Item
canonData, id, err := Canonicalize(data, signature.SignedInfo.CanonicalizationMethod.Algorithm, "", false)
if err != nil {
return nil, err
}
if id != "" {
signature.SignedInfo.Reference.URI = "#" + id
}
// calculate the digest
digest := digest(s.digestAlg, canonData)
signature.SignedInfo.Reference.DigestValue = digest
// Canonicalize the SignedInfo
canonData, _, err = Canonicalize(signature.SignedInfo, signature.SignedInfo.CanonicalizationMethod.Algorithm, "",false)
if err != nil {
return nil, err
}
sig, err := s.Sign(canonData)
if err != nil {
return nil, err
}
signature.SignatureValue = sig
x509Data := &X509Data{X509Certificate: base64.StdEncoding.EncodeToString(s.cert.Raw)}
signature.KeyInfo.X509Data = x509Data
return signature, nil
}
func (s *signer) Sign(data []byte) (string, error) {
h := s.sigAlg.hash.New()
h.Write(data)
sum := h.Sum(nil)
sig, err := s.key.Sign(rand.Reader, sum, s.sigAlg.hash)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(sig), nil
}
// NewVerifier creates a new Signer with the certificate and options
func NewVerifier(cert *x509.Certificate, options ...VerifierOptions) (Verifier, error) {
opts := VerifierOptions{}
if len(options) > 0 {
opts = options[0]
}
if opts.X509Data != "" {
if base64.StdEncoding.EncodeToString(cert.Raw) != opts.X509Data {
return nil, errors.New("certificate mismatch")
}
}
sigAlg, err := pickSignatureAlgorithm(cert.PublicKeyAlgorithm, opts.SignatureAlgorithm)
if err != nil {
return nil, err
}
digestAlg, err := pickDigestAlgorithm(opts.DigestAlgorithm)
if err != nil {
return nil, err
}
return &verifier{cert, sigAlg, digestAlg}, nil
}
func (s *verifier) Algorithm() string {
return s.sigAlg.name
}
func (s *verifier) VerifySignature(data interface{}, signature *Signature) (bool, error) {
// Canonicalize the Item
canonData, _, err := Canonicalize(data, signature.SignedInfo.CanonicalizationMethod.Algorithm, "",false)
if err != nil {
return false, err
}
return s.Verify(canonData, signature)
}
// TODO - Check mismatch of digest and signature methods
func (s *verifier) Verify(data []byte, signature *Signature) (bool, error) {
h := s.sigAlg.hash.New()
h.Write(data)
digestSum := h.Sum(nil)
if base64.StdEncoding.EncodeToString(digestSum) != signature.SignedInfo.Reference.DigestValue {
return false, nil
}
canonData, _, err := Canonicalize(signature.SignedInfo, signature.SignedInfo.CanonicalizationMethod.Algorithm, "",false)
if err != nil {
return false, err
}
h = s.sigAlg.hash.New()
h.Write(canonData)
sigSum := h.Sum(nil)
sig, err := base64.StdEncoding.DecodeString(signature.SignatureValue)
if err != nil {
return false, err
}
return rsa.VerifyPKCS1v15(s.cert.PublicKey.(*rsa.PublicKey), s.digestAlg.hash, sigSum, sig) == nil, nil
}
func newSignature() *Signature {
signature := &Signature{}
signature.SignedInfo.CanonicalizationMethod.Algorithm =
"http://www.w3.org/2001/10/xml-exc-c14n#"
transforms := &signature.SignedInfo.Reference.Transforms.Transform
*transforms = append(*transforms, Algorithm{"http://www.w3.org/2000/09/xmldsig#enveloped-signature"})
*transforms = append(*transforms, Algorithm{"http://www.w3.org/2001/10/xml-exc-c14n#"})
return signature
}
func digest(digestAlg *algorithm, data []byte) string {
h := digestAlg.hash.New()
h.Write(data)
sum := h.Sum(nil)
return base64.StdEncoding.EncodeToString(sum)
}