New upstream version 0.0~git20180430.7acd5e4
Christian Barcenas
4 years ago
0 | *.test |
0 | 0 | language: go |
1 | 1 | |
2 | 2 | go: |
3 | - 1.5 | |
4 | - 1.6 | |
5 | - tip⏎ | |
3 | - "1.6.x" | |
4 | - "1.7.x" | |
5 | - "1.8.x" | |
6 | - "1.9.x" | |
7 | - "1.10.x" | |
8 | - master |
53 | 53 | |
54 | 54 | func (c *c14N11Canonicalizer) Algorithm() AlgorithmID { |
55 | 55 | return CanonicalXML11AlgorithmId |
56 | } | |
57 | ||
58 | type c14N10RecCanonicalizer struct{} | |
59 | ||
60 | // MakeC14N10RecCanonicalizer constructs an inclusive canonicalizer. | |
61 | func MakeC14N10RecCanonicalizer() Canonicalizer { | |
62 | return &c14N10RecCanonicalizer{} | |
63 | } | |
64 | ||
65 | // Canonicalize transforms the input Element into a serialized XML document in canonical form. | |
66 | func (c *c14N10RecCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) { | |
67 | scope := make(map[string]struct{}) | |
68 | return canonicalSerialize(canonicalPrep(el, scope)) | |
69 | } | |
70 | ||
71 | func (c *c14N10RecCanonicalizer) Algorithm() AlgorithmID { | |
72 | return CanonicalXML10RecAlgorithmId | |
73 | } | |
74 | ||
75 | type c14N10CommentCanonicalizer struct{} | |
76 | ||
77 | // MakeC14N10CommentCanonicalizer constructs an inclusive canonicalizer. | |
78 | func MakeC14N10CommentCanonicalizer() Canonicalizer { | |
79 | return &c14N10CommentCanonicalizer{} | |
80 | } | |
81 | ||
82 | // Canonicalize transforms the input Element into a serialized XML document in canonical form. | |
83 | func (c *c14N10CommentCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) { | |
84 | scope := make(map[string]struct{}) | |
85 | return canonicalSerialize(canonicalPrep(el, scope)) | |
86 | } | |
87 | ||
88 | func (c *c14N10CommentCanonicalizer) Algorithm() AlgorithmID { | |
89 | return CanonicalXML10CommentAlgorithmId | |
56 | 90 | } |
57 | 91 | |
58 | 92 | func composeAttr(space, key string) string { |
15 | 15 | prefixSet[prefix] = struct{}{} |
16 | 16 | } |
17 | 17 | |
18 | err := transformExcC14n(DefaultNSContext, EmptyNSContext, el, prefixSet) | |
18 | err := transformExcC14n(DefaultNSContext, DefaultNSContext, el, prefixSet) | |
19 | 19 | if err != nil { |
20 | 20 | return err |
21 | 21 | } |
265 | 265 | // returned by NSFindIterate. |
266 | 266 | func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error { |
267 | 267 | err := NSTraverse(ctx, el, func(ctx NSContext, el *etree.Element) error { |
268 | currentNS, err := ctx.LookupPrefix(el.Space) | |
268 | _ctx, err := ctx.SubContext(el) | |
269 | if err != nil { | |
270 | return err | |
271 | } | |
272 | ||
273 | currentNS, err := _ctx.LookupPrefix(el.Space) | |
269 | 274 | if err != nil { |
270 | 275 | return err |
271 | 276 | } |
302 | 307 | }) |
303 | 308 | |
304 | 309 | if err != nil { |
310 | return nil, err | |
311 | } | |
312 | ||
313 | return found, nil | |
314 | } | |
315 | ||
316 | // NSIterateChildren iterates the children of an element, invoking the passed | |
317 | // handler with each direct child of the element, and the context surrounding | |
318 | // that child. | |
319 | func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) error { | |
320 | ctx, err := ctx.SubContext(el) | |
321 | if err != nil { | |
322 | return err | |
323 | } | |
324 | ||
325 | // Iterate the child elements. | |
326 | for _, child := range el.ChildElements() { | |
327 | err = handle(ctx, child) | |
328 | if err != nil { | |
329 | return err | |
330 | } | |
331 | } | |
332 | ||
333 | return nil | |
334 | } | |
335 | ||
336 | // NSFindIterateChildrenCtx takes an element and its surrounding context, and iterates | |
337 | // the children of that element searching for an element matching the passed namespace | |
338 | // and tag. For each such element that is found, handle is invoked with the matched | |
339 | // element and its own surrounding context. | |
340 | func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error { | |
341 | err := NSIterateChildren(ctx, el, func(ctx NSContext, el *etree.Element) error { | |
342 | _ctx, err := ctx.SubContext(el) | |
343 | if err != nil { | |
344 | return err | |
345 | } | |
346 | ||
347 | currentNS, err := _ctx.LookupPrefix(el.Space) | |
348 | if err != nil { | |
349 | return err | |
350 | } | |
351 | ||
352 | // Base case, el is the sought after element. | |
353 | if currentNS == namespace && el.Tag == tag { | |
354 | return handle(ctx, el) | |
355 | } | |
356 | ||
357 | return nil | |
358 | }) | |
359 | ||
360 | if err != nil && err != ErrTraversalHalted { | |
361 | return err | |
362 | } | |
363 | ||
364 | return nil | |
365 | } | |
366 | ||
367 | // NSFindOneChild behaves identically to NSFindOneChildCtx, but uses | |
368 | // DefaultNSContext for context. | |
369 | func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) { | |
370 | return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag) | |
371 | } | |
372 | ||
373 | // NSFindOneCtx conducts a depth-first search for the specified element. If such an | |
374 | // element is found a reference to it is returned. | |
375 | func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) { | |
376 | var found *etree.Element | |
377 | ||
378 | err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error { | |
379 | found = el | |
380 | return ErrTraversalHalted | |
381 | }) | |
382 | ||
383 | if err != nil && err != ErrTraversalHalted { | |
305 | 384 | return nil, err |
306 | 385 | } |
307 | 386 |
9 | 9 | |
10 | 10 | type X509KeyStore interface { |
11 | 11 | GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error) |
12 | } | |
13 | ||
14 | type X509ChainStore interface { | |
15 | GetChain() (certs [][]byte, err error) | |
12 | 16 | } |
13 | 17 | |
14 | 18 | type X509CertificateStore interface { |
58 | 58 | } |
59 | 59 | |
60 | 60 | func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) { |
61 | digestAlgorithmIdentifier, ok := digestAlgorithmIdentifiers[ctx.Hash] | |
62 | if !ok { | |
61 | digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() | |
62 | if digestAlgorithmIdentifier == "" { | |
63 | 63 | return nil, errors.New("unsupported hash mechanism") |
64 | 64 | } |
65 | 65 | |
66 | signatureMethodIdentifier, ok := signatureMethodIdentifiers[ctx.Hash] | |
67 | if !ok { | |
66 | signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier() | |
67 | if signatureMethodIdentifier == "" { | |
68 | 68 | return nil, errors.New("unsupported signature method") |
69 | 69 | } |
70 | 70 | |
175 | 175 | return nil, err |
176 | 176 | } |
177 | 177 | |
178 | certs := [][]byte{cert} | |
179 | if cs, ok := ctx.KeyStore.(X509ChainStore); ok { | |
180 | certs, err = cs.GetChain() | |
181 | if err != nil { | |
182 | return nil, err | |
183 | } | |
184 | } | |
185 | ||
178 | 186 | rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest) |
179 | 187 | if err != nil { |
180 | 188 | return nil, err |
185 | 193 | |
186 | 194 | keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag) |
187 | 195 | x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag) |
188 | x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag) | |
189 | x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert)) | |
196 | for _, cert := range certs { | |
197 | x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag) | |
198 | x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert)) | |
199 | } | |
190 | 200 | |
191 | 201 | return sig, nil |
192 | 202 | } |
208 | 218 | |
209 | 219 | return ret, nil |
210 | 220 | } |
221 | ||
222 | func (ctx *SigningContext) GetSignatureMethodIdentifier() string { | |
223 | if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok { | |
224 | return ident | |
225 | } | |
226 | return "" | |
227 | } | |
228 | ||
229 | func (ctx *SigningContext) GetDigestAlgorithmIdentifier() string { | |
230 | if ident, ok := digestAlgorithmIdentifiers[ctx.Hash]; ok { | |
231 | return ident | |
232 | } | |
233 | return "" | |
234 | } | |
235 | ||
236 | // Useful for signing query string (including DEFLATED AuthnRequest) when | |
237 | // using HTTP-Redirect to make a signed request. | |
238 | // See 3.4.4.1 DEFLATE Encoding of https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf | |
239 | func (ctx *SigningContext) SignString(content string) ([]byte, error) { | |
240 | hash := ctx.Hash.New() | |
241 | if ln, err := hash.Write([]byte(content)); err != nil { | |
242 | return nil, fmt.Errorf("error calculating hash: %v", err) | |
243 | } else if ln < 1 { | |
244 | return nil, fmt.Errorf("zero length hash") | |
245 | } | |
246 | digest := hash.Sum(nil) | |
247 | ||
248 | var signature []byte | |
249 | if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil { | |
250 | return nil, fmt.Errorf("unable to fetch key for signing: %v", err) | |
251 | } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil { | |
252 | return nil, fmt.Errorf("error signing: %v", err) | |
253 | } | |
254 | return signature, nil | |
255 | } |
5 | 5 | "testing" |
6 | 6 | |
7 | 7 | "github.com/beevik/etree" |
8 | "github.com/satori/go.uuid" | |
9 | 8 | "github.com/stretchr/testify/require" |
10 | 9 | ) |
11 | 10 | |
17 | 16 | Space: "samlp", |
18 | 17 | Tag: "AuthnRequest", |
19 | 18 | } |
20 | id := "_" + uuid.NewV4().String() | |
19 | id := "_97e34c50-65ec-4132-8b39-02933960a96a" | |
21 | 20 | authnRequest.CreateAttr("ID", id) |
22 | 21 | hash := crypto.SHA256.New() |
23 | 22 | canonicalized, err := ctx.Canonicalizer.Canonicalize(authnRequest) |
126 | 125 | Tag: "Bar", |
127 | 126 | } |
128 | 127 | |
129 | id := "_" + uuid.NewV4().String() | |
128 | id := "_97e34c50-65ec-4132-8b39-02933960a96b" | |
130 | 129 | |
131 | 130 | signable.CreateAttr("OtherID", id) |
132 | 131 | signed, err := ctx.SignEnveloped(signable) |
31 | 31 | |
32 | 32 | return pk, crt, nil |
33 | 33 | } |
34 | ||
35 | //GetChain impliments X509ChainStore using the underlying tls.Certificate | |
36 | func (d TLSCertKeyStore) GetChain() ([][]byte, error) { | |
37 | return d.Certificate, nil | |
38 | } |
62 | 62 | } |
63 | 63 | |
64 | 64 | type X509Data struct { |
65 | XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"` | |
66 | X509Certificate X509Certificate `xml:"X509Certificate"` | |
65 | XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"` | |
66 | X509Certificates []X509Certificate `xml:"X509Certificate"` | |
67 | 67 | } |
68 | 68 | |
69 | 69 | type X509Certificate struct { |
58 | 58 | } |
59 | 59 | } |
60 | 60 | |
61 | // The RemoveElement method on etree.Element isn't recursive... | |
62 | func recursivelyRemoveElement(tree, el *etree.Element) bool { | |
63 | if tree.RemoveChild(el) != nil { | |
61 | func mapPathToElement(tree, el *etree.Element) []int { | |
62 | for i, child := range tree.Child { | |
63 | if child == el { | |
64 | return []int{i} | |
65 | } | |
66 | } | |
67 | ||
68 | for i, child := range tree.Child { | |
69 | if childElement, ok := child.(*etree.Element); ok { | |
70 | childPath := mapPathToElement(childElement, el) | |
71 | if childElement != nil { | |
72 | return append([]int{i}, childPath...) | |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | return nil | |
78 | } | |
79 | ||
80 | func removeElementAtPath(el *etree.Element, path []int) bool { | |
81 | if len(path) == 0 { | |
82 | return false | |
83 | } | |
84 | ||
85 | if len(el.Child) <= path[0] { | |
86 | return false | |
87 | } | |
88 | ||
89 | childElement, ok := el.Child[path[0]].(*etree.Element) | |
90 | if !ok { | |
91 | return false | |
92 | } | |
93 | ||
94 | if len(path) == 1 { | |
95 | el.RemoveChild(childElement) | |
64 | 96 | return true |
65 | 97 | } |
66 | 98 | |
67 | for _, child := range tree.Child { | |
68 | if childElement, ok := child.(*etree.Element); ok { | |
69 | if recursivelyRemoveElement(childElement, el) { | |
70 | return true | |
71 | } | |
72 | } | |
73 | } | |
74 | ||
75 | return false | |
76 | } | |
77 | ||
78 | // transform applies the passed set of transforms to the specified root element. | |
99 | return removeElementAtPath(childElement, path[1:]) | |
100 | } | |
101 | ||
102 | // Transform returns a new element equivalent to the passed root el, but with | |
103 | // the set of transformations described by the ref applied. | |
79 | 104 | // |
80 | 105 | // The functionality of transform is currently very limited and purpose-specific. |
81 | // | |
82 | // NOTE(russell_h): Ideally this wouldn't mutate the root passed to it, and would | |
83 | // instead return a copy. Unfortunately copying the tree makes it difficult to | |
84 | // correctly locate the signature. I'm opting, for now, to simply mutate the root | |
85 | // parameter. | |
86 | 106 | func (ctx *ValidationContext) transform( |
87 | 107 | el *etree.Element, |
88 | 108 | sig *types.Signature, |
93 | 113 | return nil, nil, errors.New("Expected Enveloped and C14N transforms") |
94 | 114 | } |
95 | 115 | |
116 | // map the path to the passed signature relative to the passed root, in | |
117 | // order to enable removal of the signature by an enveloped signature | |
118 | // transform | |
119 | signaturePath := mapPathToElement(el, sig.UnderlyingElement()) | |
120 | ||
121 | // make a copy of the passed root | |
122 | el = el.Copy() | |
123 | ||
96 | 124 | var canonicalizer Canonicalizer |
97 | 125 | |
98 | 126 | for _, transform := range transforms { |
100 | 128 | |
101 | 129 | switch AlgorithmID(algo) { |
102 | 130 | case EnvelopedSignatureAltorithmId: |
103 | if !recursivelyRemoveElement(el, sig.UnderlyingElement()) { | |
131 | if !removeElementAtPath(el, signaturePath) { | |
104 | 132 | return nil, nil, errors.New("Error applying canonicalization transform: Signature not found") |
105 | 133 | } |
106 | 134 | |
115 | 143 | case CanonicalXML11AlgorithmId: |
116 | 144 | canonicalizer = MakeC14N11Canonicalizer() |
117 | 145 | |
146 | case CanonicalXML10RecAlgorithmId: | |
147 | canonicalizer = MakeC14N10RecCanonicalizer() | |
148 | ||
149 | case CanonicalXML10CommentAlgorithmId: | |
150 | canonicalizer = MakeC14N10CommentCanonicalizer() | |
151 | ||
118 | 152 | default: |
119 | 153 | return nil, nil, errors.New("Unknown Transform Algorithm: " + algo) |
120 | 154 | } |
150 | 184 | func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicalizer Canonicalizer, signatureMethodId string, cert *x509.Certificate, decodedSignature []byte) error { |
151 | 185 | signatureElement := sig.UnderlyingElement() |
152 | 186 | |
153 | signedInfo := signatureElement.FindElement(childPath(signatureElement.Space, SignedInfoTag)) | |
187 | nsCtx, err := etreeutils.NSBuildParentContext(signatureElement) | |
188 | if err != nil { | |
189 | return err | |
190 | } | |
191 | ||
192 | signedInfo, err := etreeutils.NSFindOneChildCtx(nsCtx, signatureElement, Namespace, SignedInfoTag) | |
193 | if err != nil { | |
194 | return err | |
195 | } | |
196 | ||
154 | 197 | if signedInfo == nil { |
155 | 198 | return errors.New("Missing SignedInfo") |
156 | 199 | } |
265 | 308 | err := etreeutils.NSFindIterate(el, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error { |
266 | 309 | |
267 | 310 | found := false |
268 | err := etreeutils.NSFindIterateCtx(ctx, el, Namespace, SignedInfoTag, | |
311 | err := etreeutils.NSFindChildrenIterateCtx(ctx, el, Namespace, SignedInfoTag, | |
269 | 312 | func(ctx etreeutils.NSContext, signedInfo *etree.Element) error { |
270 | // Ignore any SignedInfo that isn't an immediate descendent of Signature. | |
271 | if signedInfo.Parent() != el { | |
272 | return nil | |
273 | } | |
274 | ||
275 | 313 | detachedSignedInfo, err := etreeutils.NSDetatch(ctx, signedInfo) |
276 | 314 | if err != nil { |
277 | 315 | return err |
278 | 316 | } |
279 | 317 | |
280 | c14NMethod := detachedSignedInfo.FindElement(childPath(detachedSignedInfo.Space, CanonicalizationMethodTag)) | |
318 | c14NMethod, err := etreeutils.NSFindOneChildCtx(ctx, detachedSignedInfo, Namespace, CanonicalizationMethodTag) | |
319 | if err != nil { | |
320 | return err | |
321 | } | |
322 | ||
281 | 323 | if c14NMethod == nil { |
282 | 324 | return errors.New("missing CanonicalizationMethod on Signature") |
283 | 325 | } |
300 | 342 | canonicalSignedInfo = detachedSignedInfo |
301 | 343 | |
302 | 344 | case CanonicalXML11AlgorithmId: |
345 | canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{}) | |
346 | ||
347 | case CanonicalXML10RecAlgorithmId: | |
348 | canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{}) | |
349 | ||
350 | case CanonicalXML10CommentAlgorithmId: | |
303 | 351 | canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{}) |
304 | 352 | |
305 | 353 | default: |
363 | 411 | |
364 | 412 | if sig.KeyInfo != nil { |
365 | 413 | // If the Signature includes KeyInfo, extract the certificate from there |
366 | if sig.KeyInfo.X509Data.X509Certificate.Data == "" { | |
414 | if len(sig.KeyInfo.X509Data.X509Certificates) == 0 || sig.KeyInfo.X509Data.X509Certificates[0].Data == "" { | |
367 | 415 | return nil, errors.New("missing X509Certificate within KeyInfo") |
368 | 416 | } |
369 | 417 | |
370 | 418 | certData, err := base64.StdEncoding.DecodeString( |
371 | whiteSpace.ReplaceAllString(sig.KeyInfo.X509Data.X509Certificate.Data, "")) | |
419 | whiteSpace.ReplaceAllString(sig.KeyInfo.X509Data.X509Certificates[0].Data, "")) | |
372 | 420 | if err != nil { |
373 | 421 | return nil, errors.New("Failed to parse certificate") |
374 | 422 | } |
49 | 49 | CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#" |
50 | 50 | CanonicalXML11AlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11" |
51 | 51 | |
52 | CanonicalXML10RecAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" | |
53 | CanonicalXML10CommentAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" | |
54 | ||
52 | 55 | EnvelopedSignatureAltorithmId AlgorithmID = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" |
53 | 56 | ) |
54 | 57 |