unipdf/model/sighandler/sighandler_timestamp.go
Alexey Pavlyukov a69d788171
Add timestamp signature handler (#301)
* Add timestamp signature handler

* Add timestamp signature handler test

* fix PR issues

* fix PR issues

* fix PR issues

* Fix

Co-authored-by: Gunnsteinn Hall <gunnsteinn.hall@gmail.com>
2020-04-22 20:21:53 +00:00

212 lines
5.6 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package sighandler
import (
"bytes"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
"github.com/unidoc/pkcs7"
"github.com/unidoc/timestamp"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
)
// docTimeStamp DocTimeStamp signature handler.
type docTimeStamp struct {
timestampServerURL string
hashAlgorithm crypto.Hash
}
// NewDocTimeStamp creates a new DocTimeStamp signature handler.
// The timestampServerURL parameter can be empty string for the signature validation.
// The hashAlgorithm parameter can be crypto.SHA1, crypto.SHA256, crypto.SHA384, crypto.SHA512.
func NewDocTimeStamp(timestampServerURL string, hashAlgorithm crypto.Hash) (model.SignatureHandler, error) {
return &docTimeStamp{
timestampServerURL: timestampServerURL,
hashAlgorithm: hashAlgorithm,
}, nil
}
// InitSignature initialises the PdfSignature.
func (a *docTimeStamp) InitSignature(sig *model.PdfSignature) error {
handler := *a
sig.Handler = &handler
sig.Filter = core.MakeName("Adobe.PPKLite")
sig.SubFilter = core.MakeName("ETSI.RFC3161")
sig.Reference = nil
digest, err := a.NewDigest(sig)
if err != nil {
return err
}
digest.Write([]byte("calculate the Contents field size"))
return handler.Sign(sig, digest)
}
func (a *docTimeStamp) getCertificate(sig *model.PdfSignature) (*x509.Certificate, error) {
var certData []byte
switch certObj := sig.Cert.(type) {
case *core.PdfObjectString:
certData = certObj.Bytes()
case *core.PdfObjectArray:
if certObj.Len() == 0 {
return nil, errors.New("no signature certificates found")
}
for _, obj := range certObj.Elements() {
certStr, ok := core.GetString(obj)
if !ok {
return nil, fmt.Errorf("invalid certificate object type in signature certificate chain: %T", obj)
}
certData = append(certData, certStr.Bytes()...)
}
default:
return nil, fmt.Errorf("invalid signature certificate object type: %T", certObj)
}
certs, err := x509.ParseCertificates(certData)
if err != nil {
return nil, err
}
return certs[0], nil
}
// NewDigest creates a new digest.
func (a *docTimeStamp) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
return bytes.NewBuffer(nil), nil
}
type timestampInfo struct {
Version int
Policy asn1.RawValue
MessageImprint struct {
HashAlgorithm pkix.AlgorithmIdentifier
HashedMessage []byte
}
SerialNumber asn1.RawValue
GeneralizedTime time.Time
}
func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) {
switch {
case oid.Equal(pkcs7.OIDDigestAlgorithmSHA1), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA1),
oid.Equal(pkcs7.OIDDigestAlgorithmDSA), oid.Equal(pkcs7.OIDDigestAlgorithmDSASHA1),
oid.Equal(pkcs7.OIDEncryptionAlgorithmRSA):
return crypto.SHA1, nil
case oid.Equal(pkcs7.OIDDigestAlgorithmSHA256), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA256):
return crypto.SHA256, nil
case oid.Equal(pkcs7.OIDDigestAlgorithmSHA384), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA384):
return crypto.SHA384, nil
case oid.Equal(pkcs7.OIDDigestAlgorithmSHA512), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA512):
return crypto.SHA512, nil
}
return crypto.Hash(0), pkcs7.ErrUnsupportedAlgorithm
}
// Validate validates PdfSignature.
func (a *docTimeStamp) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
signed := sig.Contents.Bytes()
p7, err := pkcs7.Parse(signed)
if err != nil {
return model.SignatureValidationResult{}, err
}
if err = p7.Verify(); err != nil {
return model.SignatureValidationResult{}, err
}
var tsInfo timestampInfo
_, err = asn1.Unmarshal(p7.Content, &tsInfo)
if err != nil {
return model.SignatureValidationResult{}, err
}
hAlg, err := getHashForOID(tsInfo.MessageImprint.HashAlgorithm.Algorithm)
if err != nil {
return model.SignatureValidationResult{}, err
}
h := hAlg.New()
buffer := digest.(*bytes.Buffer)
h.Write(buffer.Bytes())
sm := h.Sum(nil)
res := model.SignatureValidationResult{
IsSigned: true,
IsVerified: bytes.Equal(sm, tsInfo.MessageImprint.HashedMessage),
GeneralizedTime: tsInfo.GeneralizedTime,
}
return res, nil
}
// Sign sets the Contents fields for the PdfSignature.
func (a *docTimeStamp) Sign(sig *model.PdfSignature, digest model.Hasher) error {
buffer := digest.(*bytes.Buffer)
h := a.hashAlgorithm.New()
if _, err := io.Copy(h, buffer); err != nil {
return err
}
s := h.Sum(nil)
r := timestamp.Request{
HashAlgorithm: a.hashAlgorithm,
HashedMessage: s,
Certificates: true,
Extensions: nil,
ExtraExtensions: nil,
}
data, err := r.Marshal()
if err != nil {
return err
}
resp, err := http.Post(a.timestampServerURL, "application/timestamp-query", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("http status code not ok (got %d)", resp.StatusCode)
}
var ci struct {
Version asn1.RawValue
Content asn1.RawValue
}
_, err = asn1.Unmarshal(body, &ci)
if err != nil {
return err
}
sig.Contents = core.MakeHexString(string(ci.Content.FullBytes))
return nil
}
// IsApplicable returns true if the signature handler is applicable for the PdfSignature.
func (a *docTimeStamp) IsApplicable(sig *model.PdfSignature) bool {
if sig == nil || sig.Filter == nil || sig.SubFilter == nil {
return false
}
return (*sig.Filter == "Adobe.PPKMS" || *sig.Filter == "Adobe.PPKLite") && *sig.SubFilter == "ETSI.RFC3161"
}