github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/sign.go (about)

     1  /*
     2  Copyright 2025 The pdf Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package api
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  
    23  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
    24  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  func signatureStats(signValidResults []*model.SignatureValidationResult) model.SignatureStats {
    29  	sigStats := model.SignatureStats{Total: len(signValidResults)}
    30  	for _, svr := range signValidResults {
    31  		signed, signedVisible, unsigned, unsignedVisible := sigStats.Counter(svr)
    32  		if svr.Signed {
    33  			*signed++
    34  			if svr.Visible {
    35  				*signedVisible++
    36  			}
    37  			continue
    38  		}
    39  		*unsigned++
    40  		if svr.Visible {
    41  			*unsignedVisible++
    42  		}
    43  	}
    44  	return sigStats
    45  }
    46  
    47  func statsCounter(stats model.SignatureStats, ss *[]string) {
    48  	plural := func(count int) string {
    49  		if count == 1 {
    50  			return ""
    51  		}
    52  		return "s"
    53  	}
    54  
    55  	if stats.FormSigned > 0 {
    56  		*ss = append(*ss, fmt.Sprintf("%d signed form signature%s (%d visible)", stats.FormSigned, plural(stats.FormSigned), stats.FormSignedVisible))
    57  	}
    58  	if stats.FormUnsigned > 0 {
    59  		*ss = append(*ss, fmt.Sprintf("%d unsigned form signature%s (%d visible)", stats.FormUnsigned, plural(stats.FormUnsigned), stats.FormUnsignedVisible))
    60  	}
    61  
    62  	if stats.PageSigned > 0 {
    63  		*ss = append(*ss, fmt.Sprintf("%d signed page signature%s (%d visible)", stats.PageSigned, plural(stats.PageSigned), stats.PageSignedVisible))
    64  	}
    65  	if stats.PageUnsigned > 0 {
    66  		*ss = append(*ss, fmt.Sprintf("%d unsigned page signature%s (%d visible)", stats.PageUnsigned, plural(stats.PageUnsigned), stats.PageUnsignedVisible))
    67  	}
    68  
    69  	if stats.URSigned > 0 {
    70  		*ss = append(*ss, fmt.Sprintf("%d signed usage rights signature%s (%d visible)", stats.URSigned, plural(stats.URSigned), stats.URSignedVisible))
    71  	}
    72  	if stats.URUnsigned > 0 {
    73  		*ss = append(*ss, fmt.Sprintf("%d unsigned usage rights signature%s (%d visible)", stats.URUnsigned, plural(stats.URUnsigned), stats.URUnsignedVisible))
    74  	}
    75  
    76  	if stats.DTSSigned > 0 {
    77  		*ss = append(*ss, fmt.Sprintf("%d signed doc timestamp signature%s (%d visible)", stats.DTSSigned, plural(stats.DTSSigned), stats.DTSSignedVisible))
    78  	}
    79  	if stats.DTSUnsigned > 0 {
    80  		*ss = append(*ss, fmt.Sprintf("%d unsigned doc timestamp signature%s (%d visible)", stats.DTSUnsigned, plural(stats.DTSUnsigned), stats.DTSUnsignedVisible))
    81  	}
    82  }
    83  
    84  func digest(signValidResults []*model.SignatureValidationResult, full bool) []string {
    85  	var ss []string
    86  
    87  	if full {
    88  		ss = append(ss, "")
    89  		for i, r := range signValidResults {
    90  			//ss = append(ss, fmt.Sprintf("%d. Sisgnature:\n", i+1))
    91  			ss = append(ss, fmt.Sprintf("%d:", i+1))
    92  			ss = append(ss, r.String()+"\n")
    93  		}
    94  		return ss
    95  	}
    96  
    97  	if len(signValidResults) == 1 {
    98  		svr := signValidResults[0]
    99  		ss = append(ss, "")
   100  		ss = append(ss, fmt.Sprintf("1 %s", svr.Signature.String(svr.Status)))
   101  		ss = append(ss, fmt.Sprintf("   Status: %s", svr.Status))
   102  		s := svr.Reason.String()
   103  		if svr.Reason == model.SignatureReasonInternal {
   104  			if len(svr.Problems) > 0 {
   105  				s = svr.Problems[0]
   106  			}
   107  		}
   108  		ss = append(ss, fmt.Sprintf("   Reason: %s", s))
   109  		ss = append(ss, fmt.Sprintf("   Signed: %s", svr.SigningTime()))
   110  		return ss
   111  	}
   112  
   113  	stats := signatureStats(signValidResults)
   114  
   115  	ss = append(ss, "")
   116  	ss = append(ss, fmt.Sprintf("%d signatures present:", stats.Total))
   117  
   118  	statsCounter(stats, &ss)
   119  
   120  	for i, svr := range signValidResults {
   121  		ss = append(ss, fmt.Sprintf("\n%d:", i+1))
   122  		ss = append(ss, fmt.Sprintf("     Type: %s", svr.Signature.String(svr.Status)))
   123  		ss = append(ss, fmt.Sprintf("   Status: %s", svr.Status.String()))
   124  		s := svr.Reason.String()
   125  		if svr.Reason == model.SignatureReasonInternal {
   126  			if len(svr.Problems) > 0 {
   127  				s = svr.Problems[0]
   128  			}
   129  		}
   130  		ss = append(ss, fmt.Sprintf("   Reason: %s", s))
   131  		ss = append(ss, fmt.Sprintf("   Signed: %s", svr.SigningTime()))
   132  	}
   133  
   134  	return ss
   135  }
   136  
   137  // ValidateSignatures validates signatures of inFile and returns the signature validation results.
   138  func ValidateSignatures(inFile string, all bool, conf *model.Configuration) ([]*model.SignatureValidationResult, error) {
   139  
   140  	if conf == nil {
   141  		conf = model.NewDefaultConfiguration()
   142  	}
   143  	conf.Cmd = model.VALIDATESIGNATURE
   144  
   145  	if _, err := LoadCertificates(); err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	f, err := os.Open(inFile)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	ctx, err := ReadValidateAndOptimize(f, conf)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	if len(ctx.Signatures) == 0 && !ctx.SignatureExist && !ctx.AppendOnly {
   160  		return nil, errors.New("pdfcpu: No signatures present.")
   161  	}
   162  
   163  	return pdfcpu.ValidateSignatures(f, ctx, all)
   164  }
   165  
   166  // ValidateSignaturesFile validates signatures of inFile.
   167  // all: processes all signatures meaning not only the authoritative/certified signature..
   168  // full: verbose output including cert chain and problems encountered.
   169  func ValidateSignaturesFile(inFile string, all, full bool, conf *model.Configuration) ([]string, error) {
   170  	if conf == nil {
   171  		conf = model.NewDefaultConfiguration()
   172  	}
   173  
   174  	signValidResults, err := ValidateSignatures(inFile, all, conf)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	return digest(signValidResults, full), nil
   180  }