Avoid downgrading the document's PDF version
This commit is contained in:
parent
1224d9be47
commit
2d08100b58
|
@ -9,8 +9,6 @@ the Cairo library. As such, it currently comes with some restrictions:
|
||||||
overwritten
|
overwritten
|
||||||
* the document may not employ cross-reference streams, or must constitute
|
* the document may not employ cross-reference streams, or must constitute
|
||||||
a hybrid-reference file at least
|
a hybrid-reference file at least
|
||||||
* the document may not be newer than PDF 1.6 already, or it will get downgraded
|
|
||||||
to that version
|
|
||||||
* the signature may take at most 4 kilobytes as a compile-time limit,
|
* the signature may take at most 4 kilobytes as a compile-time limit,
|
||||||
which should be enough space even for one intermediate certificate
|
which should be enough space even for one intermediate certificate
|
||||||
|
|
||||||
|
|
|
@ -342,6 +342,9 @@ public:
|
||||||
|
|
||||||
/// Build the cross-reference table and prepare a new trailer dictionary
|
/// Build the cross-reference table and prepare a new trailer dictionary
|
||||||
std::string initialize();
|
std::string initialize();
|
||||||
|
/// Try to extract the claimed PDF version as a positive decimal number, e.g. 17 for PDF 1.7.
|
||||||
|
/// Returns zero on failure.
|
||||||
|
int version(const pdf_object& root) const;
|
||||||
/// Retrieve an object by its number and generation -- may return NIL or END with an error
|
/// Retrieve an object by its number and generation -- may return NIL or END with an error
|
||||||
pdf_object get(uint n, uint generation) const;
|
pdf_object get(uint n, uint generation) const;
|
||||||
/// Allocate a new object number
|
/// Allocate a new object number
|
||||||
|
@ -512,7 +515,7 @@ std::string pdf_updater::load_xref(pdf_lexer& lex, std::set<uint>& loaded_entrie
|
||||||
|
|
||||||
std::string pdf_updater::initialize() {
|
std::string pdf_updater::initialize() {
|
||||||
// We only need to look for startxref roughly within the last kibibyte of the document
|
// We only need to look for startxref roughly within the last kibibyte of the document
|
||||||
static std::regex haystack_re("[\\s\\S]*\\sstartxref\\s+(\\d+)\\s+%%EOF");
|
static std::regex haystack_re(R"([\s\S]*\sstartxref\s+(\d+)\s+%%EOF)");
|
||||||
std::string haystack = document.substr(document.length() < 1024 ? 0 : document.length() - 1024);
|
std::string haystack = document.substr(document.length() < 1024 ? 0 : document.length() - 1024);
|
||||||
|
|
||||||
std::smatch m;
|
std::smatch m;
|
||||||
|
@ -560,6 +563,25 @@ std::string pdf_updater::initialize() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pdf_updater::version(const pdf_object& root) const {
|
||||||
|
auto version = root.dict.find("Version");
|
||||||
|
if (version != root.dict.end() && version->second.type == pdf_object::NAME) {
|
||||||
|
const auto& v = version->second.string;
|
||||||
|
if (isdigit(v[0]) && v[1] == '.' && isdigit(v[2]) && !v[3])
|
||||||
|
return (v[0] - '0') * 10 + (v[2] - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need to look for the comment roughly within the first kibibyte of the document
|
||||||
|
static std::regex version_re(R"((?:^|[\r\n])%(?:!PS-Adobe-\d\.\d )?PDF-(\d)\.(\d)[\r\n])");
|
||||||
|
std::string haystack = document.substr(0, 1024);
|
||||||
|
|
||||||
|
std::smatch m;
|
||||||
|
if (std::regex_search(haystack, m, version_re, std::regex_constants::match_default))
|
||||||
|
return std::stoul(m.str(1)) * 10 + std::stoul(m.str(2));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
pdf_object pdf_updater::get(uint n, uint generation) const {
|
pdf_object pdf_updater::get(uint n, uint generation) const {
|
||||||
if (n >= xref_size)
|
if (n >= xref_size)
|
||||||
return {pdf_object::NIL};
|
return {pdf_object::NIL};
|
||||||
|
@ -806,8 +828,6 @@ error:
|
||||||
/// streams from PDF 1.5, or at least constitutes a hybrid-reference file. The results with
|
/// streams from PDF 1.5, or at least constitutes a hybrid-reference file. The results with
|
||||||
/// PDF 2.0 (2017) are currently unknown as the standard costs money.
|
/// PDF 2.0 (2017) are currently unknown as the standard costs money.
|
||||||
///
|
///
|
||||||
/// Carelessly assumes that the version of the original document is at most PDF 1.6.
|
|
||||||
///
|
|
||||||
/// https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf
|
/// https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf
|
||||||
/// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
|
/// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
|
||||||
/// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf
|
/// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf
|
||||||
|
@ -892,9 +912,9 @@ static std::string pdf_sign(std::string& document) {
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Upgrade the document version for SHA-256 etc.
|
// Upgrade the document version for SHA-256 etc.
|
||||||
// XXX assuming that it's not newer than 1.6 already -- while Cairo can't currently use a newer
|
if (pdf.version(root) < 16)
|
||||||
// version than 1.5, it's not a bad idea to use cairo_pdf_surface_restrict_to_version()
|
|
||||||
root.dict["Version"] = {pdf_object::NAME, "1.6"};
|
root.dict["Version"] = {pdf_object::NAME, "1.6"};
|
||||||
|
|
||||||
pdf.update(root_ref->second.n, [&]{ pdf.document += pdf_serialize(root); });
|
pdf.update(root_ref->second.n, [&]{ pdf.document += pdf_serialize(root); });
|
||||||
pdf.flush_updates();
|
pdf.flush_updates();
|
||||||
|
|
||||||
|
|
38
pdf/pdf.go
38
pdf/pdf.go
|
@ -670,7 +670,7 @@ func (u *Updater) loadXref(lex *Lexer, loadedEntries map[uint]struct{}) error {
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
var haystackRE = regexp.MustCompile(`(?s:.*)\sstartxref\s+(\d+)\s+%%EOF`)
|
var trailerRE = regexp.MustCompile(`(?s:.*)\sstartxref\s+(\d+)\s+%%EOF`)
|
||||||
|
|
||||||
// NewUpdater initializes an Updater, building the cross-reference table and
|
// NewUpdater initializes an Updater, building the cross-reference table and
|
||||||
// preparing a new trailer dictionary.
|
// preparing a new trailer dictionary.
|
||||||
|
@ -685,7 +685,7 @@ func NewUpdater(document []byte) (*Updater, error) {
|
||||||
haystack = haystack[len(haystack)-1024:]
|
haystack = haystack[len(haystack)-1024:]
|
||||||
}
|
}
|
||||||
|
|
||||||
m := haystackRE.FindSubmatch(haystack)
|
m := trailerRE.FindSubmatch(haystack)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return nil, errors.New("cannot find startxref")
|
return nil, errors.New("cannot find startxref")
|
||||||
}
|
}
|
||||||
|
@ -739,6 +739,31 @@ func NewUpdater(document []byte) (*Updater, error) {
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var versionRE = regexp.MustCompile(
|
||||||
|
`(?:^|[\r\n])%(?:!PS-Adobe-\d\.\d )?PDF-(\d)\.(\d)[\r\n]`)
|
||||||
|
|
||||||
|
// Version extracts the claimed PDF version as a positive decimal number,
|
||||||
|
// e.g. 17 for PDF 1.7. Returns zero on failure.
|
||||||
|
func (u *Updater) Version(root *Object) int {
|
||||||
|
if version, ok := root.Dict["Version"]; ok && version.Kind == Name {
|
||||||
|
if v := version.String; len(v) == 3 && v[1] == '.' &&
|
||||||
|
v[0] >= '0' && v[0] <= '9' && v[2] >= '0' && v[2] <= '9' {
|
||||||
|
return int(v[0]-'0')*10 + int(v[2]-'0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need to look for the comment roughly within
|
||||||
|
// the first kibibyte of the document.
|
||||||
|
haystack := u.Document
|
||||||
|
if len(haystack) > 1024 {
|
||||||
|
haystack = haystack[:1024]
|
||||||
|
}
|
||||||
|
if m := versionRE.FindSubmatch(haystack); m != nil {
|
||||||
|
return int(m[1][0]-'0')*10 + int(m[2][0]-'0')
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// Get retrieves an object by its number and generation--may return
|
// Get retrieves an object by its number and generation--may return
|
||||||
// Nil or End with an error.
|
// Nil or End with an error.
|
||||||
//
|
//
|
||||||
|
@ -1094,9 +1119,6 @@ func FillInSignature(document []byte, signOff, signLen int,
|
||||||
// employ cross-reference streams from PDF 1.5, or at least constitutes
|
// employ cross-reference streams from PDF 1.5, or at least constitutes
|
||||||
// a hybrid-reference file. The results with PDF 2.0 (2017) are currently
|
// a hybrid-reference file. The results with PDF 2.0 (2017) are currently
|
||||||
// unknown as the standard costs money.
|
// unknown as the standard costs money.
|
||||||
//
|
|
||||||
// Carelessly assumes that the version of the original document is at most
|
|
||||||
// PDF 1.6.
|
|
||||||
func Sign(document []byte,
|
func Sign(document []byte,
|
||||||
key crypto.PrivateKey, certs []*x509.Certificate) ([]byte, error) {
|
key crypto.PrivateKey, certs []*x509.Certificate) ([]byte, error) {
|
||||||
pdf, err := NewUpdater(document)
|
pdf, err := NewUpdater(document)
|
||||||
|
@ -1195,10 +1217,10 @@ func Sign(document []byte,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Upgrade the document version for SHA-256 etc.
|
// Upgrade the document version for SHA-256 etc.
|
||||||
// XXX: Assuming that it's not newer than 1.6 already--while Cairo can't
|
if pdf.Version(&root) < 16 {
|
||||||
// currently use a newer version that 1.5, it's not a bad idea to use
|
|
||||||
// cairo_pdf_surface_restrict_to_version().
|
|
||||||
root.Dict["Version"] = NewName("1.6")
|
root.Dict["Version"] = NewName("1.6")
|
||||||
|
}
|
||||||
|
|
||||||
pdf.Update(rootRef.N, func(buf BytesWriter) {
|
pdf.Update(rootRef.N, func(buf BytesWriter) {
|
||||||
buf.WriteString(root.Serialize())
|
buf.WriteString(root.Serialize())
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue