From 8f89ab9339d2a3e18c84651b2ca6b7c5123a3804 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 15:19:25 -0800 Subject: [PATCH 01/17] Adding digidogo --- digidogo/main.go | 30 ++++++++++++++++++++++++++++ sign.go | 51 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 digidogo/main.go diff --git a/digidogo/main.go b/digidogo/main.go new file mode 100644 index 0000000..398fdb6 --- /dev/null +++ b/digidogo/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/beevik/etree" + dsig "github.com/russellhaering/goxmldsig" +) + +func main() { + // Generate a key and self-signed certificate for signing + randomKeyStore := dsig.RandomKeyStoreForTest() + ctx := dsig.NewDefaultSigningContext(randomKeyStore) + + // Sign the element + signedElement, err := ctx.SignEnvelopedReader("./test.dat") + if err != nil { + panic(err) + } + + // Serialize the signed element. It is important not to modify the element + // after it has been signed - even pretty-printing the XML will invalidate + // the signature. + doc := etree.NewDocument() + doc.SetRoot(signedElement) + str, err := doc.WriteToString() + if err != nil { + panic(err) + } + + println(str) +} diff --git a/sign.go b/sign.go index 2be34b7..420ae09 100644 --- a/sign.go +++ b/sign.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "errors" "fmt" + "io/ioutil" "github.com/beevik/etree" "github.com/russellhaering/goxmldsig/etreeutils" @@ -48,9 +49,12 @@ func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) { if err != nil { return nil, err } + return ctx.hash(canonical) +} +func (ctx *SigningContext) hash(data []byte) ([]byte, error) { hash := ctx.Hash.New() - _, err = hash.Write(canonical) + _, err := hash.Write(data) if err != nil { return nil, err } @@ -58,7 +62,7 @@ func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) { return hash.Sum(nil), nil } -func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) { +func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, enveloped bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() if digestAlgorithmIdentifier == "" { return nil, errors.New("unsupported hash mechanism") @@ -69,11 +73,6 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool return nil, errors.New("unsupported signature method") } - digest, err := ctx.digest(el) - if err != nil { - return nil, err - } - signedInfo := &etree.Element{ Tag: SignedInfoTag, Space: ctx.Prefix, @@ -89,14 +88,7 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool // /SignedInfo/Reference reference := ctx.createNamespacedElement(signedInfo, ReferenceTag) - - dataId := el.SelectAttrValue(ctx.IdAttribute, "") - if dataId == "" { - reference.CreateAttr(URIAttr, "") - } else { - reference.CreateAttr(URIAttr, "#"+dataId) - } - + reference.CreateAttr(URIAttr, uri) // /SignedInfo/Reference/Transforms transforms := ctx.createNamespacedElement(reference, TransformsTag) @@ -119,7 +111,18 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool } func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) { - signedInfo, err := ctx.constructSignedInfo(el, enveloped) + digest, err := ctx.digest(el) + if err != nil { + return nil, err + } + + dataId := el.SelectAttrValue(ctx.IdAttribute, "") + uri := "" + if dataId != "" { + uri = "#" + dataId + } + + signedInfo, err := ctx.constructSignedInfo(digest, uri, enveloped) if err != nil { return nil, err } @@ -167,7 +170,7 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) return nil, err } - digest, err := ctx.digest(detatchedSignedInfo) + digest, err = ctx.digest(detatchedSignedInfo) if err != nil { return nil, err } @@ -209,6 +212,20 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string return child } +func (ctx *SigningContext) SignEnvelopedReader(inputPath string) (*etree.Element, error) { + input, err := ioutil.ReadFile("./test.dat") + if err != nil { + panic(err) + } + + digest, err := ctx.hash(input) + if err != nil { + panic(err) + } + + return ctx.constructSignedInfo(digest, inputPath, true) +} + func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) { sig, err := ctx.ConstructSignature(el, true) if err != nil { From f89770c7b2e6c83ea96bb554331c6422e2df568c Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 15:24:38 -0800 Subject: [PATCH 02/17] Tests pass now --- sign.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sign.go b/sign.go index 420ae09..0879821 100644 --- a/sign.go +++ b/sign.go @@ -45,6 +45,10 @@ func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error { } func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) { + if ctx.GetDigestAlgorithmIdentifier() == "" { + return nil, errors.New("unsupported hash mechanism") + } + canonical, err := ctx.Canonicalizer.Canonicalize(el) if err != nil { return nil, err @@ -64,10 +68,6 @@ func (ctx *SigningContext) hash(data []byte) ([]byte, error) { func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, enveloped bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() - if digestAlgorithmIdentifier == "" { - return nil, errors.New("unsupported hash mechanism") - } - signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier() if signatureMethodIdentifier == "" { return nil, errors.New("unsupported signature method") From c1f2355ce4c06cc3c913563006bd1bcf47a179db Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 15:54:11 -0800 Subject: [PATCH 03/17] Generating signature --- digidogo/main.go | 4 +++- sign.go | 61 +++++++++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index 398fdb6..60e2ea9 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -1,6 +1,8 @@ package main import ( + "os" + "github.com/beevik/etree" dsig "github.com/russellhaering/goxmldsig" ) @@ -11,7 +13,7 @@ func main() { ctx := dsig.NewDefaultSigningContext(randomKeyStore) // Sign the element - signedElement, err := ctx.SignEnvelopedReader("./test.dat") + signedElement, err := ctx.SignEnvelopedReader(os.Args[1]) if err != nil { panic(err) } diff --git a/sign.go b/sign.go index 0879821..01590f5 100644 --- a/sign.go +++ b/sign.go @@ -66,7 +66,7 @@ func (ctx *SigningContext) hash(data []byte) ([]byte, error) { return hash.Sum(nil), nil } -func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, enveloped bool) (*etree.Element, error) { +func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, enveloped bool, transform bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier() if signatureMethodIdentifier == "" { @@ -91,13 +91,15 @@ func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, envelo reference.CreateAttr(URIAttr, uri) // /SignedInfo/Reference/Transforms - transforms := ctx.createNamespacedElement(reference, TransformsTag) - if enveloped { - envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag) - envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String()) + if transform { + transforms := ctx.createNamespacedElement(reference, TransformsTag) + if enveloped { + envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag) + envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String()) + } + canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag) + canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm())) } - canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag) - canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm())) // /SignedInfo/Reference/DigestMethod digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag) @@ -122,23 +124,12 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) uri = "#" + dataId } - signedInfo, err := ctx.constructSignedInfo(digest, uri, enveloped) + signedInfo, err := ctx.constructSignedInfo(digest, uri, enveloped, true) if err != nil { return nil, err } - sig := &etree.Element{ - Tag: SignatureTag, - Space: ctx.Prefix, - } - - xmlns := "xmlns" - if ctx.Prefix != "" { - xmlns += ":" + ctx.Prefix - } - - sig.CreateAttr(xmlns, Namespace) - sig.AddChild(signedInfo) + sig := ctx.baseSig(signedInfo) // When using xml-c14n11 (ie, non-exclusive canonicalization) the canonical form // of the SignedInfo must declare all namespaces that are in scope at it's final @@ -170,7 +161,28 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) return nil, err } - digest, err = ctx.digest(detatchedSignedInfo) + return ctx.constructSig(detatchedSignedInfo, sig) +} + +func (ctx *SigningContext) baseSig(signedInfo *etree.Element) *etree.Element { + sig := &etree.Element{ + Tag: SignatureTag, + Space: ctx.Prefix, + } + + xmlns := "xmlns" + if ctx.Prefix != "" { + xmlns += ":" + ctx.Prefix + } + + sig.CreateAttr(xmlns, Namespace) + sig.AddChild(signedInfo) + + return sig +} + +func (ctx *SigningContext) constructSig(signedInfo *etree.Element, sig *etree.Element) (*etree.Element, error) { + digest, err := ctx.digest(signedInfo) if err != nil { return nil, err } @@ -223,7 +235,12 @@ func (ctx *SigningContext) SignEnvelopedReader(inputPath string) (*etree.Element panic(err) } - return ctx.constructSignedInfo(digest, inputPath, true) + signedInfo, err := ctx.constructSignedInfo(digest, inputPath, false, false) + if err != nil { + panic(err) + } + + return ctx.constructSig(signedInfo, ctx.baseSig(signedInfo)) } func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) { From f9bc56a1a3e24e808c4d2942054322cfa635f76c Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 16:01:52 -0800 Subject: [PATCH 04/17] Fixing hardcoded path --- sign.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign.go b/sign.go index 01590f5..872eeb5 100644 --- a/sign.go +++ b/sign.go @@ -225,7 +225,7 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string } func (ctx *SigningContext) SignEnvelopedReader(inputPath string) (*etree.Element, error) { - input, err := ioutil.ReadFile("./test.dat") + input, err := ioutil.ReadFile(inputPath) if err != nil { panic(err) } From 36d3d94d82f9f3f949bacd2edef076da5f75b88c Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 19:52:19 -0800 Subject: [PATCH 05/17] Feature main sig (#2) * Adding support for enveloping signature and validating certificate chain * Validating random certificate chains * Fixing comments * Calculating root ID correctly and adding SHA384 as digest method * Tests pass again Co-authored-by: Oscar Finnsson --- types/signature.go | 11 +++ validate.go | 231 +++++++++++++++++++++++++++++++++++++++------ validate_test.go | 5 +- xml_constants.go | 1 + 4 files changed, 217 insertions(+), 31 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 1a94c9c..e82b22a 100644 --- a/validate.go +++ b/validate.go @@ -21,7 +21,7 @@ var ( // ErrMissingSignature indicates that no enveloped signature was found referencing // the top level element passed for signature verification. ErrMissingSignature = errors.New("Missing signature referencing the top-level element") - ErrInvalidSignature = errors.New( "Invalid Signature") + ErrInvalidSignature = errors.New("Invalid Signature") ) type ValidationContext struct { @@ -230,26 +230,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) { - idAttrEl := el.SelectAttr(ctx.IdAttribute) - idAttr := "" - if idAttrEl != nil { - idAttr = idAttrEl.Value + 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 { + 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 @@ -315,22 +344,18 @@ func validateShape(signatureEl *etree.Element) error { // findSignature searches for a Signature element referencing the passed root element. func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signature, error) { - idAttrEl := root.SelectAttr(ctx.IdAttribute) - idAttr := "" - if idAttrEl != nil { - idAttr = idAttrEl.Value - } - var sig *types.Signature + outerCtx := ctx // Traverse the tree looking for a Signature element - err := etreeutils.NSFindIterate(root, Namespace, SignatureTag, func(ctx etreeutils.NSContext, signatureEl *etree.Element) error { - err := validateShape(signatureEl) + err := etreeutils.NSFindIterate(root, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error { + err := validateShape(el) if err != nil { return err } + found := false - err = etreeutils.NSFindChildrenIterateCtx(ctx, signatureEl, Namespace, SignedInfoTag, + err = etreeutils.NSFindChildrenIterateCtx(ctx, el, Namespace, SignedInfoTag, func(ctx etreeutils.NSContext, signedInfo *etree.Element) error { detachedSignedInfo, err := etreeutils.NSDetatch(ctx, signedInfo) if err != nil { @@ -376,8 +401,8 @@ func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signatu return fmt.Errorf("invalid CanonicalizationMethod on Signature: %s", c14NAlgorithm) } - signatureEl.RemoveChild(signedInfo) - signatureEl.AddChild(canonicalSignedInfo) + el.RemoveChild(signedInfo) + el.AddChild(canonicalSignedInfo) found = true @@ -393,17 +418,32 @@ func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signatu // Unmarshal the signature into a structured Signature type _sig := &types.Signature{} - err = etreeutils.NSUnmarshalElement(ctx, signatureEl, _sig) + err = etreeutils.NSUnmarshalElement(ctx, el, _sig) if err != nil { 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 { - 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 := root.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 + } + } } } @@ -468,6 +508,108 @@ 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 + } + + 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 (skip root certs) + 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 + } + + // 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) + } + } + } + } + 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 + } + } + // if cert is not added to any certificate chain - try to process is another time + if !processed { + newCerts = append(newCerts, cert) + } + chains = newChains + } + certs = newCerts + } + } else { + if len(roots) == 0 { + return nil, errors.New("Missing x509 Element") + } + } + + return chains, 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) { @@ -486,3 +628,34 @@ 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 +// 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() + + sig, err := ctx.findSignature(el) + if err != nil { + return nil, nil, errors.New("error finding signature, err: " + err.Error()) + } + + certificateChains, err := ctx.verifyCertificateChain(sig) + if err != nil { + 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 + } + } + + return validatedElement, signatureChain, err +} diff --git a/validate_test.go b/validate_test.go index 4406f9f..ca12e6c 100644 --- a/validate_test.go +++ b/validate_test.go @@ -249,7 +249,6 @@ func TestValidateWithValid(t *testing.T) { require.NotEmpty(t, el) } - func TestValidateWithModified(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(modifiedToBeTodd)) @@ -268,7 +267,6 @@ func TestValidateWithModified(t *testing.T) { require.Error(t, err) } - func TestValidateWithModifiedAndSignatureEdited(t *testing.T) { doc := etree.NewDocument() err := doc.ReadFromBytes([]byte(spoofedAsTodd)) @@ -283,6 +281,9 @@ func TestValidateWithModifiedAndSignatureEdited(t *testing.T) { } vc := NewDefaultValidationContext(&certStore) + _, err = vc.findSignature(doc.Root()) + require.Error(t, err) + _, err = vc.Validate(doc.Root()) require.Error(t, err) } 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{} From e54fe4b65502ee634e12111d5ae9a7d71f734e40 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 4 Mar 2021 20:55:33 -0800 Subject: [PATCH 06/17] Some testing --- digidogo/main.go | 8 +++++++- sign.go | 14 ++++---------- sign_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index 60e2ea9..f0a0736 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -1,6 +1,7 @@ package main import ( + "io/ioutil" "os" "github.com/beevik/etree" @@ -13,7 +14,12 @@ func main() { ctx := dsig.NewDefaultSigningContext(randomKeyStore) // Sign the element - signedElement, err := ctx.SignEnvelopedReader(os.Args[1]) + input, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + panic(err) + } + + signedElement, err := ctx.SignEnvelopedReader(os.Args[1], input) if err != nil { panic(err) } diff --git a/sign.go b/sign.go index 872eeb5..bee05db 100644 --- a/sign.go +++ b/sign.go @@ -9,7 +9,6 @@ import ( "encoding/base64" "errors" "fmt" - "io/ioutil" "github.com/beevik/etree" "github.com/russellhaering/goxmldsig/etreeutils" @@ -224,20 +223,15 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string return child } -func (ctx *SigningContext) SignEnvelopedReader(inputPath string) (*etree.Element, error) { - input, err := ioutil.ReadFile(inputPath) - if err != nil { - panic(err) - } - +func (ctx *SigningContext) SignEnvelopedReader(uri string, input []byte) (*etree.Element, error) { digest, err := ctx.hash(input) if err != nil { - panic(err) + return nil, err } - signedInfo, err := ctx.constructSignedInfo(digest, inputPath, false, false) + signedInfo, err := ctx.constructSignedInfo(digest, uri, false, false) if err != nil { - panic(err) + return nil, err } return ctx.constructSig(signedInfo, ctx.baseSig(signedInfo)) diff --git a/sign_test.go b/sign_test.go index febfec6..9add9aa 100644 --- a/sign_test.go +++ b/sign_test.go @@ -2,7 +2,10 @@ package dsig import ( "crypto" + _ "crypto/sha1" + _ "crypto/sha256" "encoding/base64" + "reflect" "testing" "github.com/beevik/etree" @@ -126,3 +129,43 @@ func TestSignNonDefaultID(t *testing.T) { refURI := ref.SelectAttrValue("URI", "") require.Equal(t, refURI, "#"+id) } + +func TestSigningContext_SignEnvelopedReader(t *testing.T) { + type args struct { + uri string + input []byte + } + tests := []struct { + name string + args args + wantHash string + wantErr bool + }{ + {"Empty", args{"", []byte("")}, "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", false}, + {"Empty", args{"", []byte("asdasdasdasd")}, "ZrETKgFzkQsB7joV705pWDu/L38eRGLJnvvhuatb+Ag=", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := &SigningContext{ + Hash: crypto.SHA256, + KeyStore: RandomKeyStoreForTest(), + IdAttribute: "OtherID", + Prefix: DefaultPrefix, + Canonicalizer: MakeC14N11Canonicalizer(), + } + got, err := ctx.SignEnvelopedReader(tt.args.uri, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("SigningContext.SignEnvelopedReader() error = %v, wantErr %v", err, tt.wantErr) + return + } + gotEle := got.FindElement("//DigestValue") + if gotEle == nil { + t.Error(got) + } + gotHash := gotEle.Text() + if !reflect.DeepEqual(gotHash, tt.wantHash) { + t.Errorf("SigningContext.SignEnvelopedReader() = %v, want %v", gotHash, tt.wantHash) + } + }) + } +} From 4519f2069eb77f229843d9ea7f446a2c6b1fd791 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Fri, 5 Mar 2021 10:09:27 -0800 Subject: [PATCH 07/17] Preparing edoc. File is invalid right now --- digidogo/main.go | 86 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index f0a0736..0cba870 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -1,14 +1,21 @@ package main import ( + "archive/zip" "io/ioutil" + "log" + "mime" "os" + "path" + "regexp" "github.com/beevik/etree" dsig "github.com/russellhaering/goxmldsig" ) func main() { + inputName := os.Args[1] + // Generate a key and self-signed certificate for signing randomKeyStore := dsig.RandomKeyStoreForTest() ctx := dsig.NewDefaultSigningContext(randomKeyStore) @@ -19,20 +26,89 @@ func main() { panic(err) } - signedElement, err := ctx.SignEnvelopedReader(os.Args[1], input) + signedElement, err := ctx.SignEnvelopedReader(inputName, input) if err != nil { panic(err) } - // Serialize the signed element. It is important not to modify the element - // after it has been signed - even pretty-printing the XML will invalidate - // the signature. doc := etree.NewDocument() doc.SetRoot(signedElement) - str, err := doc.WriteToString() + + edoc, err := os.Create( + string(regexp.MustCompile("\\.[^\\.]+$").ReplaceAll( + []byte(inputName), []byte(".edoc"), + )), + ) if err != nil { panic(err) } + w := zip.NewWriter(edoc) + + { + f, err := w.Create("META-INF/manifest.xml") + if err != nil { + panic(err) + } + + root := etree.NewDocument() + manifest := root.CreateElement("manifest") + manifest.Space = "manifest" + + entry := manifest.CreateElement("file-entry") + entry.Space = "manifest" + entry.Attr = append(entry.Attr, etree.Attr{ + Space: "manifest", + Key: "full-path", + Value: "/", + }) + entry.Attr = append(entry.Attr, etree.Attr{ + Space: "manifest", + Key: "media-type", + Value: "application/vnd.etsi.asic-e+zip", + }) + + fileEntry := manifest.CreateElement("file-entry") + fileEntry.Space = "manifest" + fileEntry.Attr = append(fileEntry.Attr, etree.Attr{ + Space: "manifest", + Key: "full-path", + Value: path.Base(inputName), + }) + fileEntry.Attr = append(fileEntry.Attr, etree.Attr{ + Space: "manifest", + Key: "media-type", + Value: mime.TypeByExtension(inputName), + }) + + output, err := root.WriteToString() + println(output) + if err != nil { + panic(err) + } + f.Write([]byte(output)) + } + edocSigns, err := w.Create("META-INF/edoc-signatures-S1.xml") + if err != nil { + panic(err) + } + str, err := doc.WriteToString() + if err != nil { + panic(err) + } println(str) + edocSigns.Write([]byte(str)) + + f, err := w.Create(path.Base(inputName)) + if err != nil { + panic(err) + } + f.Write(input) + + if err := w.Close(); err != nil { + log.Fatal(err) + } + if err := edoc.Close(); err != nil { + log.Fatal(err) + } } From 5958ab2ea0e97b42109fd448fdd29ae0ff22a799 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Fri, 5 Mar 2021 11:50:19 -0800 Subject: [PATCH 08/17] Better matches edoc sample I have --- digidogo/main.go | 29 +++++++++++++++++++++++++++-- sign.go | 41 ++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index 0cba870..44c7d57 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -26,12 +26,13 @@ func main() { panic(err) } - signedElement, err := ctx.SignEnvelopedReader(inputName, input) + signedElement, err := ctx.SignXAdES(inputName, input) if err != nil { panic(err) } doc := etree.NewDocument() + doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8" standalone="no"`) doc.SetRoot(signedElement) edoc, err := os.Create( @@ -44,6 +45,12 @@ func main() { } w := zip.NewWriter(edoc) + mimetype, err := w.Create("mimetype") + if err != nil { + panic(err) + } + mimetype.Write([]byte("application/vnd.etsi.asic-e+zip")) + { f, err := w.Create("META-INF/manifest.xml") if err != nil { @@ -51,8 +58,20 @@ func main() { } root := etree.NewDocument() + root.CreateProcInst("xml", `version="1.0" encoding="UTF-8" standalone="no"`) manifest := root.CreateElement("manifest") manifest.Space = "manifest" + manifest.Attr = append(manifest.Attr, etree.Attr{ + Space: "xmlns", + Key: "manifest", + Value: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0", + }) + + manifest.Attr = append(manifest.Attr, etree.Attr{ + Space: "manifest", + Key: "version", + Value: "1.2", + }) entry := manifest.CreateElement("file-entry") entry.Space = "manifest" @@ -74,10 +93,16 @@ func main() { Key: "full-path", Value: path.Base(inputName), }) + mimetype := mime.TypeByExtension(inputName) + if mimetype == "" { + mimetype = map[string]string{ + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }[path.Ext(inputName)] + } fileEntry.Attr = append(fileEntry.Attr, etree.Attr{ Space: "manifest", Key: "media-type", - Value: mime.TypeByExtension(inputName), + Value: mimetype, }) output, err := root.WriteToString() diff --git a/sign.go b/sign.go index bee05db..73c0778 100644 --- a/sign.go +++ b/sign.go @@ -75,6 +75,9 @@ func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, envelo signedInfo := &etree.Element{ Tag: SignedInfoTag, Space: ctx.Prefix, + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-SignedInfo"}, + }, } // /SignedInfo/CanonicalizationMethod @@ -87,6 +90,7 @@ func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, envelo // /SignedInfo/Reference reference := ctx.createNamespacedElement(signedInfo, ReferenceTag) + reference.CreateAttr("Id", "S1-ref-1") reference.CreateAttr(URIAttr, uri) // /SignedInfo/Reference/Transforms @@ -164,20 +168,17 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) } func (ctx *SigningContext) baseSig(signedInfo *etree.Element) *etree.Element { - sig := &etree.Element{ + return &etree.Element{ Tag: SignatureTag, Space: ctx.Prefix, + Attr: []etree.Attr{ + {Space: "xmlns", Key: ctx.Prefix, Value: Namespace}, + {Key: "Id", Value: "S1"}, + }, + Child: []etree.Token{ + signedInfo, + }, } - - xmlns := "xmlns" - if ctx.Prefix != "" { - xmlns += ":" + ctx.Prefix - } - - sig.CreateAttr(xmlns, Namespace) - sig.AddChild(signedInfo) - - return sig } func (ctx *SigningContext) constructSig(signedInfo *etree.Element, sig *etree.Element) (*etree.Element, error) { @@ -223,6 +224,24 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string return child } +func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, error) { + sig := etree.NewElement("XAdESSignatures") + sig.Space = "asic" + sig.Attr = append(sig.Attr, etree.Attr{ + Space: "xmlns", + Key: "asic", + Value: "http://uri.etsi.org/02918/v1.2.1#", + }) + + dsig, err := ctx.SignEnvelopedReader(uri, input) + if err != nil { + return nil, err + } + sig.AddChild(dsig) + + return sig, nil +} + func (ctx *SigningContext) SignEnvelopedReader(uri string, input []byte) (*etree.Element, error) { digest, err := ctx.hash(input) if err != nil { From 9d7052cddf9853c3c60f452532562b40ae45e1b9 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Sat, 6 Mar 2021 10:11:54 -0800 Subject: [PATCH 09/17] Adding some more XAdES parts --- sign.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/sign.go b/sign.go index 73c0778..d5f987a 100644 --- a/sign.go +++ b/sign.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "errors" "fmt" + "time" "github.com/beevik/etree" "github.com/russellhaering/goxmldsig/etreeutils" @@ -224,6 +225,98 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string return child } +func (ctx *SigningContext) xadesSigningCertificate() (*etree.Element, error) { + sigCert := &etree.Element{ + Space: "xades", + Tag: "SigningCertificate", + } + + _, cert, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + h, err := ctx.hash(cert) + if err != nil { + return nil, err + } + sigCert.Child = append(sigCert.Child, + &etree.Element{ + Space: "xades", + Tag: "Cert", + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "CertDigest", + Child: []etree.Token{ + &etree.Element{ + Space: "ds", + Tag: "DigestMethod", + Attr: []etree.Attr{ + {Key: "Algorithm", Value: "http://www.w3.org/2001/04/xmlenc#sha256"}, + }, + }, + &etree.Element{ + Space: "ds", + Tag: "DigestValue", + Child: []etree.Token{ + &etree.CharData{Data: base64.StdEncoding.EncodeToString(h)}, + }, + }, + }, + }, + &etree.Element{ + Space: "xades", + Tag: "IssuerSerial", + Child: []etree.Token{ + &etree.Element{ + Space: "ds", + Tag: "X509IssuerName", + Child: []etree.Token{ + &etree.CharData{Data: "CN=,O=,C="}, + }, + }, + &etree.Element{ + Space: "ds", + Tag: "X509IssuerSerialNumber", + Child: []etree.Token{ + &etree.CharData{Data: "serialnumber"}, + }, + }, + }, + }, + }, + }, + ) + + return sigCert, nil +} + +func (ctx *SigningContext) xadesSignedSignatureProperties() (*etree.Element, error) { + sigCert, err := ctx.xadesSigningCertificate() + if err != nil { + return nil, err + } + + return &etree.Element{ + Space: "xades", + Tag: "SignedSignatureProperties", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-SignedSignatureProperties"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "SigningTime", + Child: []etree.Token{ + &etree.CharData{Data: time.Now().Format(time.RFC3339)}, + }, + }, + sigCert, + }, + }, nil +} + func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, error) { sig := etree.NewElement("XAdESSignatures") sig.Space = "asic" @@ -237,6 +330,39 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, if err != nil { return nil, err } + + sigProp, err := ctx.xadesSignedSignatureProperties() + if err != nil { + return nil, err + } + + dsig.AddChild(&etree.Element{ + Space: "ds", + Tag: "Object", + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "QualifyingProperties", + Attr: []etree.Attr{ + {Space: "xmlns", Key: "xades", Value: "http://uri.etsi.org/01903/v1.3.2#"}, + {Key: "Id", Value: "S1-QualifyingProperties"}, + {Key: "Target", Value: "#S1"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "SignedProperties", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-SignedProperties"}, + }, + Child: []etree.Token{ + sigProp, + }, + }, + }, + }, + }, + }) sig.AddChild(dsig) return sig, nil From ac0c911076b236909ba97aac5bdd5323a489148a Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Mon, 8 Mar 2021 16:59:02 -0800 Subject: [PATCH 10/17] File format is recognized by eparaksts.lv, but the signatures are understandably wrong --- sign.go | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/sign.go b/sign.go index d5f987a..6b40647 100644 --- a/sign.go +++ b/sign.go @@ -313,6 +313,94 @@ func (ctx *SigningContext) xadesSignedSignatureProperties() (*etree.Element, err }, }, sigCert, + &etree.Element{ + Space: "xades", + Tag: "SignatureProductionPlace", + }, + &etree.Element{ + Space: "xades", + Tag: "SignerRole", + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "ClaimedRoles", + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "ClaimedRole", + }, + }, + }, + }, + }, + }, + }, nil +} + +func (ctx *SigningContext) xadesUnsignedSignatureProperties() (*etree.Element, error) { + return &etree.Element{ + Space: "xades", + Tag: "UnsignedProperties", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-UnsignedProperties"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "UnsignedSignatureProperties", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-UnsignedSignatureProperties"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "SignatureTimeStamp", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-ts-0"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "ds", + Tag: "CanonicalizationMethod", + Attr: []etree.Attr{ + {Key: "Algorithm", Value: "http://www.w3.org/2006/12/xml-c14n11"}, + }, + Child: []etree.Token{}, + }, + &etree.Element{ + Space: "xades", + Tag: "EncapsulatedTimeStamp", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.CharData{Data: "Some cool hash"}, + }, + }, + }, + }, + &etree.Element{ + Space: "xades", + Tag: "RevocationValues", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "OCSPValues", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "EncapsulatedOCSPValue", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.CharData{Data: "Some cool hash"}, + }, + }, + }, + }, + }, + }, + }, + }, }, }, nil } @@ -336,6 +424,34 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, return nil, err } + sigObjProp := &etree.Element{ + Space: "xades", + Tag: "SignedDataObjectProperties", + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "DataObjectFormat", + Attr: []etree.Attr{ + {Key: "ObjectReference", Value: "#S1-ref-1"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "MimeType", + Child: []etree.Token{ + &etree.CharData{Data: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + }, + }, + }, + }, + }, + } + + unsigProps, err := ctx.xadesUnsignedSignatureProperties() + if err != nil { + return nil, err + } + dsig.AddChild(&etree.Element{ Space: "ds", Tag: "Object", @@ -356,9 +472,10 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, {Key: "Id", Value: "S1-SignedProperties"}, }, Child: []etree.Token{ - sigProp, + sigProp, sigObjProp, }, }, + unsigProps, }, }, }, From c78dba46f6c5d4fa9cdc108ca0435fe5104eda34 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Tue, 9 Mar 2021 20:28:08 -0800 Subject: [PATCH 11/17] Adding RFC3161 support. edoc still not validating --- rfc3161/timestamp.go | 59 +++++++++++++++++++++++++++++++++++++++ rfc3161/timestamp_test.go | 40 ++++++++++++++++++++++++++ sign.go | 30 ++++++++++++++++---- sign_test.go | 30 ++++++++++++++++++++ 4 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 rfc3161/timestamp.go create mode 100644 rfc3161/timestamp_test.go diff --git a/rfc3161/timestamp.go b/rfc3161/timestamp.go new file mode 100644 index 0000000..d51579e --- /dev/null +++ b/rfc3161/timestamp.go @@ -0,0 +1,59 @@ +package rfc3161 + +import ( + "bytes" + "encoding/base64" + "io/ioutil" + "net/http" +) + +const ( + TsaFreeTsa = "http://freetsa.org/tsr" + TsaCertum = "http://time.certum.pl" + TsaComodora = "http://timestamp.comodoca.com/rfc3161" +) + +func TimestampRequest(data []byte, url string) (*http.Request, error) { + req, err := http.NewRequest( + http.MethodPost, + url, + bytes.NewBuffer(data), + ) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/timestamp-query") + req.Header.Set("Accept", "application/timestamp-reply") + req.Header.Set("Connection", "Close") + req.Header.Set("Cache-Control", "no-cache") + + return req, nil +} + +func TimestampResponse(data []byte, url string) (*http.Response, error) { + req, err := TimestampRequest(data, url) + if err != nil { + return nil, err + } + + res, err := http.DefaultClient.Do(req) + + return res, err +} + +func Timestamp(data []byte, url string) (*string, error) { + res, err := TimestampResponse(data, url) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + encoded := base64.StdEncoding.EncodeToString(body) + + return &encoded, nil +} diff --git a/rfc3161/timestamp_test.go b/rfc3161/timestamp_test.go new file mode 100644 index 0000000..4040ee8 --- /dev/null +++ b/rfc3161/timestamp_test.go @@ -0,0 +1,40 @@ +package rfc3161 + +import ( + "encoding/base64" + "io/ioutil" + "testing" +) + +func TestTimestampResponse(t *testing.T) { + type args struct { + data []byte + url string + } + tests := []struct { + name string + args args + want int + wantErr bool + }{ + {"Empty", args{[]byte(""), TsaFreeTsa}, 200, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := TimestampResponse(tt.args.data, tt.args.url) + if (err != nil) != tt.wantErr { + t.Errorf("TimestampResponse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.StatusCode != tt.want { + t.Errorf("TimestampResponse() = %v, want %v", got, tt.want) + } + body, err := ioutil.ReadAll(got.Body) + if tt.want == 200 && err != nil { + t.Error(err) + } else if tt.want == 200 { + t.Error(base64.StdEncoding.EncodeToString(body)) + } + }) + } +} diff --git a/sign.go b/sign.go index 6b40647..ccfa941 100644 --- a/sign.go +++ b/sign.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" _ "crypto/sha1" _ "crypto/sha256" + "crypto/x509" "encoding/base64" "errors" "fmt" @@ -13,6 +14,7 @@ import ( "github.com/beevik/etree" "github.com/russellhaering/goxmldsig/etreeutils" + "github.com/russellhaering/goxmldsig/rfc3161" ) type SigningContext struct { @@ -240,6 +242,12 @@ func (ctx *SigningContext) xadesSigningCertificate() (*etree.Element, error) { if err != nil { return nil, err } + + x509Cert, err := x509.ParseCertificate(cert) + if err != nil { + return nil, err + } + sigCert.Child = append(sigCert.Child, &etree.Element{ Space: "xades", @@ -273,14 +281,14 @@ func (ctx *SigningContext) xadesSigningCertificate() (*etree.Element, error) { Space: "ds", Tag: "X509IssuerName", Child: []etree.Token{ - &etree.CharData{Data: "CN=,O=,C="}, + &etree.CharData{Data: x509Cert.Issuer.CommonName}, }, }, &etree.Element{ Space: "ds", Tag: "X509IssuerSerialNumber", Child: []etree.Token{ - &etree.CharData{Data: "serialnumber"}, + &etree.CharData{Data: x509Cert.Issuer.SerialNumber}, }, }, }, @@ -337,7 +345,12 @@ func (ctx *SigningContext) xadesSignedSignatureProperties() (*etree.Element, err }, nil } -func (ctx *SigningContext) xadesUnsignedSignatureProperties() (*etree.Element, error) { +func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature []byte) (*etree.Element, error) { + timestamp, err := rfc3161.Timestamp(signature, rfc3161.TsaFreeTsa) + if err != nil { + return nil, err + } + return &etree.Element{ Space: "xades", Tag: "UnsignedProperties", @@ -372,7 +385,7 @@ func (ctx *SigningContext) xadesUnsignedSignatureProperties() (*etree.Element, e Tag: "EncapsulatedTimeStamp", Attr: []etree.Attr{}, Child: []etree.Token{ - &etree.CharData{Data: "Some cool hash"}, + &etree.CharData{Data: *timestamp}, }, }, }, @@ -419,6 +432,13 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, return nil, err } + rawSig, err := base64.StdEncoding.DecodeString( + dsig.FindElementPath(etree.MustCompilePath("//" + SignatureValueTag)).Text(), + ) + if err != nil { + return nil, err + } + sigProp, err := ctx.xadesSignedSignatureProperties() if err != nil { return nil, err @@ -447,7 +467,7 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, }, } - unsigProps, err := ctx.xadesUnsignedSignatureProperties() + unsigProps, err := ctx.xadesUnsignedSignatureProperties(rawSig) if err != nil { return nil, err } diff --git a/sign_test.go b/sign_test.go index 9add9aa..a48155c 100644 --- a/sign_test.go +++ b/sign_test.go @@ -169,3 +169,33 @@ func TestSigningContext_SignEnvelopedReader(t *testing.T) { }) } } + +func TestSigningContext_SignXAdES(t *testing.T) { + type args struct { + uri string + input []byte + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"Empty", args{"./fake", []byte("")}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := &SigningContext{ + Hash: crypto.SHA256, + KeyStore: RandomKeyStoreForTest(), + IdAttribute: "OtherID", + Prefix: DefaultPrefix, + Canonicalizer: MakeC14N11Canonicalizer(), + } + _, err := ctx.SignXAdES(tt.args.uri, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("SigningContext.SignXAdES() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} From 8228bef10a06d78dfcaca606f8bc4bc2517d488c Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Wed, 10 Mar 2021 20:39:48 -0800 Subject: [PATCH 12/17] Supporting OCSP --- digidogo/main.go | 18 ++-- go.mod | 1 + go.sum | 8 ++ rfc3161/timestamp.go | 1 + sign.go | 199 +++++++++++++++++++++++++++++++------------ 5 files changed, 165 insertions(+), 62 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index 44c7d57..64967d6 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -15,6 +15,12 @@ import ( func main() { inputName := os.Args[1] + mimetype := mime.TypeByExtension(inputName) + if mimetype == "" { + mimetype = map[string]string{ + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }[path.Ext(inputName)] + } // Generate a key and self-signed certificate for signing randomKeyStore := dsig.RandomKeyStoreForTest() @@ -26,7 +32,7 @@ func main() { panic(err) } - signedElement, err := ctx.SignXAdES(inputName, input) + signedElement, err := ctx.SignXAdES(path.Base(inputName), mimetype, input) if err != nil { panic(err) } @@ -45,11 +51,11 @@ func main() { } w := zip.NewWriter(edoc) - mimetype, err := w.Create("mimetype") + mimetypeFile, err := w.Create("mimetype") if err != nil { panic(err) } - mimetype.Write([]byte("application/vnd.etsi.asic-e+zip")) + mimetypeFile.Write([]byte("application/vnd.etsi.asic-e+zip")) { f, err := w.Create("META-INF/manifest.xml") @@ -93,12 +99,6 @@ func main() { Key: "full-path", Value: path.Base(inputName), }) - mimetype := mime.TypeByExtension(inputName) - if mimetype == "" { - mimetype = map[string]string{ - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - }[path.Ext(inputName)] - } fileEntry.Attr = append(fileEntry.Attr, etree.Attr{ Space: "manifest", Key: "media-type", diff --git a/go.mod b/go.mod index 48ee569..bd99543 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/beevik/etree v1.1.0 github.com/jonboulle/clockwork v0.2.0 github.com/stretchr/testify v1.6.1 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 ) diff --git a/go.sum b/go.sum index e20e379..3888410 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/rfc3161/timestamp.go b/rfc3161/timestamp.go index d51579e..615fd67 100644 --- a/rfc3161/timestamp.go +++ b/rfc3161/timestamp.go @@ -48,6 +48,7 @@ func Timestamp(data []byte, url string) (*string, error) { return nil, err } + defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err diff --git a/sign.go b/sign.go index ccfa941..21bf46f 100644 --- a/sign.go +++ b/sign.go @@ -1,6 +1,7 @@ package dsig import ( + "bytes" "crypto" "crypto/rand" "crypto/rsa" @@ -10,11 +11,15 @@ import ( "encoding/base64" "errors" "fmt" + "io/ioutil" + "net/http" + "net/url" "time" "github.com/beevik/etree" "github.com/russellhaering/goxmldsig/etreeutils" "github.com/russellhaering/goxmldsig/rfc3161" + "golang.org/x/crypto/ocsp" ) type SigningContext struct { @@ -227,23 +232,56 @@ func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string return child } -func (ctx *SigningContext) xadesSigningCertificate() (*etree.Element, error) { - sigCert := &etree.Element{ - Space: "xades", - Tag: "SigningCertificate", +func oscpResponse(x509Cert *x509.Certificate) ([]byte, error) { + if len(x509Cert.OCSPServer) == 0 { + return nil, nil } + for _, ocspServer := range x509Cert.OCSPServer { + x509IssuerCert, err := x509.ParseCertificate(x509Cert.RawIssuer) + if err != nil { + return nil, err + } - _, cert, err := ctx.KeyStore.GetKeyPair() - if err != nil { - return nil, err + ocspReq, err := ocsp.CreateRequest(x509Cert, x509IssuerCert, &ocsp.RequestOptions{Hash: crypto.SHA1}) + if err != nil { + return nil, err + } + + httpRequest, err := http.NewRequest(http.MethodPost, ocspServer, bytes.NewBuffer(ocspReq)) + if err != nil { + return nil, err + } + ocspUrl, err := url.Parse(ocspServer) + if err != nil { + return nil, err + } + httpRequest.Header.Add("Content-Type", "application/ocsp-request") + httpRequest.Header.Add("Accept", "application/ocsp-response") + httpRequest.Header.Add("host", ocspUrl.Host) + httpClient := &http.Client{} + httpResponse, err := httpClient.Do(httpRequest) + if err != nil { + return nil, err + } + if httpResponse.StatusCode == http.StatusOK { + defer httpResponse.Body.Close() + output, err := ioutil.ReadAll(httpResponse.Body) + if err != nil { + return nil, err + } + return output, nil + } } + return nil, errors.New("All OSCP Servers failed") +} - h, err := ctx.hash(cert) - if err != nil { - return nil, err +func (ctx *SigningContext) xadesSigningCertificate(x509Cert *x509.Certificate) (*etree.Element, error) { + sigCert := &etree.Element{ + Space: "xades", + Tag: "SigningCertificate", } - x509Cert, err := x509.ParseCertificate(cert) + h, err := ctx.hash(x509Cert.Raw) if err != nil { return nil, err } @@ -300,8 +338,8 @@ func (ctx *SigningContext) xadesSigningCertificate() (*etree.Element, error) { return sigCert, nil } -func (ctx *SigningContext) xadesSignedSignatureProperties() (*etree.Element, error) { - sigCert, err := ctx.xadesSigningCertificate() +func (ctx *SigningContext) xadesSignedSignatureProperties(x509Cert *x509.Certificate) (*etree.Element, error) { + sigCert, err := ctx.xadesSigningCertificate(x509Cert) if err != nil { return nil, err } @@ -345,13 +383,36 @@ func (ctx *SigningContext) xadesSignedSignatureProperties() (*etree.Element, err }, nil } -func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature []byte) (*etree.Element, error) { +func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature, ocspResponse []byte) (*etree.Element, error) { timestamp, err := rfc3161.Timestamp(signature, rfc3161.TsaFreeTsa) if err != nil { return nil, err } - return &etree.Element{ + revokeVal := &etree.Element{ + Space: "xades", + Tag: "RevocationValues", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "OCSPValues", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.Element{ + Space: "xades", + Tag: "EncapsulatedOCSPValue", + Attr: []etree.Attr{}, + Child: []etree.Token{ + &etree.CharData{Data: base64.StdEncoding.EncodeToString(ocspResponse)}, + }, + }, + }, + }, + }, + } + + ele := &etree.Element{ Space: "xades", Tag: "UnsignedProperties", Attr: []etree.Attr{ @@ -390,35 +451,21 @@ func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature []byte) (* }, }, }, - &etree.Element{ - Space: "xades", - Tag: "RevocationValues", - Attr: []etree.Attr{}, - Child: []etree.Token{ - &etree.Element{ - Space: "xades", - Tag: "OCSPValues", - Attr: []etree.Attr{}, - Child: []etree.Token{ - &etree.Element{ - Space: "xades", - Tag: "EncapsulatedOCSPValue", - Attr: []etree.Attr{}, - Child: []etree.Token{ - &etree.CharData{Data: "Some cool hash"}, - }, - }, - }, - }, - }, - }, }, }, }, - }, nil + } + + if ocspResponse != nil { + ele.FindElementPath( + etree.MustCompilePath("//UnsignedSignatureProperties"), + ).AddChild(revokeVal) + } + + return ele, nil } -func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, error) { +func (ctx *SigningContext) SignXAdES(uri string, mimetype string, input []byte) (*etree.Element, error) { sig := etree.NewElement("XAdESSignatures") sig.Space = "asic" sig.Attr = append(sig.Attr, etree.Attr{ @@ -427,6 +474,16 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, Value: "http://uri.etsi.org/02918/v1.2.1#", }) + _, cert, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + x509Cert, err := x509.ParseCertificate(cert) + if err != nil { + return nil, err + } + dsig, err := ctx.SignEnvelopedReader(uri, input) if err != nil { return nil, err @@ -439,7 +496,7 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, return nil, err } - sigProp, err := ctx.xadesSignedSignatureProperties() + sigProp, err := ctx.xadesSignedSignatureProperties(x509Cert) if err != nil { return nil, err } @@ -459,7 +516,7 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, Space: "xades", Tag: "MimeType", Child: []etree.Token{ - &etree.CharData{Data: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + &etree.CharData{Data: mimetype}, }, }, }, @@ -467,11 +524,57 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, }, } - unsigProps, err := ctx.xadesUnsignedSignatureProperties(rawSig) + oscpRes, err := oscpResponse(x509Cert) + if err != nil { + return nil, err + } + + unsigProps, err := ctx.xadesUnsignedSignatureProperties(rawSig, oscpRes) if err != nil { return nil, err } + sigProps := &etree.Element{ + Space: "xades", + Tag: "SignedProperties", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-SignedProperties"}, + }, + Child: []etree.Token{ + sigProp, sigObjProp, + }, + } + sigDigest, err := ctx.digest(sigProps) + if err != nil { + return nil, err + } + + dsig.FindElementPath(etree.MustCompilePath("//SignedInfo")).AddChild(&etree.Element{ + Space: "ds", + Tag: "Reference", + Attr: []etree.Attr{ + {Key: "Id", Value: "S1-ref-SignedProperties"}, + {Key: "Type", Value: "http://uri.etsi.org/01903#SignedProperties"}, + {Key: "URI", Value: "#S1-SignedProperties"}, + }, + Child: []etree.Token{ + &etree.Element{ + Space: "ds", + Tag: "DigestMethod", + Attr: []etree.Attr{ + {Key: "Algorithm", Value: "http://www.w3.org/2001/04/xmlenc#sha256"}, + }, + }, + &etree.Element{ + Space: "ds", + Tag: "DigestValue", + Child: []etree.Token{ + &etree.CharData{Data: base64.StdEncoding.EncodeToString(sigDigest)}, + }, + }, + }, + }) + dsig.AddChild(&etree.Element{ Space: "ds", Tag: "Object", @@ -485,17 +588,7 @@ func (ctx *SigningContext) SignXAdES(uri string, input []byte) (*etree.Element, {Key: "Target", Value: "#S1"}, }, Child: []etree.Token{ - &etree.Element{ - Space: "xades", - Tag: "SignedProperties", - Attr: []etree.Attr{ - {Key: "Id", Value: "S1-SignedProperties"}, - }, - Child: []etree.Token{ - sigProp, sigObjProp, - }, - }, - unsigProps, + sigProps, unsigProps, }, }, }, From de47e3fe9089515dc1211989d7f8b9d174063d13 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 11 Mar 2021 15:47:11 -0800 Subject: [PATCH 13/17] Using correct algorithm values --- sign.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sign.go b/sign.go index 21bf46f..b441225 100644 --- a/sign.go +++ b/sign.go @@ -299,7 +299,7 @@ func (ctx *SigningContext) xadesSigningCertificate(x509Cert *x509.Certificate) ( Space: "ds", Tag: "DigestMethod", Attr: []etree.Attr{ - {Key: "Algorithm", Value: "http://www.w3.org/2001/04/xmlenc#sha256"}, + {Key: "Algorithm", Value: ctx.GetDigestAlgorithmIdentifier()}, }, }, &etree.Element{ @@ -437,7 +437,7 @@ func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature, ocspRespo Space: "ds", Tag: "CanonicalizationMethod", Attr: []etree.Attr{ - {Key: "Algorithm", Value: "http://www.w3.org/2006/12/xml-c14n11"}, + {Key: "Algorithm", Value: string(ctx.Canonicalizer.Algorithm())}, }, Child: []etree.Token{}, }, @@ -562,7 +562,7 @@ func (ctx *SigningContext) SignXAdES(uri string, mimetype string, input []byte) Space: "ds", Tag: "DigestMethod", Attr: []etree.Attr{ - {Key: "Algorithm", Value: "http://www.w3.org/2001/04/xmlenc#sha256"}, + {Key: "Algorithm", Value: ctx.GetDigestAlgorithmIdentifier()}, }, }, &etree.Element{ From fef1eba824e804f55da9e33e5f2a32f79b7b926d Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Thu, 11 Mar 2021 19:48:59 -0800 Subject: [PATCH 14/17] Adding self ref testing --- digidogo/main.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/digidogo/main.go b/digidogo/main.go index 64967d6..e350d9e 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -2,6 +2,7 @@ package main import ( "archive/zip" + "crypto/x509" "io/ioutil" "log" "mime" @@ -25,6 +26,17 @@ func main() { // Generate a key and self-signed certificate for signing randomKeyStore := dsig.RandomKeyStoreForTest() ctx := dsig.NewDefaultSigningContext(randomKeyStore) + _, cert, err := randomKeyStore.GetKeyPair() + if err != nil { + panic(err) + } + root, err := x509.ParseCertificate(cert) + if err != nil { + panic(err) + } + validCtx := dsig.NewDefaultValidationContext(&dsig.MemoryX509CertificateStore{ + Roots: []*x509.Certificate{root}, + }) // Sign the element input, err := ioutil.ReadFile(os.Args[1]) @@ -36,6 +48,10 @@ func main() { if err != nil { panic(err) } + if _, err := validCtx.Validate(signedElement); err != nil { + log.Fatal("Failed Validation ", err) + panic(err) + } doc := etree.NewDocument() doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8" standalone="no"`) From 046e3cf409045b590a7404ed8c977e10ffab9957 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Sun, 14 Mar 2021 15:22:48 -0700 Subject: [PATCH 15/17] Verify all signatures --- validate.go | 102 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/validate.go b/validate.go index 1a94c9c..164083c 100644 --- a/validate.go +++ b/validate.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "errors" "fmt" + "log" "regexp" "github.com/beevik/etree" @@ -21,7 +22,7 @@ var ( // ErrMissingSignature indicates that no enveloped signature was found referencing // the top level element passed for signature verification. ErrMissingSignature = errors.New("Missing signature referencing the top-level element") - ErrInvalidSignature = errors.New( "Invalid Signature") + ErrInvalidSignature = errors.New("Invalid Signature") ) type ValidationContext struct { @@ -230,58 +231,73 @@ func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicaliz } func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) { - idAttrEl := el.SelectAttr(ctx.IdAttribute) - idAttr := "" - if idAttrEl != nil { - idAttr = idAttrEl.Value - } - - var ref *types.Reference - + transformed := el.Copy() // Find the first reference which references the top-level element - for _, _ref := range sig.SignedInfo.References { - if _ref.URI == "" || _ref.URI[1:] == idAttr { - ref = &_ref + for _, ref := range sig.SignedInfo.References { + // 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 } - } - // 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 - } + referencedEl := transformed + if ref.URI != "" { + var rawPath string + + switch ref.URI[0] { + case '/': + rawPath = ref.URI + case '#': + rawPath = "//*[@" + ctx.IdAttribute + "='" + ref.URI[1:] + "']" + default: + log.Printf("WARNING: Signature was not checked. Unsupported URI: " + ref.URI) + continue + } + path, err := etree.CompilePath(rawPath) + if err != nil { + return nil, err + } + root := &etree.Element{ + Tag: "root", + Child: []etree.Token{transformed}, + } + referencedEl = root.FindElementPath(path) + if referencedEl == nil { + return nil, errors.New("Error implementing etree: " + rawPath) + } + } - digestAlgorithm := ref.DigestAlgo.Algorithm + digestAlgorithm := ref.DigestAlgo.Algorithm - // Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo' - digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer) - if err != nil { - return nil, err - } + // Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo' + digest, err := ctx.digest(referencedEl, digestAlgorithm, canonicalizer) + if err != nil { + return nil, err + } - decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue) - if err != nil { - return nil, err - } + decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue) + if err != nil { + return nil, err + } - if !bytes.Equal(digest, decodedDigestValue) { - return nil, errors.New("Signature could not be verified") - } + if !bytes.Equal(digest, decodedDigestValue) { + return nil, errors.New("Signature could not be verified for '" + ref.URI + "'") + } - // Decode the 'SignatureValue' so we can compare against it - decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data) - if err != nil { - return nil, errors.New("Could not decode signature") - } + // Decode the 'SignatureValue' so we can compare against it + decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data) + if err != nil { + return nil, errors.New("Could not decode signature") + } - // Actually verify the 'SignedInfo' was signed by a trusted source - signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm - err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature) - if err != nil { - return nil, err + // Actually verify the 'SignedInfo' was signed by a trusted source + signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm + err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature) + if err != nil { + return nil, err + } } - return transformed, nil } From 31dcd2a47d1db085759f8ba325d56cc4fb7e0e75 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Sun, 14 Mar 2021 11:36:58 -0700 Subject: [PATCH 16/17] Fixing XAdES test --- sign_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign_test.go b/sign_test.go index a48155c..8c3fa14 100644 --- a/sign_test.go +++ b/sign_test.go @@ -191,7 +191,7 @@ func TestSigningContext_SignXAdES(t *testing.T) { Prefix: DefaultPrefix, Canonicalizer: MakeC14N11Canonicalizer(), } - _, err := ctx.SignXAdES(tt.args.uri, tt.args.input) + _, err := ctx.SignXAdES(tt.args.uri, "mime/fake", tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("SigningContext.SignXAdES() error = %v, wantErr %v", err, tt.wantErr) return From 9d026aca7c22b8e4912e6809d43b6c26473cb8f8 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Sat, 13 Mar 2021 19:26:57 -0800 Subject: [PATCH 17/17] Self validating --- digidogo/main.go | 2 +- sign.go | 26 +++++---- validate.go | 144 +++++++++++++++++++++++++---------------------- 3 files changed, 93 insertions(+), 79 deletions(-) diff --git a/digidogo/main.go b/digidogo/main.go index e350d9e..00622c7 100644 --- a/digidogo/main.go +++ b/digidogo/main.go @@ -49,7 +49,7 @@ func main() { panic(err) } if _, err := validCtx.Validate(signedElement); err != nil { - log.Fatal("Failed Validation ", err) + log.Fatal("Failed Validation: ", err) panic(err) } diff --git a/sign.go b/sign.go index b441225..87639cd 100644 --- a/sign.go +++ b/sign.go @@ -84,7 +84,7 @@ func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, envelo Tag: SignedInfoTag, Space: ctx.Prefix, Attr: []etree.Attr{ - {Key: "Id", Value: "S1-SignedInfo"}, + {Key: ctx.IdAttribute, Value: "S1-SignedInfo"}, }, } @@ -98,7 +98,7 @@ func (ctx *SigningContext) constructSignedInfo(digest []byte, uri string, envelo // /SignedInfo/Reference reference := ctx.createNamespacedElement(signedInfo, ReferenceTag) - reference.CreateAttr("Id", "S1-ref-1") + reference.CreateAttr(ctx.IdAttribute, "S1-ref-1") reference.CreateAttr(URIAttr, uri) // /SignedInfo/Reference/Transforms @@ -181,7 +181,7 @@ func (ctx *SigningContext) baseSig(signedInfo *etree.Element) *etree.Element { Space: ctx.Prefix, Attr: []etree.Attr{ {Space: "xmlns", Key: ctx.Prefix, Value: Namespace}, - {Key: "Id", Value: "S1"}, + {Key: ctx.IdAttribute, Value: "S1"}, }, Child: []etree.Token{ signedInfo, @@ -348,7 +348,7 @@ func (ctx *SigningContext) xadesSignedSignatureProperties(x509Cert *x509.Certifi Space: "xades", Tag: "SignedSignatureProperties", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-SignedSignatureProperties"}, + {Key: ctx.IdAttribute, Value: "S1-SignedSignatureProperties"}, }, Child: []etree.Token{ &etree.Element{ @@ -416,21 +416,21 @@ func (ctx *SigningContext) xadesUnsignedSignatureProperties(signature, ocspRespo Space: "xades", Tag: "UnsignedProperties", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-UnsignedProperties"}, + {Key: ctx.IdAttribute, Value: "S1-UnsignedProperties"}, }, Child: []etree.Token{ &etree.Element{ Space: "xades", Tag: "UnsignedSignatureProperties", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-UnsignedSignatureProperties"}, + {Key: ctx.IdAttribute, Value: "S1-UnsignedSignatureProperties"}, }, Child: []etree.Token{ &etree.Element{ Space: "xades", Tag: "SignatureTimeStamp", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-ts-0"}, + {Key: ctx.IdAttribute, Value: "S1-ts-0"}, }, Child: []etree.Token{ &etree.Element{ @@ -538,13 +538,17 @@ func (ctx *SigningContext) SignXAdES(uri string, mimetype string, input []byte) Space: "xades", Tag: "SignedProperties", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-SignedProperties"}, + {Key: ctx.IdAttribute, Value: "S1-SignedProperties"}, }, Child: []etree.Token{ sigProp, sigObjProp, }, } - sigDigest, err := ctx.digest(sigProps) + canon, err := MakeC14N10CommentCanonicalizer().Canonicalize(sigProps) + if err != nil { + return nil, err + } + sigDigest, err := ctx.hash(canon) if err != nil { return nil, err } @@ -553,7 +557,7 @@ func (ctx *SigningContext) SignXAdES(uri string, mimetype string, input []byte) Space: "ds", Tag: "Reference", Attr: []etree.Attr{ - {Key: "Id", Value: "S1-ref-SignedProperties"}, + {Key: ctx.IdAttribute, Value: "S1-ref-SignedProperties"}, {Key: "Type", Value: "http://uri.etsi.org/01903#SignedProperties"}, {Key: "URI", Value: "#S1-SignedProperties"}, }, @@ -584,7 +588,7 @@ func (ctx *SigningContext) SignXAdES(uri string, mimetype string, input []byte) Tag: "QualifyingProperties", Attr: []etree.Attr{ {Space: "xmlns", Key: "xades", Value: "http://uri.etsi.org/01903/v1.3.2#"}, - {Key: "Id", Value: "S1-QualifyingProperties"}, + {Key: ctx.IdAttribute, Value: "S1-QualifyingProperties"}, {Key: "Target", Value: "#S1"}, }, Child: []etree.Token{ diff --git a/validate.go b/validate.go index e82b22a..24cb16d 100644 --- a/validate.go +++ b/validate.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "errors" "fmt" + "log" "regexp" "github.com/beevik/etree" @@ -230,85 +231,94 @@ func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicaliz } func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) { - refID := "" - if sig.Object != nil { - refID = sig.Object.ID + if el == nil { + el = sig.UnderlyingElement().Copy() } else { - idAttr := el.SelectAttr(ctx.IdAttribute) - if idAttr == nil || idAttr.Value == "" { - return nil, errors.New("Missing ID attribute") - } - refID = idAttr.Value + el = el.Copy() } - 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:] == refID { - ref = &_ref - } + root := &etree.Element{ + Tag: "root", + Child: []etree.Token{el}, } + // Iterate through all references 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") + for _, ref := range sig.SignedInfo.References { + var canonicalizer Canonicalizer + var err error + + if ref.URI != "" { + var rawPath string + + switch ref.URI[0] { + case '/': + rawPath = ref.URI + case '#': + rawPath = "//*[@" + ctx.IdAttribute + "='" + ref.URI[1:] + "']" + default: + log.Printf("WARNING: Signature not checked. Unsupported URI: " + ref.URI) + continue + } + path, err := etree.CompilePath(rawPath) + if err != nil { + return nil, err + } + + transformed = root.FindElementPath(path) + if transformed == nil { + return nil, errors.New("Error implementing etree: " + rawPath) + } + transformed, canonicalizer, err = ctx.transform(transformed, sig, &ref) + if err != nil { + return nil, err + } + } else { + transformed, canonicalizer, err = ctx.transform(el, sig, &ref) + if err != nil { + return nil, err + } } - } else { - // Perform all transformations listed in the 'SignedInfo' - // Basically, this means removing the 'SignedInfo' - transformed, canonicalizer, err = ctx.transform(el, sig, ref) + + digestAlgorithm := ref.DigestAlgo.Algorithm + + // Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo' + digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer) if err != nil { return nil, err } - } - - digestAlgorithm := ref.DigestAlgo.Algorithm - - // Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo' - digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer) - if err != nil { - return nil, err - } - decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue) - if err != nil { - return nil, err - } + decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue) + if err != nil { + return nil, err + } - if !bytes.Equal(digest, decodedDigestValue) { - return nil, errors.New("Signature could not be verified") - } + if !bytes.Equal(digest, decodedDigestValue) { + xml, _ := canonicalizer.Canonicalize(transformed) + return nil, errors.New( + "Signature could not be verified for " + + ref.URI + + ". Digest expected " + + base64.StdEncoding.EncodeToString(decodedDigestValue) + + ". Digest found " + + base64.StdEncoding.EncodeToString(digest) + + ". Transformed: " + + string(xml), + ) + } - // Decode the 'SignatureValue' so we can compare against it - decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data) - if err != nil { - return nil, errors.New("Could not decode signature") - } + // Decode the 'SignatureValue' so we can compare against it + decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data) + if err != nil { + return nil, errors.New("Could not decode signature") + } - // Actually verify the 'SignedInfo' was signed by a trusted source - signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm - err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature) - if err != nil { - return nil, err + // Actually verify the 'SignedInfo' was signed by a trusted source + signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm + err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature) + if err != nil { + return nil, err + } } return transformed, nil @@ -428,7 +438,7 @@ func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signatu // 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 { + if ref.URI == "" || ref.URI[1:] == objectID || ref.URI[0] == '#' { sig = _sig return etreeutils.ErrTraversalHalted } @@ -439,7 +449,7 @@ func (ctx *ValidationContext) findSignature(root *etree.Element) (*types.Signatu // 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 { + if ref.URI == "" || ref.URI[1:] == idAttr.Value || ref.URI[0] == '#' { sig = _sig return etreeutils.ErrTraversalHalted }