From 1bbeff59654db24d0e788187d759ad0f4e42f2c0 Mon Sep 17 00:00:00 2001 From: Oscar Finnsson Date: Mon, 26 Feb 2018 10:56:36 +0100 Subject: [PATCH 1/4] Adding support for enveloping signature and validating certificate chain --- types/signature.go | 11 +++ validate.go | 182 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 173 insertions(+), 20 deletions(-) diff --git a/types/signature.go b/types/signature.go index 17fd3d7..955d592 100644 --- a/types/signature.go +++ b/types/signature.go @@ -72,11 +72,22 @@ type X509Certificate struct { Data string `xml:",chardata"` } +// Object element. See https://www.w3.org/TR/xmldsig-core/#sec-Object +type Object struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Object"` + ID string `xml:"Id,attr"` + MimeType string `xml:"MimeType,attr"` + Encoding string `xml:"Encoding,attr"` +} + +// Signature element. See https://www.w3.org/TR/xmldsig-core/#sec-Signature type Signature struct { XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Signature"` SignedInfo *SignedInfo `xml:"SignedInfo"` SignatureValue *SignatureValue `xml:"SignatureValue"` KeyInfo *KeyInfo `xml:"KeyInfo"` + Object *Object `xml:"Object"` + ID string `xml:"Id"` el *etree.Element } diff --git a/validate.go b/validate.go index 980a4e5..e56a9dd 100644 --- a/validate.go +++ b/validate.go @@ -196,25 +196,55 @@ func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicaliz } func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) { - idAttr := el.SelectAttr(ctx.IdAttribute) - if idAttr == nil || idAttr.Value == "" { - return nil, errors.New("Missing ID attribute") + refID := "" + if sig.Object != nil { + refID = sig.Object.ID + } else { + idAttr := el.SelectAttr(ctx.IdAttribute) + if idAttr == nil || idAttr.Value == "" { + return nil, errors.New("Missing ID attribute") + } + refID = idAttr.Value } var ref *types.Reference // Find the first reference which references the top-level element for _, _ref := range sig.SignedInfo.References { - if _ref.URI == "" || _ref.URI[1:] == idAttr.Value { + if _ref.URI == "" || _ref.URI[1:] == refID { ref = &_ref } } - // Perform all transformations listed in the 'SignedInfo' - // Basically, this means removing the 'SignedInfo' - transformed, canonicalizer, err := ctx.transform(el, sig, ref) - if err != nil { - return nil, err + var transformed *etree.Element + var canonicalizer Canonicalizer + var err error + if sig.Object != nil { + objectElement := sig.UnderlyingElement().FindElement("Object") + transformed = objectElement + if transformed == nil { + return nil, errors.New("Error implementing etree") + } + switch AlgorithmID(sig.SignedInfo.CanonicalizationMethod.Algorithm) { + case CanonicalXML11AlgorithmId: + canonicalizer = MakeC14N11Canonicalizer() + break + case CanonicalXML10RecAlgorithmId: + canonicalizer = MakeC14N10RecCanonicalizer() + break + case CanonicalXML10CommentAlgorithmId: + canonicalizer = MakeC14N10CommentCanonicalizer() + break + default: + return nil, errors.New("Unknown algorithm") + } + } else { + // Perform all transformations listed in the 'SignedInfo' + // Basically, this means removing the 'SignedInfo' + transformed, canonicalizer, err = ctx.transform(el, sig, ref) + if err != nil { + return nil, err + } } digestAlgorithm := ref.DigestAlgo.Algorithm @@ -261,12 +291,8 @@ func contains(roots []*x509.Certificate, cert *x509.Certificate) bool { // findSignature searches for a Signature element referencing the passed root element. func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature, error) { - idAttr := el.SelectAttr(ctx.IdAttribute) - if idAttr == nil || idAttr.Value == "" { - return nil, errors.New("Missing ID attribute") - } - var sig *types.Signature + outerCtx := ctx // Traverse the tree looking for a Signature element err := etreeutils.NSFindIterate(el, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error { @@ -341,12 +367,25 @@ func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature return err } - // Traverse references in the signature to determine whether it has at least - // one reference to the top level element. If so, conclude the search. - for _, ref := range _sig.SignedInfo.References { - if ref.URI == "" || ref.URI[1:] == idAttr.Value { - sig = _sig - return etreeutils.ErrTraversalHalted + if _sig.Object != nil { // enveloping signature + objectID := _sig.Object.ID + // Traverse references in the signature to determine whether is has at least + // one reference to the Object element. If so, conclude the search + for _, ref := range _sig.SignedInfo.References { + if ref.URI == "" || ref.URI[1:] == objectID { + sig = _sig + return etreeutils.ErrTraversalHalted + } + } + } else { + idAttr := el.SelectAttr(outerCtx.IdAttribute) + // Traverse references in the signature to determine whether it has at least + // one reference to the top level element. If so, conclude the search. + for _, ref := range _sig.SignedInfo.References { + if ref.URI == "" || ref.URI[1:] == idAttr.Value { + sig = _sig + return etreeutils.ErrTraversalHalted + } } } @@ -411,6 +450,89 @@ func (ctx *ValidationContext) verifyCertificate(sig *types.Signature) (*x509.Cer return cert, nil } +// validates the certificate chain and returns the leaf certificate +func (ctx *ValidationContext) verifyCertificateChain(sig *types.Signature) ([]*x509.Certificate, error) { + now := ctx.Clock.Now() + + roots, err := ctx.CertificateStore.Certificates() + if err != nil { + return nil, err + } + + var leafCert *x509.Certificate + chain := []*x509.Certificate{} + + if sig.KeyInfo != nil { + certificates := sig.KeyInfo.X509Data.X509Certificates + // If the Signature includes KeyInfo, extract the certificate from there + if len(certificates) == 0 || sig.KeyInfo.X509Data.X509Certificates[0].Data == "" { + return nil, errors.New("missing X509Certificate within KeyInfo") + } + // parse all certificates + certs := []*x509.Certificate{} + var rootCert *x509.Certificate + for _, c := range certificates { + certData, err := base64.StdEncoding.DecodeString( + whiteSpace.ReplaceAllString(c.Data, "")) + if err != nil { + return nil, errors.New("Failed to parse certificate") + } + + cert, err := x509.ParseCertificate(certData) + if err != nil { + return nil, err + } + certs = append(certs, cert) + // set cert as root cert if in the root cert list + if contains(roots, cert) { + rootCert = cert + } + } + if rootCert == nil { + return nil, errors.New("Failed to find a root cert") + } + currentCert := rootCert + chain = append(chain, currentCert) + for i := 0; i < len(certs)-1; i++ { + // find cert signed by currentCert + cert := certs[i] + if bytes.Equal(cert.AuthorityKeyId, currentCert.SubjectKeyId) { + certPool := x509.NewCertPool() + certPool.AddCert(currentCert) + verifiedChain, err := cert.Verify(x509.VerifyOptions{ + Roots: certPool, + }) + if err != nil { + return nil, err + } + if len(verifiedChain) < 1 || len(verifiedChain[0]) < 2 { + return nil, errors.New("Certificate cannot be verified") + } + firstChain := verifiedChain[0] + if !(firstChain[1].Equal(currentCert)) { + return nil, errors.New("Certificate not in valid certificate chain") + } + currentCert = cert + chain = append(chain, currentCert) + } + } + leafCert = currentCert + } else { + // If the Signature doesn't have KeyInfo, Use the root certificate if there is only one + if len(roots) == 1 { + leafCert = roots[0] + } else { + return nil, errors.New("Missing x509 Element") + } + } + + if now.Before(leafCert.NotBefore) || now.After(leafCert.NotAfter) { + return nil, errors.New("Cert is not valid at this time") + } + + return chain, nil +} + // Validate verifies that the passed element contains a valid enveloped signature // matching a currently-valid certificate in the context's CertificateStore. func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error) { @@ -429,3 +551,23 @@ func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error return ctx.validateSignature(el, sig, cert) } + +// ValidateSignature validates the signature in `el` and returns the validated element as well as the certificate +// that signed the signature. +func (ctx *ValidationContext) ValidateSignature(el *etree.Element) (*etree.Element, []*x509.Certificate, error) { + // Make a copy of the element to avoid mutating the one we were passed. + el = el.Copy() + + sig, err := ctx.findSignature(el) + if err != nil { + return nil, nil, err + } + + certs, err := ctx.verifyCertificateChain(sig) + if err != nil { + return nil, nil, err + } + leafCert := certs[len(certs)-1] + validatedElement, err := ctx.validateSignature(el, sig, leafCert) + return validatedElement, certs, err +} From 1820770e90e92e2e755b96c3b3279adcf89626a3 Mon Sep 17 00:00:00 2001 From: Oscar Finnsson Date: Wed, 28 Feb 2018 16:46:33 +0100 Subject: [PATCH 2/4] Validating random certificate chains --- validate.go | 132 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 51 deletions(-) diff --git a/validate.go b/validate.go index e56a9dd..fbb2c1b 100644 --- a/validate.go +++ b/validate.go @@ -451,7 +451,7 @@ func (ctx *ValidationContext) verifyCertificate(sig *types.Signature) (*x509.Cer } // validates the certificate chain and returns the leaf certificate -func (ctx *ValidationContext) verifyCertificateChain(sig *types.Signature) ([]*x509.Certificate, error) { +func (ctx *ValidationContext) verifyCertificateChain(sig *types.Signature) ([][]*x509.Certificate, error) { now := ctx.Clock.Now() roots, err := ctx.CertificateStore.Certificates() @@ -459,18 +459,20 @@ func (ctx *ValidationContext) verifyCertificateChain(sig *types.Signature) ([]*x return nil, err } - var leafCert *x509.Certificate - chain := []*x509.Certificate{} - + chains := [][]*x509.Certificate{} + // create a chain for each root + for _, root := range roots { + chains = append(chains, []*x509.Certificate{root}) + } if sig.KeyInfo != nil { certificates := sig.KeyInfo.X509Data.X509Certificates // If the Signature includes KeyInfo, extract the certificate from there if len(certificates) == 0 || sig.KeyInfo.X509Data.X509Certificates[0].Data == "" { return nil, errors.New("missing X509Certificate within KeyInfo") } - // parse all certificates + // parse all certificates (skip root certs) certs := []*x509.Certificate{} - var rootCert *x509.Certificate + // var rootCert *x509.Certificate for _, c := range certificates { certData, err := base64.StdEncoding.DecodeString( whiteSpace.ReplaceAllString(c.Data, "")) @@ -482,55 +484,72 @@ func (ctx *ValidationContext) verifyCertificateChain(sig *types.Signature) ([]*x if err != nil { return nil, err } - certs = append(certs, cert) - // set cert as root cert if in the root cert list - if contains(roots, cert) { - rootCert = cert + + // skip root certs + if !contains(roots, cert) { + // skip old cert since it is not valid anylonger + if !(now.Before(cert.NotBefore) || now.After(cert.NotAfter)) { + // skip self signed certs + if !bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) { + certs = append(certs, cert) + } + } } } - if rootCert == nil { - return nil, errors.New("Failed to find a root cert") - } - currentCert := rootCert - chain = append(chain, currentCert) - for i := 0; i < len(certs)-1; i++ { - // find cert signed by currentCert - cert := certs[i] - if bytes.Equal(cert.AuthorityKeyId, currentCert.SubjectKeyId) { - certPool := x509.NewCertPool() - certPool.AddCert(currentCert) - verifiedChain, err := cert.Verify(x509.VerifyOptions{ - Roots: certPool, - }) - if err != nil { - return nil, err - } - if len(verifiedChain) < 1 || len(verifiedChain[0]) < 2 { - return nil, errors.New("Certificate cannot be verified") + changed := true + for changed { + changed = false + // keep list of not processed certs + newCerts := []*x509.Certificate{} + // for each non-root cert + for _, cert := range certs { + + processed := false + // copy list of certificate chains since it might be altered in the loop below + newChains := chains[:] + for _, chain := range chains { + leafCertInChain := chain[len(chain)-1] + // check if AuthorityKeyId matches any leaf cert in any cert chain + if bytes.Equal(cert.AuthorityKeyId, leafCertInChain.SubjectKeyId) { + // create cert pool of current leaf cert + certPool := x509.NewCertPool() + certPool.AddCert(leafCertInChain) + verifiedChain, err := cert.Verify(x509.VerifyOptions{ + Roots: certPool, + }) + if err != nil { + return nil, err + } + if len(verifiedChain) < 1 || len(verifiedChain[0]) < 2 { + return nil, errors.New("Certificate cannot be verified") + } + firstChain := verifiedChain[0] + if !(firstChain[0].Equal(cert)) { + return nil, errors.New("Certificate not in valid certificate chain") + } + newChain := append(chain, cert) + newChains = append(newChains, newChain) + // certificate is processed - should not be processed another time + processed = true + // new certificate chain in created + changed = true + } } - firstChain := verifiedChain[0] - if !(firstChain[1].Equal(currentCert)) { - return nil, errors.New("Certificate not in valid certificate chain") + // if cert is not added to any certificate chain - try to process is another time + if !processed { + newCerts = append(newCerts, cert) } - currentCert = cert - chain = append(chain, currentCert) + chains = newChains } + certs = newCerts } - leafCert = currentCert } else { - // If the Signature doesn't have KeyInfo, Use the root certificate if there is only one - if len(roots) == 1 { - leafCert = roots[0] - } else { + if len(roots) == 0 { return nil, errors.New("Missing x509 Element") } } - if now.Before(leafCert.NotBefore) || now.After(leafCert.NotAfter) { - return nil, errors.New("Cert is not valid at this time") - } - - return chain, nil + return chains, nil } // Validate verifies that the passed element contains a valid enveloped signature @@ -553,21 +572,32 @@ func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error } // ValidateSignature validates the signature in `el` and returns the validated element as well as the certificate -// that signed the signature. +// chain that signed the signature. func (ctx *ValidationContext) ValidateSignature(el *etree.Element) (*etree.Element, []*x509.Certificate, error) { // Make a copy of the element to avoid mutating the one we were passed. el = el.Copy() sig, err := ctx.findSignature(el) if err != nil { - return nil, nil, err + return nil, nil, errors.New("error finding signature, err: " + err.Error()) } - certs, err := ctx.verifyCertificateChain(sig) + certificateChains, err := ctx.verifyCertificateChain(sig) if err != nil { - return nil, nil, err + return nil, nil, errors.New("error verifying certificate chain, err: " + err.Error()) + } + // try to find out which certificate chain that signed + var validatedElement *etree.Element + signatureChain := []*x509.Certificate{} + for _, chain := range certificateChains { + leafCert := chain[len(chain)-1] + validatedElement, err = ctx.validateSignature(el, sig, leafCert) + if err == nil { + // found valid chain + signatureChain = chain + break + } } - leafCert := certs[len(certs)-1] - validatedElement, err := ctx.validateSignature(el, sig, leafCert) - return validatedElement, certs, err + + return validatedElement, signatureChain, err } From 20b6e10036ba6f10fa7cb91bb6cf013693cd1ee6 Mon Sep 17 00:00:00 2001 From: Oscar Finnsson Date: Wed, 28 Feb 2018 16:49:36 +0100 Subject: [PATCH 3/4] Fixing comments --- validate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validate.go b/validate.go index fbb2c1b..90a6fd1 100644 --- a/validate.go +++ b/validate.go @@ -572,7 +572,7 @@ func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error } // ValidateSignature validates the signature in `el` and returns the validated element as well as the certificate -// chain that signed the signature. +// chain that validates the element. The last certificate in the chain signed the validated element. func (ctx *ValidationContext) ValidateSignature(el *etree.Element) (*etree.Element, []*x509.Certificate, error) { // Make a copy of the element to avoid mutating the one we were passed. el = el.Copy() From de21135287d557ee53a63311f5845f525d979c11 Mon Sep 17 00:00:00 2001 From: Oscar Finnsson Date: Thu, 9 Aug 2018 11:00:05 +0200 Subject: [PATCH 4/4] Calculating root ID correctly and adding SHA384 as digest method --- validate.go | 20 +++++++++++--------- xml_constants.go | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/validate.go b/validate.go index 90a6fd1..2d9c1e5 100644 --- a/validate.go +++ b/validate.go @@ -290,12 +290,12 @@ func contains(roots []*x509.Certificate, cert *x509.Certificate) bool { } // findSignature searches for a Signature element referencing the passed root element. -func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature, error) { +func (ctx *ValidationContext) findSignature(rootEl *etree.Element) (*types.Signature, error) { var sig *types.Signature outerCtx := ctx // Traverse the tree looking for a Signature element - err := etreeutils.NSFindIterate(el, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error { + err := etreeutils.NSFindIterate(rootEl, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error { found := false err := etreeutils.NSFindIterateCtx(ctx, el, Namespace, SignedInfoTag, @@ -378,13 +378,15 @@ func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature } } } else { - idAttr := el.SelectAttr(outerCtx.IdAttribute) - // Traverse references in the signature to determine whether it has at least - // one reference to the top level element. If so, conclude the search. - for _, ref := range _sig.SignedInfo.References { - if ref.URI == "" || ref.URI[1:] == idAttr.Value { - sig = _sig - return etreeutils.ErrTraversalHalted + idAttr := rootEl.SelectAttr(outerCtx.IdAttribute) + if idAttr != nil { + // Traverse references in the signature to determine whether it has at least + // one reference to the top level element. If so, conclude the search. + for _, ref := range _sig.SignedInfo.References { + if ref.URI == "" || ref.URI[1:] == idAttr.Value { + sig = _sig + return etreeutils.ErrTraversalHalted + } } } } diff --git a/xml_constants.go b/xml_constants.go index c4b815b..dcd99ee 100644 --- a/xml_constants.go +++ b/xml_constants.go @@ -60,6 +60,7 @@ var digestAlgorithmIdentifiers = map[crypto.Hash]string{ crypto.SHA1: "http://www.w3.org/2000/09/xmldsig#sha1", crypto.SHA256: "http://www.w3.org/2001/04/xmlenc#sha256", crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512", + crypto.SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384", } var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}