package verify import ( "bytes" "crypto/x509" "encoding/asn1" "fmt" "io" "io/ioutil" "os" "time" "bitbucket.org/digitorus/pdf" "bitbucket.org/digitorus/pdfsign/revocation" "github.com/digitorus/pkcs7" "github.com/digitorus/timestamp" "log" "golang.org/x/crypto/ocsp" ) type Response struct { Error string DocumentInfo string Signers []Signer } type Signer struct { Name string Reason string Location string ContactInfo string ValidSignature bool TrustedIssuer bool RevokedCertificate bool Certificates []Certificate TimeStamp *timestamp.Timestamp } type Certificate struct { Certificate *x509.Certificate VerifyError string OCSPResponse *ocsp.Response OCSPEmbedded bool CRLRevoked time.Time CRLEmbedded bool } func Verify(file *os.File) (apiResp *Response, err error) { defer func() { if r := recover(); r != nil { apiResp = nil err = fmt.Errorf("Failed to verify file (%v)", r) } }() apiResp = &Response{} finfo, _ := file.Stat() size := finfo.Size() file.Seek(0, 0) rdr, err := pdf.NewReader(file, size) if err != nil { return nil, fmt.Errorf("Failed to open file: %v", err) } // AcroForm will contain a SigFlags value if the form contains a digital signature t := rdr.Trailer().Key("Root").Key("AcroForm").Key("SigFlags") if t.IsNull() { return nil, fmt.Errorf("No digital signature in document") } // Walk over the cross references in the document for _, x := range rdr.Xref() { // Get the xref object Value v := rdr.Resolve(x.Ptr(), x.Ptr()) // We must have a Filter Adobe.PPKLite if v.Key("Filter").Name() != "Adobe.PPKLite" { continue } signer := Signer{ Name: v.Key("Name").Text(), Reason: v.Key("Reason").Text(), Location: v.Key("Location").Text(), ContactInfo: v.Key("ContactInfo").Text(), } // (Required) The signature value. When ByteRange is present, the // value shall be a hexadecimal string (see 7.3.4.3, “Hexadecimal // Strings”) representing the value of the byte range digest. // For public-key signatures, Contents should be either a DER-encoded // PKCS#1 binary data object or a DER-encoded PKCS#7 binary data object. // Space for the Contents value must be allocated before the message // digest is computed. (See 7.3.4, “String Objects“) p7, err := pkcs7.Parse([]byte(v.Key("Contents").RawString())) if err != nil { //fmt.Println(err) continue } // An array of pairs of integers (starting byte offset, length in // bytes) that shall describe the exact byte range for the digest // calculation. Multiple discontiguous byte ranges shall be used to // describe a digest that does not include the signature value (the // Contents entry) itself. for i := 0; i < v.Key("ByteRange").Len(); i++ { // As the byte range comes in pairs, we increment one extra i++ // Read the byte range from the raw file and add it to the contents. // This content will be hashed with the corresponding algorithm to // verify the signature. content, err := ioutil.ReadAll(io.NewSectionReader(file, v.Key("ByteRange").Index(i-1).Int64(), v.Key("ByteRange").Index(i).Int64())) if err != nil { apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err) } p7.Content = append(p7.Content, content...) } // Signer certificate // http://www.alvestrand.no/objectid/1.2.840.113549.1.9.html // http://www.alvestrand.no/objectid/1.2.840.113583.1.1.8.html var isn []byte for _, s := range p7.Signers { isn = s.IssuerAndSerialNumber.IssuerName.FullBytes //for _, a := range s.AuthenticatedAttributes { //fmt.Printf("A: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, a.Type) //} // Timestamp // http://www.alvestrand.no/objectid/1.2.840.113549.1.9.16.2.14.html // Timestamp // 1.2.840.113549.1.9.16.2.14 - RFC 3161 id-aa-timeStampToken for _, attr := range s.UnauthenticatedAttributes { //fmt.Printf("U: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, attr.Type) if attr.Type.Equal(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}) { //fmt.Println("Found timestamp") signer.TimeStamp, err = timestamp.Parse(attr.Value.Bytes) if err != nil { apiResp.Error = fmt.Sprintln("Failed to parse timestamp", err) } break } } } // Directory of certificates, including OCSP //var ica *x509.Certificate certPool := x509.NewCertPool() for _, cert := range p7.Certificates { certPool.AddCert(cert) if bytes.Equal(isn, cert.RawSubject) { //ica = cert } } // Verify the digital signature of the pdf file. err = p7.VerifyWithChain(certPool) if err != nil { err = p7.Verify() if err == nil { signer.ValidSignature = true signer.TrustedIssuer = false } log.Println("Invalid sig") apiResp.Error = fmt.Sprintln("Failed to verify signature:", err) } else { log.Println("Valid sig") signer.ValidSignature = true signer.TrustedIssuer = true } // PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8) var revInfo revocation.InfoArchival p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo) // Parse OCSP response var ocspStatus = make(map[string]*ocsp.Response) for _, o := range revInfo.OCSP { resp, err := ocsp.ParseResponse(o.FullBytes, nil) if err != nil { apiResp.Error = fmt.Sprintln("Failed to parse or verify OCSP response", err) ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = nil } else { ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = resp } } // Build certificate chains and verify revocation status for _, cert := range p7.Certificates { var c Certificate c.Certificate = cert chain, err := cert.Verify(x509.VerifyOptions{ Intermediates: certPool, CurrentTime: cert.NotBefore, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, }) if err != nil { c.VerifyError = err.Error() } if resp, ok := ocspStatus[fmt.Sprintf("%x", cert.SerialNumber)]; ok { c.OCSPResponse = resp c.OCSPEmbedded = true if resp.Status != ocsp.Good { signer.RevokedCertificate = true } if len(chain) > 1 && len(chain[0]) > 1 { issuer := chain[0][1] if resp.Certificate != nil { err = resp.Certificate.CheckSignatureFrom(issuer) if err != nil { apiResp.Error = fmt.Sprintln("OCSP signing cerificate not from certificate issuer:", err) } } else { // CA Signed response err = resp.CheckSignatureFrom(issuer) if err != nil { apiResp.Error = fmt.Sprintln("Failed to verify OCSP response signature:", err) } } } } else { // Check OCSP status for certificate out of band } // Add certificate to result signer.Certificates = append(signer.Certificates, c) } // Certificate revocation lists when included in this document for _, crl := range p7.CRLs { //var crlissuer *pkix.Name //crlissuerdr.FillFromRDNSequence(&crl.TBSCertList.Issuer) if len(crl.TBSCertList.RevokedCertificates) > 0 { } //apiResp.Error = fmt.Sprintf("CRL %v , with %d entries\n", crl.TBSCertList.Issuer, len(crl.TBSCertList.RevokedCertificates)) // TODO(vanbroup): Check revocation via CRL // signer.RevokedCertificate = true } // Parse CRL file for _, c := range revInfo.CRL { crl, err := x509.ParseCRL(c.FullBytes) if err != nil { apiResp.Error = fmt.Sprintln("Failed to parse or verify embedded CRL") } if len(crl.TBSCertList.RevokedCertificates) > 0 { } //var crlissuer *pkix.Name //crlissuerdr.FillFromRDNSequence(&crl.TBSCertList.Issuer) //apiResp.Error = fmt.Sprintf("CRL %v , with %d entries\n", crl.TBSCertList.Issuer, len(crl.TBSCertList.RevokedCertificates)) // TODO(vanbroup): Check revocation via CRL // signer.RevokedCertificate = true } // If SubFilter is adbe.pkcs7.detached or adbe.pkcs7.sha1, this entry // shall not be used, and the certificate chain shall be put in the PKCS#7 // envelope in Contents. //v.Key("Cert").Text() apiResp.Signers = append(apiResp.Signers, signer) } if apiResp == nil { err = fmt.Errorf("Document looks to have a signature but got no results") } return } func walk(t pdf.Value, pad int) { for _, k := range t.Keys() { v := t.Key(k) if v.Kind() == pdf.Array || v.Kind() == pdf.Dict { pad++ walk(v, pad) } } }