mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-24 13:48:49 +08:00
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>
This commit is contained in:
parent
6678fc040a
commit
a69d788171
3
go.mod
3
go.mod
@ -6,8 +6,9 @@ require (
|
||||
github.com/adrg/sysfont v0.1.0
|
||||
github.com/boombuler/barcode v1.0.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/gunnsth/pkcs7 v0.0.0-20181213175627-3cffc6fbfe83
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/unidoc/pkcs7 v0.0.0-20200411230602-d883fd70d1df
|
||||
github.com/unidoc/timestamp v0.0.0-20200412005513-91597fd3793a
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
|
||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
|
||||
golang.org/x/text v0.3.2
|
||||
|
4
go.sum
4
go.sum
@ -17,6 +17,10 @@ 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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/unidoc/pkcs7 v0.0.0-20200411230602-d883fd70d1df h1:1RV3lxQ6L6xGFNhngpP9iMjJPSwvH3p17JNbK9u5274=
|
||||
github.com/unidoc/pkcs7 v0.0.0-20200411230602-d883fd70d1df/go.mod h1:UEzOZUEpJfDpywVJMUT8QiugqEZC29pDq7kdIZhWCr8=
|
||||
github.com/unidoc/timestamp v0.0.0-20200412005513-91597fd3793a h1:RLtvUhe4DsUDl66m7MJ8OqBjq8jpWBXPK6/RKtqeTkc=
|
||||
github.com/unidoc/timestamp v0.0.0-20200412005513-91597fd3793a/go.mod h1:j+qMWZVpZFTvDey3zxUkSgPJZEX33tDgU/QIA0IzCUw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -7,6 +7,7 @@ package model_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -920,7 +921,8 @@ func validateFile(t *testing.T, fileName string) {
|
||||
|
||||
handler, _ := sighandler.NewAdobeX509RSASHA1(nil, nil)
|
||||
handler2, _ := sighandler.NewAdobePKCS7Detached(nil, nil)
|
||||
handlers := []model.SignatureHandler{handler, handler2}
|
||||
handler3, _ := sighandler.NewDocTimeStamp("", 0)
|
||||
handlers := []model.SignatureHandler{handler, handler2, handler3}
|
||||
|
||||
res, err := reader.ValidateSignatures(handlers)
|
||||
if err != nil {
|
||||
@ -1577,3 +1579,243 @@ func TestAppenderAttemptMultiWrite(t *testing.T) {
|
||||
t.Fatalf("Second invokation of appender.Write should yield an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppenderTimestampSign(t *testing.T) {
|
||||
f1, err := os.Open(testPdfFile1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := sighandler.NewDocTimeStamp("https://freetsa.org/tsr", crypto.SHA512)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create signature field and appearance.
|
||||
signature := model.NewPdfSignature(handler)
|
||||
signature.SetName("Test Appender")
|
||||
signature.SetReason("TestAppenderSignPage4")
|
||||
signature.SetDate(time.Now(), "")
|
||||
|
||||
if err := signature.Initialize(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sigField := model.NewPdfFieldSignature(signature)
|
||||
sigField.T = core.MakeString("Signature1")
|
||||
sigField.Rect = core.MakeArray(
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
)
|
||||
|
||||
if err = appender.Sign(1, sigField); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = appender.WriteToFile(tempFile("appender-sign-timestamp.pdf"))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
validateFile(t, tempFile("appender-sign-timestamp.pdf"))
|
||||
}
|
||||
|
||||
func TestSignatureAppearanceWithTimestamp(t *testing.T) {
|
||||
f, err := os.Open(testPdf3pages)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pdfReader, err := model.NewPdfReader(f)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
|
||||
|
||||
appender, err := model.NewPdfAppender(pdfReader)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
pfxData, _ := ioutil.ReadFile(testPKS12Key)
|
||||
privateKey, cert, err := pkcs12.Decode(pfxData, testPKS12KeyPassword)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create signature.
|
||||
signature := model.NewPdfSignature(handler)
|
||||
signature.SetName("Test Signature Appearance Name")
|
||||
signature.SetReason("TestSignatureAppearance Reason")
|
||||
signature.SetDate(time.Now(), "")
|
||||
|
||||
if err := signature.Initialize(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
numPages, err := pdfReader.GetNumPages()
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < numPages; i++ {
|
||||
pageNum := i + 1
|
||||
|
||||
// Annot1
|
||||
opts := annotator.NewSignatureFieldOpts()
|
||||
opts.FontSize = 10
|
||||
opts.Rect = []float64{10, 25, 75, 60}
|
||||
|
||||
sigField, err := annotator.NewSignatureField(
|
||||
signature,
|
||||
[]*annotator.SignatureLine{
|
||||
annotator.NewSignatureLine("Name", "Jane Doe"),
|
||||
annotator.NewSignatureLine("Date", "2019.01.03"),
|
||||
annotator.NewSignatureLine("Reason", "Some reason"),
|
||||
annotator.NewSignatureLine("Location", "New York"),
|
||||
annotator.NewSignatureLine("DN", "authority1:name1"),
|
||||
},
|
||||
opts,
|
||||
)
|
||||
sigField.T = core.MakeString(fmt.Sprintf("Signature %d", pageNum))
|
||||
|
||||
if err = appender.Sign(pageNum, sigField); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Annot2
|
||||
opts = annotator.NewSignatureFieldOpts()
|
||||
opts.FontSize = 8
|
||||
opts.Rect = []float64{250, 25, 325, 70}
|
||||
opts.TextColor = model.NewPdfColorDeviceRGB(255, 0, 0)
|
||||
|
||||
sigField, err = annotator.NewSignatureField(
|
||||
signature,
|
||||
[]*annotator.SignatureLine{
|
||||
annotator.NewSignatureLine("Name", "John Doe"),
|
||||
annotator.NewSignatureLine("Date", "2019.03.14"),
|
||||
annotator.NewSignatureLine("Reason", "No reason"),
|
||||
annotator.NewSignatureLine("Location", "London"),
|
||||
annotator.NewSignatureLine("DN", "authority2:name2"),
|
||||
},
|
||||
opts,
|
||||
)
|
||||
sigField.T = core.MakeString(fmt.Sprintf("Signature2 %d", pageNum))
|
||||
|
||||
if err = appender.Sign(pageNum, sigField); err != nil {
|
||||
log.Fatalf("Fail: %v\n", err)
|
||||
}
|
||||
|
||||
// Annot3
|
||||
opts = annotator.NewSignatureFieldOpts()
|
||||
opts.BorderSize = 1
|
||||
opts.FontSize = 10
|
||||
opts.Rect = []float64{475, 25, 590, 80}
|
||||
opts.FillColor = model.NewPdfColorDeviceRGB(255, 255, 0)
|
||||
opts.TextColor = model.NewPdfColorDeviceRGB(0, 0, 200)
|
||||
|
||||
sigField, err = annotator.NewSignatureField(
|
||||
signature,
|
||||
[]*annotator.SignatureLine{
|
||||
annotator.NewSignatureLine("Name", "John Smith"),
|
||||
annotator.NewSignatureLine("Date", "2019.02.19"),
|
||||
annotator.NewSignatureLine("Reason", "Another reason"),
|
||||
annotator.NewSignatureLine("Location", "Paris"),
|
||||
annotator.NewSignatureLine("DN", "authority3:name3"),
|
||||
},
|
||||
opts,
|
||||
)
|
||||
sigField.T = core.MakeString(fmt.Sprintf("Signature3 %d", pageNum))
|
||||
|
||||
if err = appender.Sign(pageNum, sigField); err != nil {
|
||||
log.Fatalf("Fail: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
outDoc := bytes.NewBuffer(nil)
|
||||
|
||||
if err = appender.Write(outDoc); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
pdf1, err := model.NewPdfReader(bytes.NewReader(outDoc.Bytes()))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err = model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
handler, err = sighandler.NewDocTimeStamp("https://freetsa.org/tsr", crypto.SHA512)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create signature field and appearance.
|
||||
signature = model.NewPdfSignature(handler)
|
||||
signature.SetName("Test Appender")
|
||||
signature.SetReason("TestAppenderSignPage4")
|
||||
signature.SetDate(time.Now(), "")
|
||||
|
||||
if err := signature.Initialize(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sigField := model.NewPdfFieldSignature(signature)
|
||||
sigField.T = core.MakeString("Signature1")
|
||||
sigField.Rect = core.MakeArray(
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
)
|
||||
|
||||
if err = appender.Sign(1, sigField); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = appender.WriteToFile(tempFile("appender-signature-appearance-with-timestamp.pdf"))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
validateFile(t, tempFile("appender-signature-appearance-with-timestamp.pdf"))
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gunnsth/pkcs7"
|
||||
"github.com/unidoc/pkcs7"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/core"
|
||||
"github.com/unidoc/unipdf/v3/model"
|
||||
|
211
model/sighandler/sighandler_timestamp.go
Normal file
211
model/sighandler/sighandler_timestamp.go
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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"
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unipdf/v3/common"
|
||||
"github.com/unidoc/unipdf/v3/core"
|
||||
@ -47,6 +48,9 @@ type SignatureValidationResult struct {
|
||||
// TODO(gunnsth): Add more fields such as ability to access the certificate information (name, CN, etc).
|
||||
// TODO: Also add flags to indicate whether the signature covers the entire file, or the entire portion of
|
||||
// a revision (if incremental updates used).
|
||||
|
||||
// GeneralizedTime is the time at which the time-stamp token has been created by the TSA (RFC 3161).
|
||||
GeneralizedTime time.Time
|
||||
}
|
||||
|
||||
func (v SignatureValidationResult) String() string {
|
||||
@ -88,7 +92,9 @@ func (v SignatureValidationResult) String() string {
|
||||
} else {
|
||||
buf.WriteString("Trusted: Untrusted certificate\n")
|
||||
}
|
||||
|
||||
if !v.GeneralizedTime.IsZero() {
|
||||
buf.WriteString(fmt.Sprintf("GeneralizedTime: %s\n", v.GeneralizedTime.String()))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user