Skip to content

Instantly share code, notes, and snippets.

@trueroad
Last active August 1, 2023 10:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trueroad/5fc2de151f717e34b250ac99fa141604 to your computer and use it in GitHub Desktop.
Save trueroad/5fc2de151f717e34b250ac99fa141604 to your computer and use it in GitHub Desktop.
Extract PDF signature tool

Extract PDF signature tool

https://gist.github.com/trueroad/5fc2de151f717e34b250ac99fa141604

Copyright (C) 2019 Masamichi Hosoda. All rights reserved.

License: BSD-2-Clause


Extract PDF signature tools can extract PKCS#7 signed-data from digitally signed PDF. You can verify the signature with OpenSSL.

Experiment PDF sign tool can create digitally signed PDF.

Require

Build

  • Edit QPDF_PREFIX valiable in Makefile
  • Run make command

Usage

No certificate verify (message digest check only)

$ ./extract-pdf-sign input.pdf prefix
extract-pdf-sig
Copyright (C) Masamichi Hosoda 2019
License: BSD-2-Clause

INPUT.pdf    : input-signed.pdf
OUTPUT_PREFIX: prefix

--- signature dictionary 21/0 ---
offset sig dict = 59515 (0xe87b)
offset contents = 59599 (0xe8cf)
size contents = 2243
File: /ByteRange [ 0 59599 64087 4759 ]
offset /ByteRange offset1 = 59529 (0xe889)
offset /ByteRange length1 = 59531 (0xe88b)
offset /ByteRange offset2 = 59537 (0xe891)
offset /ByteRange length2 = 59543 (0xe897)
offset eof = 68846 (0x10cee)
Correct: /ByteRange [ 0 59599 64087 4759 ]
writing prefix-21-0-sigdict-contents.p7s ...
writing prefix-21-0-for-md-calc.bin ...
complete

$ openssl smime -verify \
    -in prefix-21-0-sigdict-contents.p7s -inform DER \
    -content prefix-21-0-for-md-calc.bin \
    -noverify -out /dev/null
Verification successful

$

With root certificate verify

$ ./extract-pdf-sig input-cacert-signed.pdf input-cacert-signed
extract-pdf-sig
Copyright (C) Masamichi Hosoda 2019
License: BSD-2-Clause

INPUT.pdf    : input-cacert-signed.pdf
OUTPUT_PREFIX: input-cacert-signed

--- signature dictionary 5/0 ---
offset sig dict = 691 (0x2b3)
offset contents = 754 (0x2f2)
size contents = 8192
File: /ByteRange [ 0 754 17140 1080 ]
offset /ByteRange offset1 = 707 (0x2c3)
offset /ByteRange length1 = 709 (0x2c5)
offset /ByteRange offset2 = 713 (0x2c9)
offset /ByteRange length2 = 719 (0x2cf)
offset eof = 18220 (0x472c)
Correct: /ByteRange [ 0 754 17140 1080 ]
writing input-cacert-signed-5-0-sigdict-contents.p7s ...
writing input-cacert-signed-5-0-for-md-calc.bin ...
complete

$ openssl smime -verify \
    -in input-cacert-signed-5-0-sigdict-contents.p7s -inform DER \
    -content input-cacert-signed-5-0-for-md-calc.bin \
    -CAfile root_X0F.crt \
    -out /dev/null
Verification successful

$
//
// extract-pdf-sig
// Copyright (C) 2019 Masamichi Hosoda. All rights reserved.
// License: BSD-2-Clause
//
// https://gist.github.com/trueroad/5fc2de151f717e34b250ac99fa141604
//
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFObjectHandle.hh>
class sig_dict_contents
{
public:
explicit sig_dict_contents (QPDFObjectHandle dict):
dict_ (dict),
bvalid_ (false)
{
if (dict.isDictionary () &&
dict.hasKey ("/ByteRange") &&
dict.getKey ("/ByteRange").isArray () &&
dict.hasKey ("/Contents") &&
dict.getKey ("/Contents").isString () &&
dict.hasKey ("/Type") &&
dict.getKey ("/Type").isName () &&
dict.getKey ("/Type").getName () == "/Sig")
{
object_ = dict.getKey ("/Contents");
offset_ = object_.getParsedOffset ();
size_ = object_.getStringValue ().size ();
bvalid_ = true;
}
}
void show ();
void save (const std::string &file);
QPDFObjectHandle object () const
{
return object_;
}
QPDFObjectHandle dict () const
{
return dict_;
}
qpdf_offset_t offset () const noexcept
{
return offset_;
}
size_t size () const noexcept
{
return size_;
}
explicit operator bool () const noexcept
{
return bvalid_;
}
private:
QPDFObjectHandle object_;
QPDFObjectHandle dict_;
qpdf_offset_t offset_;
size_t size_;
bool bvalid_;
};
class byterange
{
public:
explicit byterange (const sig_dict_contents &sdc, size_t eof):
sdc_ (sdc),
offset_eof_ (eof)
{
offset1_ = 0;
length1_ = sdc.offset ();
offset2_ = length1_ + sdc.size () * 2 + 2;
length2_ = eof - offset2_;
}
static byterange calc (const std::string &file,
const sig_dict_contents &sdc);
void show ();
void create_file (const std::string &in, const std::string &prefix);
public:
sig_dict_contents sdc_;
size_t offset_eof_;
unsigned int offset1_;
unsigned int length1_;
unsigned int offset2_;
unsigned int length2_;
};
void sig_dict_contents::show ()
{
if(!bvalid_)
return;
std::cout << "--- signature dictionary "
<< dict_.getObjectID ()
<< "/"
<< dict_.getGeneration ()
<< " ---" << std::endl;
auto offset_dict {dict_.getParsedOffset ()};
std::cout << "offset sig dict = "
<< offset_dict
<< " (0x"
<< std::hex << offset_dict << std::dec
<< ")" << std::endl;
std::cout << "offset contents = "
<< offset_
<< " (0x"
<< std::hex << offset_ << std::dec
<< ")" << std::endl;
std::cout << "size contents = "
<< size_ << std::endl;
auto byterange {dict_.getKey ("/ByteRange")};
std::cout << "File: /ByteRange [ "
<< byterange.getArrayItem (0).getIntValue ()
<< " "
<< byterange.getArrayItem (1).getIntValue ()
<< " "
<< byterange.getArrayItem (2).getIntValue ()
<< " "
<< byterange.getArrayItem (3).getIntValue ()
<< " ]"
<< std::endl;
auto offset1 = byterange.getArrayItem (0).getParsedOffset ();
auto length1 = byterange.getArrayItem (1).getParsedOffset ();
auto offset2 = byterange.getArrayItem (2).getParsedOffset ();
auto length2 = byterange.getArrayItem (3).getParsedOffset ();
std::cout << "offset /ByteRange offset1 = "
<< offset1
<< " (0x"
<< std::hex << offset1 << std::dec
<< ")" << std::endl;
std::cout << "offset /ByteRange length1 = "
<< length1
<< " (0x"
<< std::hex << length1 << std::dec
<< ")" << std::endl;
std::cout << "offset /ByteRange offset2 = "
<< offset2
<< " (0x"
<< std::hex << offset2 << std::dec
<< ")" << std::endl;
std::cout << "offset /ByteRange length2 = "
<< length2
<< " (0x"
<< std::hex << length2 << std::dec
<< ")" << std::endl;
}
void sig_dict_contents::save (const std::string &prefix)
{
auto str {object_.getStringValue ()};
std::stringstream ss;
ss << prefix << "-"
<< dict_.getObjectID () << "-"
<< dict_.getGeneration () << "-sigdict-contents.p7s";
std::cout << "writing " << ss.str () << " ..." << std::endl;
std::ofstream ofs (ss.str (), std::ios_base::out | std::ios_base::binary);
ofs.write (&str[0], str.size ());
}
byterange byterange::calc (const std::string &file,
const sig_dict_contents &sdc)
{
std::ifstream ifs (file, std::ios::in | std::ios::binary);
ifs.seekg (0, std::fstream::end);
auto offset_eof {static_cast<size_t> (ifs.tellg ())};
byterange retval (sdc, offset_eof);
return retval;
}
void byterange::show ()
{
std::cout << "offset eof = "
<< offset_eof_
<< " (0x"
<< std::hex << offset_eof_ << std::dec
<< ")" << std::endl;
std::cout << "Correct: /ByteRange [ "
<< offset1_
<< " "
<< length1_
<< " "
<< offset2_
<< " "
<< length2_
<< " ]" << std::endl;
}
void byterange::create_file (const std::string &in,
const std::string &prefix)
{
std::stringstream ss;
ss << prefix << "-"
<< sdc_.dict ().getObjectID () << "-"
<< sdc_.dict ().getGeneration () << "-for-md-calc.bin";
std::cout << "writing " << ss.str () << " ..." << std::endl;
std::ifstream ifs (in, std::ios_base::in | std::ios_base::binary);
std::ofstream ofs (ss.str (), std::ios_base::out | std::ios_base::binary);
std::vector<char> buff;
buff.resize (length1_);
ifs.seekg (0, std::fstream::beg);
ifs.read (&buff[0], length1_);
ofs.write (&buff[0], ifs.gcount ());
buff.resize (length2_);
ifs.seekg (offset2_, std::fstream::beg);
ifs.read (&buff[0], length2_);
ofs.write (&buff[0], ifs.gcount ());
}
int main (int argc, char *argv[])
{
std::cout
<< "extract-pdf-sig" << std::endl
<< "Copyright (C) Masamichi Hosoda 2019" << std::endl
<< "License: BSD-2-Clause" << std::endl << std::endl;
if (argc != 3)
{
std::cerr
<< "usage: " << argv[0]
<< " INPUT.pdf OUTPUT_PREFIX"
<< std::endl;
return 1;
}
std::string filename_input {argv[1]};
std::string filename_output_prefix {argv[2]};
std::cerr << "INPUT.pdf : " << filename_input << std::endl
<< "OUTPUT_PREFIX: " << filename_output_prefix << std::endl
<< std::endl;
QPDF qpdf;
qpdf.processFile (filename_input.c_str ());
auto objs {qpdf.getAllObjects ()};
for (auto obj: objs)
{
sig_dict_contents sdc (obj);
if (sdc)
{
sdc.show ();
auto br {byterange::calc (filename_input, sdc)};
br.show ();
sdc.save (filename_output_prefix);
br.create_file (filename_input, filename_output_prefix);
}
}
std::cout << "complete" << std::endl;
return 0;
}
QPDF_PREFIX = /usr/local
QPDF_PKGCONFIG_PATH = $(QPDF_PREFIX)/lib/pkgconfig
BIN = extract-pdf-sig
.PHONY: all clean
all: $(BIN)
OBJS = $(addsuffix .o,$(BIN))
CXXFLAGS_STD = -std=c++11
QPDF_CPPFLAGS = \
$(shell PKG_CONFIG_PATH=$(QPDF_PKGCONFIG_PATH) pkg-config --cflags libqpdf)
QPDF_LDLIBS = \
$(shell PKG_CONFIG_PATH=$(QPDF_PKGCONFIG_PATH) pkg-config --libs libqpdf)
CXXFLAGS += $(CXXFLAGS_STD)
CPPFLAGS += $(QPDF_CPPFLAGS)
LDLIBS += $(QPDF_LDLIBS)
DEPS = $(OBJS:.o=.d)
CPPFLAGS += -MMD -MP -MF $(@:.o=.d) -MT $@
-include $(DEPS)
clean:
$(RM) *~ $(OBJS) $(DEPS)
$(BIN): $(OBJS)
$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
#QPDF_DLL_PATH = $(QPDF_PREFIX)/bin
#QPDF_DLL = $(notdir $(wildcard $(QPDF_DLL_PATH)/cygqpdf-*.dll))
#all: $(QPDF_DLL)
#
#$(QPDF_DLL): $(QPDF_DLL_PATH)/$(QPDF_DLL)
# cp $^ $@
@insinfo
Copy link

insinfo commented Jun 13, 2023

Do you have a compiled version for windows?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment