Update current master with working signing code
This commit is contained in:
19
certificate.pem
Normal file
19
certificate.pem
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDBDCCAm2gAwIBAgIJAP6vkkLP72OOMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYD
|
||||||
|
VQQGEwJOTDEVMBMGA1UECAwMWnVpZC1Ib2xsYW5kMRIwEAYDVQQHDAlSb3R0ZXJk
|
||||||
|
YW0xEjAQBgNVBAoMCVVuaWNvZGVyczELMAkGA1UECwwCSVQxGjAYBgNVBAMMEUpl
|
||||||
|
cm9lbiBCb2JiZWxkaWprMSIwIAYJKoZIhvcNAQkBFhNqZXJvZW5AdW5pY29kZXJz
|
||||||
|
Lm5sMCAXDTE3MDcwNjE5MzYwOVoYDzMwMTYxMTA2MTkzNjA5WjCBmTELMAkGA1UE
|
||||||
|
BhMCTkwxFTATBgNVBAgMDFp1aWQtSG9sbGFuZDESMBAGA1UEBwwJUm90dGVyZGFt
|
||||||
|
MRIwEAYDVQQKDAlVbmljb2RlcnMxCzAJBgNVBAsMAklUMRowGAYDVQQDDBFKZXJv
|
||||||
|
ZW4gQm9iYmVsZGlqazEiMCAGCSqGSIb3DQEJARYTamVyb2VuQHVuaWNvZGVycy5u
|
||||||
|
bDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArpfaVUltYdOSISuc8V5vAy6b
|
||||||
|
jpqYuxsS5I6jpL1nMKms9IB5+uk+Glo2O/tb+W/R8zxQ3xrQ6JWZ4ZSsBhKNVink
|
||||||
|
Su3+kdAQJfHn3NLJzx0QGceo0TF2RvVGo5c91zxuA8rchdNz1QxrD6QesGKyfsXn
|
||||||
|
F+oELezafT346PbeqikCAwEAAaNQME4wHQYDVR0OBBYEFKA68BB0iwhY2RIRFIYs
|
||||||
|
gmq0l6y7MB8GA1UdIwQYMBaAFKA68BB0iwhY2RIRFIYsgmq0l6y7MAwGA1UdEwQF
|
||||||
|
MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAZ75HjcE/d/nclPTQbCN9qvUyuU76ml4O
|
||||||
|
jDN8T+loOsUKmI4VVsNLzF6DXq8sg4EP7s8kEEzM7qhoijw09OUhVniBYN3SzJYX
|
||||||
|
l8AiThPGqcIm1TrkqPULYQBu/FnMoL6SP7kAULcsUvEmn1rPcG9ESQ4sK/ceJhFZ
|
||||||
|
zk9o3rVC0PU=
|
||||||
|
-----END CERTIFICATE-----
|
145
p11sign.go
Normal file
145
p11sign.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
|
"bitbucket.org/digitorus/pdfsign/sign"
|
||||||
|
"bitbucket.org/digitorus/pdfsign/verify"
|
||||||
|
"bitbucket.org/digitorus/pkcs11"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
log.Fatal("Usage: sign input.pdf output.pdf pkcs11-password [chain.crt] OR verify input.pdf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
method := flag.Arg(0)
|
||||||
|
if method != "sign" && method != "verify" {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
input := flag.Arg(1)
|
||||||
|
if len(input) == 0 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "verify" {
|
||||||
|
input_file, err := os.Open(input)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer input_file.Close()
|
||||||
|
|
||||||
|
resp, err := verify.Verify(input_file)
|
||||||
|
log.Println(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "sign" {
|
||||||
|
if len(flag.Args()) < 4 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
output := flag.Arg(2)
|
||||||
|
if len(output) == 0 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkcs11 key
|
||||||
|
lib, err := pkcs11.FindLib("/lib64/libeTPkcs11.so")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Library
|
||||||
|
ctx := pkcs11.New(lib)
|
||||||
|
if ctx == nil {
|
||||||
|
log.Fatal("Failed to load library")
|
||||||
|
}
|
||||||
|
err = ctx.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// login
|
||||||
|
session, err := pkcs11.CreateSession(ctx, 0, flag.Arg(3), false)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// select the first certificate
|
||||||
|
cert, ckaId, err := pkcs11.GetCert(ctx, session, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// private key
|
||||||
|
pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate_chains := make([][]*x509.Certificate, 0)
|
||||||
|
|
||||||
|
if flag.Arg(4) != "" {
|
||||||
|
certificate_pool := x509.NewCertPool()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain_data, err := ioutil.ReadFile(flag.Arg(4))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate_pool.AppendCertsFromPEM(chain_data)
|
||||||
|
certificate_chains, err = cert.Verify(x509.VerifyOptions{
|
||||||
|
Intermediates: certificate_pool,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Obtain TSA from certificate or CLI
|
||||||
|
err = sign.SignFile(input, output, sign.SignData{
|
||||||
|
Signature: sign.SignDataSignature{
|
||||||
|
Info: sign.SignDataSignatureInfo{
|
||||||
|
Name: "Jeroen Bobbeldijk",
|
||||||
|
Location: "Rotterdam",
|
||||||
|
Reason: "Test",
|
||||||
|
ContactInfo: "Geen",
|
||||||
|
Date: time.Now().Local(),
|
||||||
|
},
|
||||||
|
CertType: 2,
|
||||||
|
Approval: false,
|
||||||
|
},
|
||||||
|
Signer: pkey,
|
||||||
|
Certificate: cert,
|
||||||
|
CertificateChains: certificate_chains,
|
||||||
|
TSA: sign.TSA{
|
||||||
|
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23",
|
||||||
|
},
|
||||||
|
RevocationData: revocation.InfoArchival{},
|
||||||
|
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
log.Println("Signed PDF written to " + output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
revocation/revocation.go
Normal file
55
revocation/revocation.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package revocation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InfoArchival is the pkcs7 container containing the revocation information for
|
||||||
|
// all embedded certificates.
|
||||||
|
//
|
||||||
|
// Currently the internal structure is exposed but I don't like to expose the
|
||||||
|
// asn1.RawValue objects. We can probably make them private and expose the
|
||||||
|
// information with functions.
|
||||||
|
type InfoArchival struct {
|
||||||
|
CRL CRL `asn1:"tag:0,optional,explicit"`
|
||||||
|
OCSP OCSP `asn1:"tag:1,optional,explicit"`
|
||||||
|
Other Other `asn1:"tag:2,optional,explicit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCRL is used to embed an CRL to revocation.InfoArchival object. You directly
|
||||||
|
// pass the bytes of a downloaded CRL to this function.
|
||||||
|
func (r *InfoArchival) AddCRL(b []byte) error {
|
||||||
|
r.CRL = append(r.CRL, asn1.RawValue{FullBytes: b})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOCSP is used to embed the raw bytes of an OCSP response.
|
||||||
|
func (r *InfoArchival) AddOCSP(b []byte) error {
|
||||||
|
r.OCSP = append(r.OCSP, asn1.RawValue{FullBytes: b})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRevoked checks if there is a status inclded for the certificate and returns
|
||||||
|
// true if the certificate is marked as revoked.
|
||||||
|
//
|
||||||
|
// TODO: We should report if there is no CRL or OCSP response embeded for this certificate
|
||||||
|
// TODO: Information about the revocation (time, reason, etc) must be extractable
|
||||||
|
func (r *InfoArchival) IsRevoked(c *x509.Certificate) bool {
|
||||||
|
// check the crl and ocsp to see if this certificate is revoked
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRL contains the raw bytes of a pkix.CertificateList and can be parsed with
|
||||||
|
// x509.PParseCRL.
|
||||||
|
type CRL []asn1.RawValue
|
||||||
|
|
||||||
|
// OCSP contains the raw bytes of an OCSP response and can be parsed with
|
||||||
|
// x/crypto/ocsp.ParseResponse
|
||||||
|
type OCSP []asn1.RawValue
|
||||||
|
|
||||||
|
// ANS.1 Object OtherRevInfo
|
||||||
|
type Other struct {
|
||||||
|
Type asn1.ObjectIdentifier
|
||||||
|
Value []byte
|
||||||
|
}
|
144
sign.go
Normal file
144
sign.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
|
"bitbucket.org/digitorus/pdfsign/sign"
|
||||||
|
"bitbucket.org/digitorus/pdfsign/verify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
log.Fatal("Usage: sign input.pdf output.pdf certificate.crt private_key.key [chain.crt] OR verify input.pdf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
method := flag.Arg(0)
|
||||||
|
if method != "sign" && method != "verify" {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
input := flag.Arg(1)
|
||||||
|
if len(input) == 0 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "verify" {
|
||||||
|
input_file, err := os.Open(input)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer input_file.Close()
|
||||||
|
|
||||||
|
resp, err := verify.Verify(input_file)
|
||||||
|
log.Println(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "sign" {
|
||||||
|
if len(flag.Args()) < 5 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
output := flag.Arg(2)
|
||||||
|
if len(output) == 0 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate_data, err := ioutil.ReadFile(flag.Arg(3))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
certificate_data_block, _ := pem.Decode(certificate_data)
|
||||||
|
if certificate_data_block == nil {
|
||||||
|
log.Fatal(errors.New("failed to parse PEM block containing the certificate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(certificate_data_block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key_data, err := ioutil.ReadFile(flag.Arg(4))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
key_data_block, _ := pem.Decode(key_data)
|
||||||
|
if key_data_block == nil {
|
||||||
|
log.Fatal(errors.New("failed to parse PEM block containing the private key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate_chains := make([][]*x509.Certificate, 0)
|
||||||
|
|
||||||
|
if flag.Arg(5) != "" {
|
||||||
|
certificate_pool := x509.NewCertPool()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain_data, err := ioutil.ReadFile(flag.Arg(5))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate_pool.AppendCertsFromPEM(chain_data)
|
||||||
|
certificate_chains, err = cert.Verify(x509.VerifyOptions{
|
||||||
|
Intermediates: certificate_pool,
|
||||||
|
CurrentTime: cert.NotBefore,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sign.SignFile(input, output, sign.SignData{
|
||||||
|
Signature: sign.SignDataSignature{
|
||||||
|
Info: sign.SignDataSignatureInfo{
|
||||||
|
Name: "Jeroen Bobbeldijk",
|
||||||
|
Location: "Rotterdam",
|
||||||
|
Reason: "Test",
|
||||||
|
ContactInfo: "Geen",
|
||||||
|
Date: time.Now().Local(),
|
||||||
|
},
|
||||||
|
CertType: 2,
|
||||||
|
Approval: false,
|
||||||
|
},
|
||||||
|
Signer: pkey,
|
||||||
|
Certificate: cert,
|
||||||
|
CertificateChains: certificate_chains,
|
||||||
|
TSA: sign.TSA{
|
||||||
|
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23",
|
||||||
|
},
|
||||||
|
RevocationData: revocation.InfoArchival{},
|
||||||
|
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
log.Println("Signed PDF written to " + output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
sign/helpers.go
Normal file
125
sign/helpers.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bitbucket.org/digitorus/pdf"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findFirstPage(parent pdf.Value) (pdf.Value, error) {
|
||||||
|
value_type := parent.Key("Type").String()
|
||||||
|
if value_type == "/Pages" {
|
||||||
|
recurse_parent, recurse_err := findFirstPage(parent.Key("Kids").Index(0))
|
||||||
|
return recurse_parent, recurse_err
|
||||||
|
}
|
||||||
|
|
||||||
|
if value_type == "/Page" {
|
||||||
|
return parent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent, errors.New("Could not find first page.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func pdfString(text string) string {
|
||||||
|
text = strings.Replace(text, "\\", "\\\\", -1)
|
||||||
|
text = strings.Replace(text, ")", "\\)", -1)
|
||||||
|
text = strings.Replace(text, "(", "\\(", -1)
|
||||||
|
text = strings.Replace(text, "\r", "\\r", -1)
|
||||||
|
|
||||||
|
text = "(" + text + ")"
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func pdfDateTime(date time.Time) string {
|
||||||
|
// Calculate timezone offset from GMT.
|
||||||
|
_, original_offset := date.Zone()
|
||||||
|
offset := original_offset
|
||||||
|
if offset < 0 {
|
||||||
|
offset = (offset - offset) - offset
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_duration := time.Duration(offset) * time.Second
|
||||||
|
offset_hours := int(math.Floor(offset_duration.Hours()))
|
||||||
|
offset_minutes := int(math.Floor(offset_duration.Minutes()))
|
||||||
|
offset_minutes = offset_minutes - (offset_hours * 60)
|
||||||
|
|
||||||
|
dateString := "D:" + date.Format("20060102150405")
|
||||||
|
|
||||||
|
// Do some special formatting as the PDF timezone format isn't supported by Go.
|
||||||
|
if original_offset < 0 {
|
||||||
|
dateString += "-"
|
||||||
|
} else {
|
||||||
|
dateString += "+"
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_hours_formatted := fmt.Sprintf("%d", offset_hours)
|
||||||
|
offset_minutes_formatted := fmt.Sprintf("%d", offset_minutes)
|
||||||
|
dateString += leftPad(offset_hours_formatted, "0", 2-len(offset_hours_formatted)) + "'" + leftPad(offset_minutes_formatted, "0", 2-len(offset_minutes_formatted)) + "'"
|
||||||
|
|
||||||
|
return pdfString(dateString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func leftPad(s string, padStr string, pLen int) string {
|
||||||
|
return strings.Repeat(padStr, pLen) + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePartFromSourceFileToTargetFile(input_file *os.File, output_file *os.File, offset int64, length int64) error {
|
||||||
|
input_file.Seek(offset, 0)
|
||||||
|
|
||||||
|
// Create a small buffer for proper IO handling.
|
||||||
|
max_chunk_length := int64(1024)
|
||||||
|
|
||||||
|
// If the target length is smaller than our chunk size, use that as chunk size.
|
||||||
|
if length < max_chunk_length {
|
||||||
|
max_chunk_length = length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track read/written bytes so we know when we're done.
|
||||||
|
read_bytes := int64(0)
|
||||||
|
|
||||||
|
// Create a buffer for the chunks.
|
||||||
|
buf := make([]byte, max_chunk_length)
|
||||||
|
for {
|
||||||
|
// Read the chunk from the input file.
|
||||||
|
n, err := input_file.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got to the end of the file, break.
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing was read, break.
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the chunk to the output file.
|
||||||
|
if _, err := output_file.Write(buf[:n]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
read_bytes += int64(n)
|
||||||
|
|
||||||
|
// If we read enough bytes, break.
|
||||||
|
if read_bytes >= length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our next chunk will be too big, make a smaller buffer.
|
||||||
|
// If we won't do this, we might end up with more data than we want.
|
||||||
|
if length-read_bytes < max_chunk_length {
|
||||||
|
buf = make([]byte, length-read_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
44
sign/pdfbyterange.go
Normal file
44
sign/pdfbyterange.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) updateByteRange() error {
|
||||||
|
// Get current filesize. Easier than what should be the current size.
|
||||||
|
// @todo: find out of this is safe.
|
||||||
|
output_file_stat, _ := context.OutputFile.Stat()
|
||||||
|
|
||||||
|
output_file_size := output_file_stat.Size()
|
||||||
|
|
||||||
|
// Calculate ByteRange values to replace them.
|
||||||
|
context.ByteRangeValues = make([]int64, 4)
|
||||||
|
|
||||||
|
// Signature ByteRange part 1 start byte is always byte 0.
|
||||||
|
context.ByteRangeValues[0] = int64(0)
|
||||||
|
|
||||||
|
// Signature ByteRange part 1 length always stops at the actual signature start byte.
|
||||||
|
context.ByteRangeValues[1] = context.SignatureContentsStartByte - 1
|
||||||
|
|
||||||
|
// Signature ByteRange part 2 start byte directly starts after the actual signature.
|
||||||
|
context.ByteRangeValues[2] = context.ByteRangeValues[1] + 1 + int64(context.SignatureMaxLength) + 1
|
||||||
|
|
||||||
|
// Signature ByteRange part 2 length is everything else of the file.
|
||||||
|
context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2]
|
||||||
|
|
||||||
|
new_byte_range := fmt.Sprintf("/ByteRange[%d %d %d %d]", context.ByteRangeValues[0], context.ByteRangeValues[1], context.ByteRangeValues[2], context.ByteRangeValues[3])
|
||||||
|
|
||||||
|
// Make sure our ByteRange string didn't shrink in length.
|
||||||
|
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range))
|
||||||
|
|
||||||
|
// Seek to ByteRange position in file.
|
||||||
|
context.OutputFile.Seek(context.ByteRangeStartByte, 0)
|
||||||
|
|
||||||
|
// Write new ByteRange.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(new_byte_range)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
59
sign/pdfcatalog.go
Normal file
59
sign/pdfcatalog.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) createCatalog() (catalog string, err error) {
|
||||||
|
catalog = strconv.Itoa(int(context.CatalogData.ObjectId)) + " 0 obj\n"
|
||||||
|
catalog += "<< /Type /Catalog"
|
||||||
|
catalog += " /Version /" + context.PDFReader.PDFVersion
|
||||||
|
|
||||||
|
root := context.PDFReader.Trailer().Key("Root")
|
||||||
|
root_keys := root.Keys()
|
||||||
|
found_pages := false
|
||||||
|
for _, key := range root_keys {
|
||||||
|
if key == "Pages" {
|
||||||
|
found_pages = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_pages {
|
||||||
|
return "", errors.New("Didn't find pages in PDF trailer Root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootPtr := root.GetPtr()
|
||||||
|
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
|
||||||
|
|
||||||
|
pages := root.Key("Pages").GetPtr()
|
||||||
|
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
|
||||||
|
catalog += " /AcroForm <<"
|
||||||
|
catalog += " /Fields [" + strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R]"
|
||||||
|
|
||||||
|
if !context.SignData.Signature.Approval {
|
||||||
|
catalog += " /NeedAppearances false"
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.SignData.Signature.CertType > 0 {
|
||||||
|
catalog += " /SigFlags 3"
|
||||||
|
} else {
|
||||||
|
catalog += " /SigFlags 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog += " >>"
|
||||||
|
|
||||||
|
if !context.SignData.Signature.Approval {
|
||||||
|
if context.SignData.Signature.CertType > 0 {
|
||||||
|
catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"
|
||||||
|
} else {
|
||||||
|
catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog += " >>"
|
||||||
|
catalog += "\nendobj\n"
|
||||||
|
|
||||||
|
return catalog, nil
|
||||||
|
}
|
25
sign/pdfinfo.go
Normal file
25
sign/pdfinfo.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) createInfo() (info string, err error) {
|
||||||
|
original_info := context.PDFReader.Trailer().Key("Info")
|
||||||
|
info = strconv.Itoa(int(context.InfoData.ObjectId)) + " 0 obj\n"
|
||||||
|
info += "<<"
|
||||||
|
|
||||||
|
info_keys := original_info.Keys()
|
||||||
|
for _, key := range info_keys {
|
||||||
|
info += "/" + key
|
||||||
|
if key == "ModDate" {
|
||||||
|
info += pdfDateTime(context.SignData.Signature.Info.Date)
|
||||||
|
} else {
|
||||||
|
info += pdfString(original_info.Key(key).RawString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info += ">>"
|
||||||
|
info += "\nendobj\n"
|
||||||
|
return info, nil
|
||||||
|
}
|
272
sign/pdfsignature.go
Normal file
272
sign/pdfsignature.go
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"crypto/x509"
|
||||||
|
"github.com/digitorus/pkcs7"
|
||||||
|
"github.com/digitorus/timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pkiStatusInfo struct {
|
||||||
|
Status int
|
||||||
|
StatusString string `asn1:"optional"`
|
||||||
|
FailInfo int `asn1:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.4.2. Response Format
|
||||||
|
type TSAResponse struct {
|
||||||
|
Status pkiStatusInfo
|
||||||
|
TimeStampToken asn1.RawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
var signatureByteRangePlaceholder = "/ByteRange[0 ********** ********** **********]"
|
||||||
|
|
||||||
|
func (context *SignContext) createSignaturePlaceholder() (signature string, byte_range_start_byte int64, signature_contents_start_byte int64) {
|
||||||
|
signature = strconv.Itoa(int(context.SignData.ObjectId)) + " 0 obj\n"
|
||||||
|
signature += "<< /Type /Sig"
|
||||||
|
signature += " /Filter /Adobe.PPKLite"
|
||||||
|
signature += " /SubFilter /adbe.pkcs7.detached"
|
||||||
|
|
||||||
|
byte_range_start_byte = int64(len(signature)) + 1
|
||||||
|
|
||||||
|
// Create a placeholder for the byte range string, we will replace it later.
|
||||||
|
signature += " " + signatureByteRangePlaceholder
|
||||||
|
|
||||||
|
signature_contents_start_byte = int64(len(signature)) + 11
|
||||||
|
|
||||||
|
// Create a placeholder for the actual signature content, we wil replace it later.
|
||||||
|
signature += " /Contents<" + strings.Repeat("0", int(context.SignatureMaxLength)) + ">"
|
||||||
|
|
||||||
|
if !context.SignData.Signature.Approval {
|
||||||
|
signature += " /Reference [" // array of signature reference dictionaries
|
||||||
|
signature += " << /Type /SigRef"
|
||||||
|
if context.SignData.Signature.CertType > 0 {
|
||||||
|
signature += " /TransformMethod /DocMDP"
|
||||||
|
signature += " /TransformParams <<"
|
||||||
|
signature += " /Type /TransformParams"
|
||||||
|
signature += " /P " + strconv.Itoa(int(context.SignData.Signature.CertType))
|
||||||
|
signature += " /V /1.2"
|
||||||
|
} else {
|
||||||
|
signature += " /TransformMethod /UR3"
|
||||||
|
signature += " /TransformParams <<"
|
||||||
|
signature += " /Type /TransformParams"
|
||||||
|
signature += " /V /2.2"
|
||||||
|
}
|
||||||
|
|
||||||
|
signature += " >>" // close TransformParams
|
||||||
|
signature += " >>"
|
||||||
|
signature += " ]" // end of reference
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.SignData.Signature.Info.Name != "" {
|
||||||
|
signature += " /Name " + pdfString(context.SignData.Signature.Info.Name)
|
||||||
|
}
|
||||||
|
if context.SignData.Signature.Info.Location != "" {
|
||||||
|
signature += " /Location " + pdfString(context.SignData.Signature.Info.Location)
|
||||||
|
}
|
||||||
|
if context.SignData.Signature.Info.Reason != "" {
|
||||||
|
signature += " /Reason " + pdfString(context.SignData.Signature.Info.Reason)
|
||||||
|
}
|
||||||
|
if context.SignData.Signature.Info.ContactInfo != "" {
|
||||||
|
signature += " /ContactInfo " + pdfString(context.SignData.Signature.Info.ContactInfo)
|
||||||
|
}
|
||||||
|
signature += " /M " + pdfDateTime(context.SignData.Signature.Info.Date)
|
||||||
|
signature += " >>"
|
||||||
|
signature += "\nendobj\n"
|
||||||
|
|
||||||
|
return signature, byte_range_start_byte, signature_contents_start_byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) fetchRevocationData() error {
|
||||||
|
if context.SignData.RevocationFunction != nil {
|
||||||
|
if context.SignData.CertificateChains != nil && (len(context.SignData.CertificateChains) > 0) {
|
||||||
|
certificate_chain := context.SignData.CertificateChains[0]
|
||||||
|
if certificate_chain != nil && (len(certificate_chain) > 0) {
|
||||||
|
for i, certificate := range certificate_chain {
|
||||||
|
if i < len(certificate_chain)-1 {
|
||||||
|
err := context.SignData.RevocationFunction(certificate, certificate_chain[i+1], &context.SignData.RevocationData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := context.SignData.RevocationFunction(certificate, nil, &context.SignData.RevocationData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate space needed for signature.
|
||||||
|
for _, crl := range context.SignData.RevocationData.CRL {
|
||||||
|
context.SignatureMaxLength += uint32(len(crl.FullBytes) * 2)
|
||||||
|
}
|
||||||
|
for _, ocsp := range context.SignData.RevocationData.OCSP {
|
||||||
|
context.SignatureMaxLength += uint32(len(ocsp.FullBytes) * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) createSignature() ([]byte, error) {
|
||||||
|
|
||||||
|
// Sadly we can't efficiently sign a file, we need to read all the bytes we want to sign.
|
||||||
|
context.OutputFile.Seek(0, 0)
|
||||||
|
sign_buf := bytes.NewBuffer(nil)
|
||||||
|
io.Copy(sign_buf, context.OutputFile)
|
||||||
|
file_content := sign_buf.Bytes()
|
||||||
|
|
||||||
|
// Collect the parts to sign.
|
||||||
|
sign_content := make([]byte, 0)
|
||||||
|
sign_content = append(sign_content, file_content[context.ByteRangeValues[0]:(context.ByteRangeValues[0]+context.ByteRangeValues[1])]...)
|
||||||
|
sign_content = append(sign_content, file_content[context.ByteRangeValues[2]:(context.ByteRangeValues[2]+context.ByteRangeValues[3])]...)
|
||||||
|
|
||||||
|
// Initialize pkcs7 signer.
|
||||||
|
signed_data, err := pkcs7.NewSignedData(sign_content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer_config := pkcs7.SignerInfoConfig{
|
||||||
|
ExtraSignedAttributes: []pkcs7.Attribute{
|
||||||
|
{
|
||||||
|
Type: asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8},
|
||||||
|
Value: context.SignData.RevocationData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the first certificate chain without our own certificate.
|
||||||
|
var certificate_chain []*x509.Certificate
|
||||||
|
if len(context.SignData.CertificateChains) > 0 && len(context.SignData.CertificateChains[0]) > 1 {
|
||||||
|
certificate_chain = context.SignData.CertificateChains[0][1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the signer and sign the data.
|
||||||
|
if err := signed_data.AddSignerChain(context.SignData.Certificate, context.SignData.Signer, certificate_chain, signer_config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PDF needs a detached signature, meaning the content isn't included.
|
||||||
|
signed_data.Detach()
|
||||||
|
|
||||||
|
if context.SignData.TSA.URL != "" {
|
||||||
|
signature_data := signed_data.GetSignedData()
|
||||||
|
|
||||||
|
timestamp_response, err := context.GetTSA(signature_data.SignerInfos[0].EncryptedDigest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rest []byte
|
||||||
|
var resp TSAResponse
|
||||||
|
if rest, err = asn1.Unmarshal(timestamp_response, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
return nil, errors.New("trailing data in Time-Stamp response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Status.Status > 0 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("%s: %s", timestamp.FailureInfo(resp.Status.FailInfo).String(), resp.Status.StatusString))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pkcs7.Parse(resp.TimeStampToken.FullBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.TimeStampToken.Bytes) == 0 {
|
||||||
|
return nil, errors.New("no pkcs7 data in Time-Stamp response")
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp_attribute := pkcs7.Attribute{
|
||||||
|
Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14},
|
||||||
|
Value: resp.TimeStampToken,
|
||||||
|
}
|
||||||
|
signature_data.SignerInfos[0].SetUnauthenticatedAttributes([]pkcs7.Attribute{timestamp_attribute})
|
||||||
|
}
|
||||||
|
|
||||||
|
return signed_data.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) GetTSA(sign_content []byte) (timestamp_response []byte, err error) {
|
||||||
|
sign_reader := bytes.NewReader(sign_content)
|
||||||
|
ts_request, err := timestamp.CreateRequest(sign_reader, ×tamp.RequestOptions{
|
||||||
|
Certificates: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts_request_reader := bytes.NewReader(ts_request)
|
||||||
|
req, err := http.NewRequest("POST", context.SignData.TSA.URL, ts_request_reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/timestamp-query")
|
||||||
|
req.Header.Add("Content-Transfer-Encoding", "binary")
|
||||||
|
|
||||||
|
if context.SignData.TSA.Username != "" && context.SignData.TSA.Password != "" {
|
||||||
|
req.SetBasicAuth(context.SignData.TSA.Username, context.SignData.TSA.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
code := 0
|
||||||
|
|
||||||
|
if resp != nil {
|
||||||
|
code = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || (code < 200 || code > 299) {
|
||||||
|
if err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
err = errors.New("Non success response (" + strconv.Itoa(code) + "): " + string(body))
|
||||||
|
} else {
|
||||||
|
err = errors.New("Non success response (" + strconv.Itoa(code) + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
timestamp_response_body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return timestamp_response_body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) replaceSignature() error {
|
||||||
|
signature, err := context.createSignature()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, hex.EncodedLen(len(signature)))
|
||||||
|
hex.Encode(dst, signature)
|
||||||
|
|
||||||
|
if uint32(len(dst)) > context.SignatureMaxLength {
|
||||||
|
return errors.New("Signature is too big to fit in reserved space.")
|
||||||
|
}
|
||||||
|
|
||||||
|
context.OutputFile.WriteAt(dst, context.ByteRangeValues[0]+context.ByteRangeValues[1]+1)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
51
sign/pdftrailer.go
Normal file
51
sign/pdftrailer.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) writeTrailer() error {
|
||||||
|
trailer_length := context.PDFReader.XrefInformation.IncludingTrailerEndPos - context.PDFReader.XrefInformation.EndPos
|
||||||
|
|
||||||
|
// Read the trailer so we can replace the size.
|
||||||
|
context.InputFile.Seek(context.PDFReader.XrefInformation.EndPos+1, 0)
|
||||||
|
trailer_buf := make([]byte, trailer_length)
|
||||||
|
if _, err := context.InputFile.Read(trailer_buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
root_string := "Root " + context.CatalogData.RootString
|
||||||
|
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
|
||||||
|
|
||||||
|
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
||||||
|
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+4, 10)
|
||||||
|
|
||||||
|
info := context.PDFReader.Trailer().Key("Info")
|
||||||
|
infoPtr := info.GetPtr()
|
||||||
|
|
||||||
|
info_string := "Info " + strconv.Itoa(int(infoPtr.GetID())) + " " + strconv.Itoa(int(infoPtr.GetGen())) + " R"
|
||||||
|
new_info := "Info " + strconv.FormatInt(int64(context.InfoData.ObjectId), 10) + " 0 R"
|
||||||
|
|
||||||
|
trailer_string := string(trailer_buf)
|
||||||
|
trailer_string = strings.Replace(trailer_string, root_string, new_root, -1)
|
||||||
|
trailer_string = strings.Replace(trailer_string, size_string, new_size, -1)
|
||||||
|
trailer_string = strings.Replace(trailer_string, info_string, new_info, -1)
|
||||||
|
|
||||||
|
// Write the new trailer.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(trailer_string)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the new xref start position.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(strconv.FormatInt(context.NewXrefStart, 10) + "\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write PDF ending.
|
||||||
|
if _, err := context.OutputFile.Write([]byte("%%EOF")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
50
sign/pdfvisualsignature.go
Normal file
50
sign/pdfvisualsignature.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) createVisualSignature() (visual_signature string, err error) {
|
||||||
|
visual_signature = strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 obj\n"
|
||||||
|
visual_signature += "<< /Type /Annot"
|
||||||
|
visual_signature += " /Subtype /Widget"
|
||||||
|
visual_signature += " /Rect [0 0 0 0]"
|
||||||
|
|
||||||
|
root := context.PDFReader.Trailer().Key("Root")
|
||||||
|
root_keys := root.Keys()
|
||||||
|
found_pages := false
|
||||||
|
for _, key := range root_keys {
|
||||||
|
if key == "Pages" {
|
||||||
|
found_pages = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_pages {
|
||||||
|
return "", errors.New("Didn't find pages in PDF trailer Root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootPtr := root.GetPtr()
|
||||||
|
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
|
||||||
|
|
||||||
|
first_page, err := findFirstPage(root.Key("Pages"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
first_page_ptr := first_page.GetPtr()
|
||||||
|
|
||||||
|
visual_signature += " /P " + strconv.Itoa(int(first_page_ptr.GetID())) + " " + strconv.Itoa(int(first_page_ptr.GetGen())) + " R"
|
||||||
|
|
||||||
|
visual_signature += " /F 132"
|
||||||
|
visual_signature += " /FT /Sig"
|
||||||
|
visual_signature += " /T " + pdfString("Signature")
|
||||||
|
visual_signature += " /Ff 0"
|
||||||
|
visual_signature += " /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R"
|
||||||
|
|
||||||
|
visual_signature += " >>"
|
||||||
|
visual_signature += "\nendobj\n"
|
||||||
|
|
||||||
|
return visual_signature, nil
|
||||||
|
}
|
73
sign/pdfxref.go
Normal file
73
sign/pdfxref.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) writeXref() error {
|
||||||
|
|
||||||
|
// @todo: support stream xref.
|
||||||
|
|
||||||
|
if context.PDFReader.XrefInformation.Type == "table" {
|
||||||
|
if err := context.writeXrefTable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("Unkwn xref type: " + context.PDFReader.XrefInformation.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) writeXrefTable() error {
|
||||||
|
xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
||||||
|
new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+4, 10)
|
||||||
|
|
||||||
|
if _, err := context.OutputFile.Write([]byte(new_xref_size)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the old xref table to the output pdf.
|
||||||
|
if err := writePartFromSourceFileToTargetFile(context.InputFile, context.OutputFile, context.PDFReader.XrefInformation.StartPos+int64(len(xref_size)), context.PDFReader.XrefInformation.Length-int64(len(xref_size))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new catalog xref line.
|
||||||
|
visual_signature_object_start_position := strconv.FormatInt(context.Filesize, 10)
|
||||||
|
visual_signature_xref_line := leftPad(visual_signature_object_start_position, "0", 10-len(visual_signature_object_start_position)) + " 00000 n \n"
|
||||||
|
|
||||||
|
// Write the new catalog xref line.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(visual_signature_xref_line)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new catalog xref line.
|
||||||
|
catalog_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length, 10)
|
||||||
|
catalog_xref_line := leftPad(catalog_object_start_position, "0", 10-len(catalog_object_start_position)) + " 00000 n \n"
|
||||||
|
|
||||||
|
// Write the new catalog xref line.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(catalog_xref_line)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new signature xref line.
|
||||||
|
info_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length, 10)
|
||||||
|
info_xref_line := leftPad(info_object_start_position, "0", 10-len(info_object_start_position)) + " 00000 n \n"
|
||||||
|
|
||||||
|
// Write the new signature xref line.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(info_xref_line)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new signature xref line.
|
||||||
|
signature_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length+context.InfoData.Length, 10)
|
||||||
|
signature_xref_line := leftPad(signature_object_start_position, "0", 10-len(signature_object_start_position)) + " 00000 n \n"
|
||||||
|
|
||||||
|
// Write the new signature xref line.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(signature_xref_line)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
91
sign/revocation.go
Normal file
91
sign/revocation.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func embedOCSPRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||||
|
req, err := ocsp.CreateRequest(cert, issuer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspUrl := fmt.Sprintf("%s/%s", strings.TrimRight(cert.OCSPServer[0], "/"),
|
||||||
|
base64.StdEncoding.EncodeToString(req))
|
||||||
|
|
||||||
|
resp, err := http.Get(ocspUrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we got a valid OCSP response
|
||||||
|
_, err = ocsp.ParseResponseForCert(body, cert, issuer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.AddOCSP(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedCRLRevocationStatus requires an issuer as it needs to implement the
|
||||||
|
// the interface, a nil argment might be given if the issuer is not known.
|
||||||
|
func embedCRLRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||||
|
resp, err := http.Get(cert.CRLDistributionPoints[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: verify crl and certificate before embedding
|
||||||
|
return i.AddCRL(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultEmbedRevocationStatusFunction(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
|
||||||
|
// For each certificate a revoction status needs to be included, this can be done
|
||||||
|
// by embedding a CRL or OCSP response. In most cases an OCSP response is smaller
|
||||||
|
// to embed in the document but and empty CRL (often seen of dediced high volume
|
||||||
|
// hirachies) can be smaller.
|
||||||
|
//
|
||||||
|
// There have been some reports that the usage of a CRL would result in a better
|
||||||
|
// compatibilty.
|
||||||
|
//
|
||||||
|
// TODO: Find and embed link about compatibilty
|
||||||
|
// TODO: Implement revocation status caching (required for higher volume signing)
|
||||||
|
|
||||||
|
// using an OCSP server
|
||||||
|
// OCSP requires issuer certificate.
|
||||||
|
if issuer != nil && len(cert.OCSPServer) > 0 {
|
||||||
|
err := embedOCSPRevocationStatus(cert, issuer, i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// using a crl
|
||||||
|
if len(cert.CRLDistributionPoints) > 0 {
|
||||||
|
err := embedCRLRevocationStatus(cert, issuer, i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
119
sign/revocation_test.go
Normal file
119
sign/revocation_test.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
|
)
|
||||||
|
|
||||||
|
const certPem = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGKDCCBRCgAwIBAgIMW8J4m7huCPO5f+wbMA0GCSqGSIb3DQEBCwUAMEsxCzAJ
|
||||||
|
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhH
|
||||||
|
bG9iYWxTaWduIENBIDIgZm9yIEFBVEwwHhcNMTcwNzA2MDk0MDUyWhcNMjAwNzA2
|
||||||
|
MDk0MDUyWjCBpzELMAkGA1UEBhMCR0IxDTALBgNVBAgTBEtlbnQxEjAQBgNVBAcT
|
||||||
|
CU1haWRzdG9uZTEfMB0GA1UEChMWR01PIEdsb2JhbFNpZ24gTGltaXRlZDEfMB0G
|
||||||
|
A1UEAxMWUGF1bCBWYW4gQnJvdXdlcnNoYXZlbjEzMDEGCSqGSIb3DQEJARYkcGF1
|
||||||
|
bC52YW5icm91d2Vyc2hhdmVuQGdsb2JhbHNpZ24uY29tMIIBIjANBgkqhkiG9w0B
|
||||||
|
AQEFAAOCAQ8AMIIBCgKCAQEAr5jbAIZDjkWngxlwJqneE9VEDTvmMIGwvgy71g5j
|
||||||
|
k+igHxB6tTfaqGD87oIm2wcrlZHpPJG9n2Rh9FhFmvx8ZXiceNI9Ks5Ho5iYNFUk
|
||||||
|
y2JuVfFPxtp6amqpLzM5HZUePgu1Gdy1Zn1PUajii7paFPuhdemcA9DdTAQ1GsDv
|
||||||
|
C9MZ2D5sKM0hLCRePCzJ3TeQmHefFrC0XQ7u2i7LDD990URiFz7WNq2tSDJwBe/6
|
||||||
|
1tMewpekmtE5X43PqzgcyGBsDJqAKcthsLQnhqrIryuwE2bEhP/FxQrHkT+f/OkK
|
||||||
|
vwAjB9gYgJRNoMFUwXLW789JsOkbCqepoWsg1PnWrAbAMQIDAQABo4ICrTCCAqkw
|
||||||
|
DgYDVR0PAQH/BAQDAgeAMIGIBggrBgEFBQcBAQR8MHowQQYIKwYBBQUHMAKGNWh0
|
||||||
|
dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYWF0bDJzaGEyZzIu
|
||||||
|
Y3J0MDUGCCsGAQUFBzABhilodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nh
|
||||||
|
YXRsMnNoYTJnMjCB2wYDVR0gBIHTMIHQMIHNBgsrBgEEAaAyASgeAjCBvTCBhgYI
|
||||||
|
KwYBBQUHAgIwegx4VGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQgaW4g
|
||||||
|
YWNjb3JkYW5jZSB3aXRoIHRoZSBHbG9iYWxTaWduIENQUyBsb2NhdGVkIGF0IGh0
|
||||||
|
dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDIGCCsGAQUFBwIB
|
||||||
|
FiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRME
|
||||||
|
AjAAMD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20v
|
||||||
|
Z3MvZ3NhYXRsMnNoYTJnMi5jcmwwLwYDVR0RBCgwJoEkcGF1bC52YW5icm91d2Vy
|
||||||
|
c2hhdmVuQGdsb2JhbHNpZ24uY29tMFwGCiqGSIb3LwEBCQEETjBMAgEBhkRodHRw
|
||||||
|
Oi8vYWF0bC10aW1lc3RhbXAuZ2xvYmFsc2lnbi5jb20vdHNhL2FvaGZld2F0MjM4
|
||||||
|
OTUzNWZuYXNnbmxnNW0yMwEBADATBgNVHSUEDDAKBggrBgEFBQcDBDAdBgNVHQ4E
|
||||||
|
FgQUtimjVds00OgBZ747tqkKbZJeCCowHwYDVR0jBBgwFoAUxRNOzofGiRsj6EDj
|
||||||
|
dTKbA3A67+8wDQYJKoZIhvcNAQELBQADggEBACBUFVwUKVpOWt1eLf7lKKPfVhEL
|
||||||
|
9QrkAkV/UZPMsDwBDIJhphqjCqfbJVTgybm79gUCJiwbarCYOHRgFAdNTPEvEcT0
|
||||||
|
+XwR6WZcDdfQAtaHfO6X9ExgJv93txoFVcpYLY1hR3o6QdP4VQSDhRTv3bM1j/WC
|
||||||
|
mcCoiIQz28Y8L+8rRx5J7JAgYpupoU/8sCpidBMhYAGF5p8Z8p0LbqvZndRHaVqp
|
||||||
|
yXQ0kYj1n45it5FXsKECWZKTx0v4IBySJY3RGpF+5cpPUYulJfINBg7nj7aQG/Uv
|
||||||
|
qtyxnVAG3W4pTHWd/0Gyc3lrgRtyZy+b9DaxHZ/N6HHNgnRHB4PUkektpX4=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
const issuerPem = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEpjCCA46gAwIBAgIORea3r9ymcb22XRTz2sAwDQYJKoZIhvcNAQELBQAwVzEL
|
||||||
|
MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExLTArBgNVBAMT
|
||||||
|
JEdsb2JhbFNpZ24gQ0EgZm9yIEFBVEwgLSBTSEEyNTYgLSBHMjAeFw0xNDEyMTAw
|
||||||
|
MDAwMDBaFw0yNDEyMTAwMDAwMDBaMEsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBH
|
||||||
|
bG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhHbG9iYWxTaWduIENBIDIgZm9yIEFB
|
||||||
|
VEwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJS4vhNfaSmXtX7bWy
|
||||||
|
hVhBGqisbqgnX9/K5psHMrkwm10pnicCSmvb6nvgMAPbRPyfpGHj5ArrDli6rCDR
|
||||||
|
CLR1tjied/6AxQCCPgyvDyEwDWxzytVHkCldzEHjHmc1kL0zI7aQfNrD25xAUnHa
|
||||||
|
X2jBm71filgduyQBfuLJLlL/NGh46X6eI9xpqBmqwBFa6wHPisnwkAB22CQB/OY3
|
||||||
|
mnlhLCJmrEL9fGPRDQnc6w849ws3nkKISkEPmTyfUAJUkEgCxOjfiwoNSFjZ99lA
|
||||||
|
K+ujj90dqYU3mJ7dTEHA2+dFrdvVsoyA4JZBu7utxe2rJgpifO3B/L+f4Cat12kA
|
||||||
|
2IqtAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
|
||||||
|
/wIBADAdBgNVHQ4EFgQUxRNOzofGiRsj6EDjdTKbA3A67+8wHwYDVR0jBBgwFoAU
|
||||||
|
YMLxUj6tjBPc28oO+grmKiyZS9gwgYYGCCsGAQUFBwEBBHoweDA0BggrBgEFBQcw
|
||||||
|
AYYoaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzYWF0bHNoYTJnMjBABggr
|
||||||
|
BgEFBQcwAoY0aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nh
|
||||||
|
YXRsc2hhMmcyLmNydDA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLmdsb2Jh
|
||||||
|
bHNpZ24uY29tL2dzL2dzYWF0bHNoYTJnMi5jcmwwRwYDVR0gBEAwPjA8BgRVHSAA
|
||||||
|
MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
|
||||||
|
aXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAkH9vI0bJPzbtf5ikdFb5Kc4L9/IIZ
|
||||||
|
GPeVbS3UPl8x4WPzmZHwGvSeG53DDcJ9weGHO4PORBgBT5KDZEt7CKXd4vt83xw8
|
||||||
|
P1kYvvjv4E+8R8VD3hP48zkygHMR3JtBdMPkbNbE10TCb4XmQPpkwGIVhaD7ojtS
|
||||||
|
+4mPjVts6ZZzbnHI42CbYwdaOf2W8IUu0b1w4T7T5YPfi8rSKwQxIKibFG1mSsOC
|
||||||
|
vG9tDxJVJUNdiWTPoHGn+n+3qeJvPRgHhicZ+ivOqqmLQSNgtp1WdQ+uJmUqU7EY
|
||||||
|
htN+laG7bS/8xGTPothL9Abgd/9L3X0KKGUDCdcpzRuy20CI7E4uygD8
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
func TestEmbedRevocationStatus(t *testing.T) {
|
||||||
|
var ia revocation.InfoArchival
|
||||||
|
|
||||||
|
err := DefaultEmbedRevocationStatusFunction(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ia.OCSP) != 1 {
|
||||||
|
t.Errorf("Expected one OCSP status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmbedOCSPRevocationStatus(t *testing.T) {
|
||||||
|
var ia revocation.InfoArchival
|
||||||
|
|
||||||
|
err := embedOCSPRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ia.OCSP) != 1 {
|
||||||
|
t.Errorf("Expected one OCSP status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmbedCRLRevocationStatus(t *testing.T) {
|
||||||
|
var ia revocation.InfoArchival
|
||||||
|
|
||||||
|
err := embedCRLRevocationStatus(pemToCert(certPem), nil, &ia)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ia.CRL) != 1 {
|
||||||
|
t.Errorf("Expected one CRL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pemToCert(p string) *x509.Certificate {
|
||||||
|
block, _ := pem.Decode([]byte(p))
|
||||||
|
cert, _ := x509.ParseCertificate(block.Bytes)
|
||||||
|
|
||||||
|
return cert
|
||||||
|
}
|
238
sign/sign.go
Normal file
238
sign/sign.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bitbucket.org/digitorus/pdf"
|
||||||
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CatalogData struct {
|
||||||
|
ObjectId uint32
|
||||||
|
Length int64
|
||||||
|
RootString string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TSA struct {
|
||||||
|
URL string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RevocationFunction func(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error
|
||||||
|
|
||||||
|
type SignData struct {
|
||||||
|
ObjectId uint32
|
||||||
|
Signature SignDataSignature
|
||||||
|
Signer crypto.Signer
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
CertificateChains [][]*x509.Certificate
|
||||||
|
TSA TSA
|
||||||
|
RevocationData revocation.InfoArchival
|
||||||
|
RevocationFunction RevocationFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisualSignData struct {
|
||||||
|
ObjectId uint32
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type InfoData struct {
|
||||||
|
ObjectId uint32
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignDataSignature struct {
|
||||||
|
Approval bool
|
||||||
|
CertType uint32
|
||||||
|
Info SignDataSignatureInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignDataSignatureInfo struct {
|
||||||
|
Name string
|
||||||
|
Location string
|
||||||
|
Reason string
|
||||||
|
ContactInfo string
|
||||||
|
Date time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignContext struct {
|
||||||
|
Filesize int64
|
||||||
|
InputFile *os.File
|
||||||
|
OutputFile *os.File
|
||||||
|
SignData SignData
|
||||||
|
CatalogData CatalogData
|
||||||
|
VisualSignData VisualSignData
|
||||||
|
InfoData InfoData
|
||||||
|
PDFReader *pdf.Reader
|
||||||
|
NewXrefStart int64
|
||||||
|
ByteRangeStartByte int64
|
||||||
|
SignatureContentsStartByte int64
|
||||||
|
ByteRangeValues []int64
|
||||||
|
SignatureMaxLength uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignFile(input string, output string, sign_data SignData) error {
|
||||||
|
input_file, err := os.Open(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer input_file.Close()
|
||||||
|
|
||||||
|
output_file, err := os.Create(output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer output_file.Close()
|
||||||
|
|
||||||
|
finfo, err := input_file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size := finfo.Size()
|
||||||
|
|
||||||
|
rdr, err := pdf.NewReader(input_file, size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
||||||
|
|
||||||
|
// We do size+1 because we insert a newline.
|
||||||
|
context := SignContext{
|
||||||
|
Filesize: size + 1,
|
||||||
|
PDFReader: rdr,
|
||||||
|
InputFile: input_file,
|
||||||
|
OutputFile: output_file,
|
||||||
|
VisualSignData: VisualSignData{
|
||||||
|
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
||||||
|
},
|
||||||
|
CatalogData: CatalogData{
|
||||||
|
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
||||||
|
},
|
||||||
|
InfoData: InfoData{
|
||||||
|
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
|
||||||
|
},
|
||||||
|
SignData: sign_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = context.SignPDF()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) SignPDF() error {
|
||||||
|
// Copy old file into new file.
|
||||||
|
if _, err := io.Copy(context.OutputFile, context.InputFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := context.OutputFile.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File always needs an empty line after %%EOF.
|
||||||
|
if _, err := context.OutputFile.Write([]byte("\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base size for signature.
|
||||||
|
context.SignatureMaxLength = 100000
|
||||||
|
|
||||||
|
// Add estimated size for TSA.
|
||||||
|
// We can't kow actual size of TSA until after signing.
|
||||||
|
if context.SignData.TSA.URL != "" {
|
||||||
|
context.SignatureMaxLength += 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch revocation data before adding signature placeholder.
|
||||||
|
// Revocation data can be quite large and we need to create enough space in the placeholder.
|
||||||
|
context.fetchRevocationData()
|
||||||
|
|
||||||
|
visual_signature, err := context.createVisualSignature()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.VisualSignData.Length = int64(len(visual_signature))
|
||||||
|
|
||||||
|
// Write the new catalog object.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(visual_signature)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := context.createCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CatalogData.Length = int64(len(catalog))
|
||||||
|
|
||||||
|
// Write the new catalog object.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(catalog)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the signature object
|
||||||
|
signature_object, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder()
|
||||||
|
|
||||||
|
info, err := context.createInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.InfoData.Length = int64(len(info))
|
||||||
|
|
||||||
|
// Write the new catalog object.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appended_bytes := context.Filesize + int64(len(catalog)) + int64(len(visual_signature)) + int64(len(info))
|
||||||
|
|
||||||
|
// Positions are relative to old start position of xref table.
|
||||||
|
byte_range_start_byte += appended_bytes
|
||||||
|
signature_contents_start_byte += appended_bytes
|
||||||
|
|
||||||
|
context.ByteRangeStartByte = byte_range_start_byte
|
||||||
|
context.SignatureContentsStartByte = signature_contents_start_byte
|
||||||
|
|
||||||
|
// Write the new signature object.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(signature_object)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the new start position of the xref table.
|
||||||
|
context.NewXrefStart = appended_bytes + int64(len(signature_object))
|
||||||
|
|
||||||
|
if err := context.writeXref(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := context.writeTrailer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := context.updateByteRange(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := context.replaceSignature(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = context.OutputFile.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,306 +1,313 @@
|
|||||||
// http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf
|
package verify
|
||||||
|
|
||||||
package signatures
|
import (
|
||||||
|
"bytes"
|
||||||
import (
|
"crypto/x509"
|
||||||
"bytes"
|
"encoding/asn1"
|
||||||
"crypto/x509"
|
"fmt"
|
||||||
"encoding/asn1"
|
"io"
|
||||||
"fmt"
|
"io/ioutil"
|
||||||
"io"
|
"os"
|
||||||
"os"
|
"time"
|
||||||
"io/ioutil"
|
|
||||||
// "log"
|
"bitbucket.org/digitorus/pdf"
|
||||||
// "strings"
|
"bitbucket.org/digitorus/pdfsign/revocation"
|
||||||
"time"
|
"github.com/digitorus/pkcs7"
|
||||||
|
"github.com/digitorus/timestamp"
|
||||||
"bitbucket.org/digitorus/pdf"
|
"log"
|
||||||
"github.com/digitorus/timestamp"
|
"golang.org/x/crypto/ocsp"
|
||||||
"go.mozilla.org/pkcs7"
|
"crypto"
|
||||||
"golang.org/x/crypto/ocsp"
|
)
|
||||||
)
|
|
||||||
|
type Response struct {
|
||||||
type RevocationInfoArchival struct {
|
Error string
|
||||||
CRL RevCRL `asn1:"tag:0,optional,explicit"`
|
|
||||||
OCSP RevOCSP `asn1:"tag:1,optional,explicit"`
|
DocumentInfo string
|
||||||
OtherRevInfo OtherRevInfo `asn1:"tag:2,optional,explicit"`
|
Signers []Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
type RevCRL []asn1.RawValue
|
type Signer struct {
|
||||||
type RevOCSP []asn1.RawValue
|
Name string
|
||||||
|
Reason string
|
||||||
type OtherRevInfo struct {
|
Location string
|
||||||
Type asn1.ObjectIdentifier
|
ContactInfo string
|
||||||
Value []byte
|
ValidSignature bool
|
||||||
}
|
TrustedIssuer bool
|
||||||
|
RevokedCertificate bool
|
||||||
type Response struct {
|
Certificates []Certificate
|
||||||
Error string
|
TimeStamp *timestamp.Timestamp
|
||||||
|
}
|
||||||
DocumentInfo string
|
|
||||||
Signers []Signer
|
type Certificate struct {
|
||||||
}
|
Certificate *x509.Certificate
|
||||||
|
VerifyError string
|
||||||
type Signer struct {
|
OCSPResponse *ocsp.Response
|
||||||
Name string
|
OCSPEmbedded bool
|
||||||
Reason string
|
CRLRevoked time.Time
|
||||||
Location string
|
CRLEmbedded bool
|
||||||
ContactInfo string
|
}
|
||||||
ValidSignature bool
|
|
||||||
TrustedIssuer bool
|
func Verify(file *os.File) (apiResp *Response, err error) {
|
||||||
RevokedCertificate bool
|
defer func() {
|
||||||
Certificates []Certificate
|
if r := recover(); r != nil {
|
||||||
TimeStamp *timestamp.Timestamp
|
apiResp = nil
|
||||||
}
|
err = fmt.Errorf("Failed to verify file (%v)", r)
|
||||||
|
}
|
||||||
type Certificate struct {
|
}()
|
||||||
Certificate *x509.Certificate
|
apiResp = &Response{}
|
||||||
VerifyError string
|
|
||||||
OCSPResponse *ocsp.Response
|
finfo, _ := file.Stat()
|
||||||
OCSPEmbedded bool
|
size := finfo.Size()
|
||||||
CRLRevoked time.Time
|
|
||||||
CRLEmbedded bool
|
file.Seek(0, 0)
|
||||||
}
|
|
||||||
|
rdr, err := pdf.NewReader(file, size)
|
||||||
func Verify(file *os.File) (apiResp *Response, err error) {
|
if err != nil {
|
||||||
defer func() {
|
return nil, fmt.Errorf("Failed to open file: %v", err)
|
||||||
if r := recover(); r != nil {
|
}
|
||||||
apiResp = nil
|
|
||||||
err = fmt.Errorf("Failed to verify file (%v)", r)
|
// 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() {
|
||||||
apiResp = &Response{}
|
return nil, fmt.Errorf("No digital signature in document")
|
||||||
|
}
|
||||||
finfo, _ := file.Stat()
|
|
||||||
size := finfo.Size()
|
// Walk over the cross references in the document
|
||||||
|
for _, x := range rdr.Xref() {
|
||||||
rdr, err := pdf.NewReader(file, size)
|
// Get the xref object Value
|
||||||
if err != nil {
|
v := rdr.Resolve(x.Ptr(), x.Ptr())
|
||||||
return nil, fmt.Errorf("Failed to open file")
|
|
||||||
}
|
// We must have a Filter Adobe.PPKLite
|
||||||
|
if v.Key("Filter").Name() != "Adobe.PPKLite" {
|
||||||
// AcroForm will contain a SigFlags value if the form contains a digital signature
|
continue
|
||||||
t := rdr.Trailer().Key("Root").Key("AcroForm").Key("SigFlags")
|
}
|
||||||
if t.IsNull() {
|
|
||||||
return nil, fmt.Errorf("No digital signature in document")
|
signer := Signer{
|
||||||
}
|
Name: v.Key("Name").Text(),
|
||||||
|
Reason: v.Key("Reason").Text(),
|
||||||
// Walk over the cross references in the document
|
Location: v.Key("Location").Text(),
|
||||||
for _, x := range rdr.Xref() {
|
ContactInfo: v.Key("ContactInfo").Text(),
|
||||||
// Get the xref object Value
|
}
|
||||||
v := rdr.Resolve(x.Ptr(), x.Ptr())
|
|
||||||
|
// (Required) The signature value. When ByteRange is present, the
|
||||||
// We must have a Filter Adobe.PPKLite
|
// value shall be a hexadecimal string (see 7.3.4.3, “Hexadecimal
|
||||||
if v.Key("Filter").Name() != "Adobe.PPKLite" {
|
// Stringsâ€) representing the value of the byte range digest.
|
||||||
continue
|
// 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
|
||||||
signer := Signer{
|
// digest is computed. (See 7.3.4, “String Objects“)
|
||||||
Name: v.Key("Name").Text(),
|
p7, err := pkcs7.Parse([]byte(v.Key("Contents").RawString()))
|
||||||
Reason: v.Key("Reason").Text(),
|
if err != nil {
|
||||||
Location: v.Key("Location").Text(),
|
//fmt.Println(err)
|
||||||
ContactInfo: v.Key("ContactInfo").Text(),
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// (Required) The signature value. When ByteRange is present, the
|
// An array of pairs of integers (starting byte offset, length in
|
||||||
// value shall be a hexadecimal string (see 7.3.4.3, “Hexadecimal
|
// bytes) that shall describe the exact byte range for the digest
|
||||||
// Strings”) representing the value of the byte range digest.
|
// calculation. Multiple discontiguous byte ranges shall be used to
|
||||||
// For public-key signatures, Contents should be either a DER-encoded
|
// describe a digest that does not include the signature value (the
|
||||||
// PKCS#1 binary data object or a DER-encoded PKCS#7 binary data object.
|
// Contents entry) itself.
|
||||||
// Space for the Contents value must be allocated before the message
|
for i := 0; i < v.Key("ByteRange").Len(); i++ {
|
||||||
// digest is computed. (See 7.3.4, “String Objects“)
|
// As the byte range comes in pairs, we increment one extra
|
||||||
p7, err := pkcs7.Parse([]byte(v.Key("Contents").RawString()))
|
i++
|
||||||
if err != nil {
|
|
||||||
//fmt.Println(err)
|
// Read the byte range from the raw file and add it to the contents.
|
||||||
continue
|
// This content will be hashed with the corresponding algorithm to
|
||||||
}
|
// verify the signature.
|
||||||
|
|
||||||
// An array of pairs of integers (starting byte offset, length in
|
content, err := ioutil.ReadAll(io.NewSectionReader(file, v.Key("ByteRange").Index(i-1).Int64(), v.Key("ByteRange").Index(i).Int64()))
|
||||||
// bytes) that shall describe the exact byte range for the digest
|
if err != nil {
|
||||||
// calculation. Multiple discontiguous byte ranges shall be used to
|
apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err)
|
||||||
// describe a digest that does not include the signature value (the
|
}
|
||||||
// Contents entry) itself.
|
|
||||||
for i := 0; i < v.Key("ByteRange").Len(); i++ {
|
p7.Content = append(p7.Content, content...)
|
||||||
// As the byte range comes in pairs, we increment one extra
|
}
|
||||||
i++
|
|
||||||
|
// Signer certificate
|
||||||
// Read the byte range from the raw file and add it to the contents.
|
// http://www.alvestrand.no/objectid/1.2.840.113549.1.9.html
|
||||||
// This content will be hashed with the corresponding algorithm to
|
// http://www.alvestrand.no/objectid/1.2.840.113583.1.1.8.html
|
||||||
// verify the signature.
|
var isn []byte
|
||||||
content, err := ioutil.ReadAll(io.NewSectionReader(file, v.Key("ByteRange").Index(i-1).Int64(), v.Key("ByteRange").Index(i).Int64()))
|
for _, s := range p7.Signers {
|
||||||
if err != nil {
|
isn = s.IssuerAndSerialNumber.IssuerName.FullBytes
|
||||||
apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err)
|
//for _, a := range s.AuthenticatedAttributes {
|
||||||
}
|
//fmt.Printf("A: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, a.Type)
|
||||||
p7.Content = append(p7.Content, content...)
|
//}
|
||||||
}
|
|
||||||
|
// Timestamp
|
||||||
// Signer certificate
|
// http://www.alvestrand.no/objectid/1.2.840.113549.1.9.16.2.14.html
|
||||||
// http://www.alvestrand.no/objectid/1.2.840.113549.1.9.html
|
// Timestamp
|
||||||
// http://www.alvestrand.no/objectid/1.2.840.113583.1.1.8.html
|
// 1.2.840.113549.1.9.16.2.14 - RFC 3161 id-aa-timeStampToken
|
||||||
var isn []byte
|
for _, attr := range s.UnauthenticatedAttributes {
|
||||||
for _, s := range p7.Signers {
|
//fmt.Printf("U: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, attr.Type)
|
||||||
isn = s.IssuerAndSerialNumber.IssuerName.FullBytes
|
|
||||||
//for _, a := range s.AuthenticatedAttributes {
|
if attr.Type.Equal(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}) {
|
||||||
//fmt.Printf("A: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, a.Type)
|
//fmt.Println("Found timestamp")
|
||||||
//}
|
|
||||||
|
signer.TimeStamp, err = timestamp.Parse(attr.Value.Bytes)
|
||||||
// Timestamp
|
if err != nil {
|
||||||
// http://www.alvestrand.no/objectid/1.2.840.113549.1.9.16.2.14.html
|
apiResp.Error = fmt.Sprintln("Failed to parse timestamp", err)
|
||||||
// Timestamp
|
}
|
||||||
// 1.2.840.113549.1.9.16.2.14 - RFC 3161 id-aa-timeStampToken
|
|
||||||
for _, attr := range s.UnauthenticatedAttributes {
|
r := bytes.NewReader(s.EncryptedDigest)
|
||||||
//fmt.Printf("U: %v, %#v\n", s.IssuerAndSerialNumber.SerialNumber, attr.Type)
|
h := crypto.SHA256.New()
|
||||||
|
b := make([]byte, 32)
|
||||||
if attr.Type.Equal(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}) {
|
for {
|
||||||
//fmt.Println("Found timestamp")
|
n, err := r.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
signer.TimeStamp, err = timestamp.Parse(attr.Value.Bytes)
|
break
|
||||||
if err != nil {
|
}
|
||||||
apiResp.Error = fmt.Sprintln("Failed to parse timestamp", err)
|
|
||||||
}
|
h.Write(b[:n])
|
||||||
|
}
|
||||||
break
|
|
||||||
}
|
if !bytes.Equal(h.Sum(nil), signer.TimeStamp.HashedMessage) {
|
||||||
}
|
apiResp.Error = fmt.Sprintln("Hash in timestamp is different from pkcs7")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directory of certificates, including OCSP
|
break
|
||||||
//var ica *x509.Certificate
|
}
|
||||||
certPool := x509.NewCertPool()
|
}
|
||||||
for _, cert := range p7.Certificates {
|
}
|
||||||
certPool.AddCert(cert)
|
|
||||||
if bytes.Equal(isn, cert.RawSubject) {
|
// Directory of certificates, including OCSP
|
||||||
//ica = cert
|
//var ica *x509.Certificate
|
||||||
}
|
certPool := x509.NewCertPool()
|
||||||
}
|
for _, cert := range p7.Certificates {
|
||||||
|
certPool.AddCert(cert)
|
||||||
// Verify the digital signature of the pdf file.
|
if bytes.Equal(isn, cert.RawSubject) {
|
||||||
err = p7.VerifyWithChain(certPool)
|
//ica = cert
|
||||||
if err != nil {
|
}
|
||||||
err = p7.Verify()
|
}
|
||||||
if err == nil {
|
|
||||||
signer.ValidSignature = true
|
// Verify the digital signature of the pdf file.
|
||||||
signer.TrustedIssuer = false
|
err = p7.VerifyWithChain(certPool)
|
||||||
}
|
if err != nil {
|
||||||
//apiResp.Error = fmt.Sprintln("Failed to verify signature:", err)
|
err = p7.Verify()
|
||||||
} else {
|
if err == nil {
|
||||||
signer.ValidSignature = true
|
signer.ValidSignature = true
|
||||||
signer.TrustedIssuer = true
|
signer.TrustedIssuer = false
|
||||||
}
|
}
|
||||||
|
log.Println("Invalid sig")
|
||||||
// PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8)
|
apiResp.Error = fmt.Sprintln("Failed to verify signature:", err)
|
||||||
var revInfo RevocationInfoArchival
|
} else {
|
||||||
p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo)
|
log.Println("Valid sig")
|
||||||
|
signer.ValidSignature = true
|
||||||
// Parse OCSP response
|
signer.TrustedIssuer = true
|
||||||
var ocspStatus = make(map[string]*ocsp.Response)
|
}
|
||||||
for _, o := range revInfo.OCSP {
|
|
||||||
resp, err := ocsp.ParseResponse(o.FullBytes, nil)
|
// PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8)
|
||||||
if err != nil {
|
var revInfo revocation.InfoArchival
|
||||||
apiResp.Error = fmt.Sprintln("Failed to parse or verify OCSP response", err)
|
p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo)
|
||||||
ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = nil
|
|
||||||
} else {
|
// Parse OCSP response
|
||||||
ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = resp
|
var ocspStatus = make(map[string]*ocsp.Response)
|
||||||
}
|
for _, o := range revInfo.OCSP {
|
||||||
}
|
resp, err := ocsp.ParseResponse(o.FullBytes, nil)
|
||||||
|
if err != nil {
|
||||||
// Build certificate chains and verify revocation status
|
apiResp.Error = fmt.Sprintln("Failed to parse or verify OCSP response", err)
|
||||||
for _, cert := range p7.Certificates {
|
ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = nil
|
||||||
var c Certificate
|
} else {
|
||||||
c.Certificate = cert
|
ocspStatus[fmt.Sprintf("%x", resp.SerialNumber)] = resp
|
||||||
|
}
|
||||||
chain, err := cert.Verify(x509.VerifyOptions{
|
}
|
||||||
Intermediates: certPool,
|
|
||||||
CurrentTime: cert.NotBefore,
|
// Build certificate chains and verify revocation status
|
||||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
for _, cert := range p7.Certificates {
|
||||||
})
|
var c Certificate
|
||||||
|
c.Certificate = cert
|
||||||
if err != nil {
|
|
||||||
c.VerifyError = err.Error()
|
chain, err := cert.Verify(x509.VerifyOptions{
|
||||||
}
|
Intermediates: certPool,
|
||||||
|
CurrentTime: cert.NotBefore,
|
||||||
if resp, ok := ocspStatus[fmt.Sprintf("%x", cert.SerialNumber)]; ok {
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
c.OCSPResponse = resp
|
})
|
||||||
c.OCSPEmbedded = true
|
|
||||||
|
if err != nil {
|
||||||
if resp.Status != ocsp.Good {
|
c.VerifyError = err.Error()
|
||||||
signer.RevokedCertificate = true
|
}
|
||||||
}
|
|
||||||
|
if resp, ok := ocspStatus[fmt.Sprintf("%x", cert.SerialNumber)]; ok {
|
||||||
if len(chain) > 1 && len(chain[0]) > 1 {
|
c.OCSPResponse = resp
|
||||||
issuer := chain[0][1]
|
c.OCSPEmbedded = true
|
||||||
if resp.Certificate != nil {
|
|
||||||
err = resp.Certificate.CheckSignatureFrom(issuer)
|
if resp.Status != ocsp.Good {
|
||||||
if err != nil {
|
signer.RevokedCertificate = true
|
||||||
apiResp.Error = fmt.Sprintln("OCSP signing cerificate not from certificate issuer:", err)
|
}
|
||||||
}
|
|
||||||
} else {
|
if len(chain) > 0 && len(chain[0]) > 1 {
|
||||||
// CA Signed response
|
issuer := chain[0][1]
|
||||||
err = resp.CheckSignatureFrom(issuer)
|
if resp.Certificate != nil {
|
||||||
if err != nil {
|
err = resp.Certificate.CheckSignatureFrom(issuer)
|
||||||
apiResp.Error = fmt.Sprintln("Failed to verify OCSP response signature:", err)
|
if err != nil {
|
||||||
}
|
apiResp.Error = fmt.Sprintln("OCSP signing cerificate not from certificate issuer:", err)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
// CA Signed response
|
||||||
// Check OCSP status for certificate out of band
|
err = resp.CheckSignatureFrom(issuer)
|
||||||
}
|
if err != nil {
|
||||||
|
apiResp.Error = fmt.Sprintln("Failed to verify OCSP response signature:", err)
|
||||||
// Add certificate to result
|
}
|
||||||
signer.Certificates = append(signer.Certificates, c)
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Certificate revocation lists when included in this document
|
// Check OCSP status for certificate out of band
|
||||||
for _, crl := range p7.CRLs {
|
}
|
||||||
//var crlissuer *pkix.Name
|
|
||||||
//crlissuerdr.FillFromRDNSequence(&crl.TBSCertList.Issuer)
|
// Add certificate to result
|
||||||
if len(crl.TBSCertList.RevokedCertificates) > 0 {
|
signer.Certificates = append(signer.Certificates, c)
|
||||||
|
}
|
||||||
}
|
|
||||||
//apiResp.Error = fmt.Sprintf("CRL %v , with %d entries\n", crl.TBSCertList.Issuer, len(crl.TBSCertList.RevokedCertificates))
|
// Certificate revocation lists when included in this document
|
||||||
// TODO(vanbroup): Check revocation via CRL
|
for _, crl := range p7.CRLs {
|
||||||
// signer.RevokedCertificate = true
|
//var crlissuer *pkix.Name
|
||||||
}
|
//crlissuerdr.FillFromRDNSequence(&crl.TBSCertList.Issuer)
|
||||||
|
if len(crl.TBSCertList.RevokedCertificates) > 0 {
|
||||||
// Parse CRL file
|
|
||||||
for _, c := range revInfo.CRL {
|
}
|
||||||
crl, err := x509.ParseCRL(c.FullBytes)
|
//apiResp.Error = fmt.Sprintf("CRL %v , with %d entries\n", crl.TBSCertList.Issuer, len(crl.TBSCertList.RevokedCertificates))
|
||||||
if err != nil {
|
// TODO(vanbroup): Check revocation via CRL
|
||||||
apiResp.Error = fmt.Sprintln("Failed to parse or verify embedded CRL")
|
// signer.RevokedCertificate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(crl.TBSCertList.RevokedCertificates) > 0 {
|
// Parse CRL file
|
||||||
|
for _, c := range revInfo.CRL {
|
||||||
}
|
crl, err := x509.ParseCRL(c.FullBytes)
|
||||||
|
if err != nil {
|
||||||
//var crlissuer *pkix.Name
|
apiResp.Error = fmt.Sprintln("Failed to parse or verify embedded CRL")
|
||||||
//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
|
if len(crl.TBSCertList.RevokedCertificates) > 0 {
|
||||||
// signer.RevokedCertificate = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If SubFilter is adbe.pkcs7.detached or adbe.pkcs7.sha1, this entry
|
//var crlissuer *pkix.Name
|
||||||
// shall not be used, and the certificate chain shall be put in the PKCS#7
|
//crlissuerdr.FillFromRDNSequence(&crl.TBSCertList.Issuer)
|
||||||
// envelope in Contents.
|
//apiResp.Error = fmt.Sprintf("CRL %v , with %d entries\n", crl.TBSCertList.Issuer, len(crl.TBSCertList.RevokedCertificates))
|
||||||
//v.Key("Cert").Text()
|
// TODO(vanbroup): Check revocation via CRL
|
||||||
|
// signer.RevokedCertificate = true
|
||||||
apiResp.Signers = append(apiResp.Signers, signer)
|
}
|
||||||
}
|
|
||||||
|
// If SubFilter is adbe.pkcs7.detached or adbe.pkcs7.sha1, this entry
|
||||||
if apiResp == nil {
|
// shall not be used, and the certificate chain shall be put in the PKCS#7
|
||||||
err = fmt.Errorf("Document looks to have a signature but got no results")
|
// envelope in Contents.
|
||||||
}
|
//v.Key("Cert").Text()
|
||||||
|
|
||||||
return
|
apiResp.Signers = append(apiResp.Signers, signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func walk(t pdf.Value, pad int) {
|
if apiResp == nil {
|
||||||
for _, k := range t.Keys() {
|
err = fmt.Errorf("Document looks to have a signature but got no results")
|
||||||
v := t.Key(k)
|
}
|
||||||
if v.Kind() == pdf.Array || v.Kind() == pdf.Dict {
|
|
||||||
pad++
|
return
|
||||||
walk(v, pad)
|
}
|
||||||
}
|
|
||||||
}
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user