ultimatepp/uppsrc/plugin/wincert/pdf.cpp
rylek e5a6d9d07f +Draw/, Report/, PdfDraw/, DocTypes/: minimalistic PDF/A support
+plugin/wincert: basic Win32 certificate manipulation and PDF digital signing

git-svn-id: svn://ultimatepp.org/upp/trunk@3715 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2011-07-27 09:23:37 +00:00

363 lines
8.4 KiB
C++

#include "wincert.h"
class PDFInfo {
public:
struct Reference : Moveable<Reference> {
Reference(const Nuller& = Null) : index(0) {}
Reference(int index_, word revision_) : index(index_), revision(revision_) {}
String ToString() const;
bool IsNullInstance() const { return index == 0; }
int index;
word revision;
};
PDFInfo();
void Clear();
bool Open(const String& pdf_data);
public:
bool SkipWhite();
bool Check(const char *text);
int ScanInteger();
Reference ScanReference();
bool ParseXRef(int startxref);
bool ParseRoot();
bool ParsePageTree(Reference root);
bool Seek(Reference ref);
public:
enum { MAX_PDF_OBJECTS = 1000000 };
String pdf_data;
Vector<int> objects;
Vector<word> revisions;
Vector<Reference> pages;
int startxref;
Reference trailer_root;
Reference trailer_info;
Reference page_tree;
String root_object;
const char *term;
};
String PDFInfo::Reference::ToString() const
{
StringBuffer out;
out << index << ' ' << (int)revision << " R";
return out;
}
PDFInfo::PDFInfo()
{
Clear();
}
void PDFInfo::Clear()
{
pdf_data = Null;
objects.Clear();
revisions.Clear();
startxref = -1;
trailer_root = Null;
trailer_info = Null;
page_tree = Null;
root_object = Null;
term = NULL;
}
bool PDFInfo::Open(const String& pdf_data_)
{
Clear();
pdf_data = pdf_data_;
const char *begin = pdf_data.Begin();
term = pdf_data.End();
while(term > begin && (byte)term[-1] <= ' ')
term--;
if(term - begin <= 5 || memcmp(term - 5, "%%EOF", 5))
return false;
term -= 5;
while(term > begin && !IsDigit(*--term))
;
while(term > begin && IsDigit(term[-1]))
term--;
startxref = ScanInt(term);
if(!ParseXRef(startxref))
return false;
if(!ParseRoot() || !ParsePageTree(page_tree))
return false;
return true;
}
bool PDFInfo::ParseXRef(int start)
{
if(start < 0 || start >= pdf_data.GetLength() - 20)
return false;
term = pdf_data.GetIter(start);
if(!Check("xref"))
return false;
while(!Check("trailer")) {
if(!SkipWhite())
return false;
int start = ScanInteger();
int count = ScanInteger();
if(start < 0 || count <= 0 || start + count > MAX_PDF_OBJECTS || !SkipWhite())
return false;
int end = start + count;
if(objects.GetCount() < end) {
objects.SetCountR(end, -1);
revisions.SetCountR(end, 0xffffu);
}
for(int i = 0; i < count; i++) {
int offset = ScanInteger();
int rev = ScanInteger();
if(!SkipWhite())
return false;
if(*term != 'f' && *term != 'n')
return false;
if(*term++ == 'f') {
offset = 0;
rev = 0xffffu;
}
if(objects[start + i] == -1) {
objects[start + i] = offset;
revisions[start + i] = rev;
}
}
}
if(!Check("<<"))
return false;
while(!Check(">>")) {
if(!SkipWhite())
return false;
if(Check("/Size")) {
int sz = ScanInteger();
if(sz < objects.GetCount())
return false;
objects.SetCount(sz, -1);
revisions.SetCount(sz, 0xffffu);
}
else if(Check("/Root")) {
Reference root = ScanReference();
if(IsNull(trailer_root))
trailer_root = root;
}
else if(Check("/Info")) {
Reference info = ScanReference();
if(IsNull(trailer_info))
trailer_info = info;
}
else if(Check("/Prev")) {
int prev = ScanInteger();
const char *store_term = term;
if(!ParseXRef(prev))
return false;
term = store_term;
}
else {
while(*++term && *term != '/' && !(*term == '>' && term[1] == '>'))
;
}
}
return true;
}
bool PDFInfo::Seek(Reference r)
{
if(r.index <= 0 || r.index >= objects.GetCount() || objects[r.index] == -1)
return false;
term = pdf_data.GetIter(objects[r.index]);
int index = ScanInteger();
if(index != r.index)
return false;
int rev = ScanInteger();
if(rev != r.revision)
return false;
if(!Check("obj") || !Check("<<"))
return false;
return true;
}
bool PDFInfo::ParseRoot()
{
if(!Seek(trailer_root))
return false;
const char *begin = term;
while(*term) {
if(Check("/Pages")) {
page_tree = ScanReference();
}
else
if(*term == '>' && term[1] == '>')
break;
else
term++;
}
root_object = String(begin, term);
return true;
}
bool PDFInfo::ParsePageTree(Reference node)
{
if(!Seek(node))
return false;
if(!Check("/Type"))
return false;
if(Check("/Pages")) {
if(!Check("/Kids") || !Check("["))
return false;
while(*term && !Check("]")) {
Reference kid = ScanReference();
const char *stack = term;
if(!ParsePageTree(kid))
return false;
term = stack;
}
return true;
}
else if(Check("/Page")) {
pages.Add(node);
return true;
}
else
return false;
}
bool PDFInfo::SkipWhite()
{
while(*term && (byte)*term <= ' ')
term++;
return !!*term;
}
bool PDFInfo::Check(const char *text)
{
if(!SkipWhite())
return false;
int l = strlen(text);
if(memcmp(term, text, l))
return false;
term += l;
return true;
}
int PDFInfo::ScanInteger()
{
if(!SkipWhite())
return Null;
return ScanInt(term, &term);
}
PDFInfo::Reference PDFInfo::ScanReference()
{
int obj = ScanInteger();
if(IsNull(obj))
return Reference();
int rev = ScanInteger();
if(IsNull(rev))
return Reference();
if(SkipWhite() && *term == 'R') {
term++;
return Reference(obj, rev);
}
return Reference();
}
String CertSignPDF(PCCERT_CONTEXT cert_context, const String& pdf_data)
{
PDFInfo pdfinfo;
if(!pdfinfo.Open(pdf_data) || pdfinfo.pages.IsEmpty())
return String::GetVoid();
StringBuffer out;
out.Cat(pdf_data);
int obj_index = pdfinfo.objects.GetCount();
int sign_object = obj_index++;
int sign_dict = obj_index++;
int new_root = obj_index++;
int sign_off = out.GetLength();
out << sign_object << " 0 obj\n"
"<</FT/Sig/T(Signature1)"
"/V " << sign_dict << " 0 R"
"/F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]";
if(!IsNull(pdfinfo.page_tree))
out << "/P " << pdfinfo.pages[0];
out << ">>\n"
"endobj\n";
int dict_off = out.GetLength();
Time tm = GetSysTime();
out << sign_dict << " 0 obj\n"
"<</Type/Sig/Filter/Adobe.PPKLite/SubFilter/adbe.pkcs7.detached"
"/Reason()"
"/Location()"
"/ContactInfo()"
"/M(D:" << NFormat("%04d%02d%02d%02d%02d%02d+02'00'",
tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second)
<< ")/ByteRange [0 ";
int br_offset = out.GetLength();
int cert_length = 4096;
String tail = "/Contents <";
static const int RCL_CONT = 60;
for(;;) {
out.SetLength(br_offset);
out.Cat(' ', RCL_CONT);
out.Cat(tail);
int cert_offset = out.GetLength() - 1;
out.Cat('0', cert_length);
int post_offset = out.GetLength() + 1;
out << ">>>\n"
"endobj\n";
int root_off = out.GetLength();
out
<< new_root << " 0 obj\n"
"<<" << pdfinfo.root_object
<< "/AcroForm<</Fields[" << sign_object << " 0 R]/SigFlags 3>>>>\n"
"endobj\n";
int xref_offset = out.GetLength();
out << "xref\n"
<< sign_object << " 3\n"
<< FormatIntDec(sign_off, 10, '0') << " 00000 n\r\n"
<< FormatIntDec(dict_off, 10, '0') << " 00000 n\r\n"
<< FormatIntDec(root_off, 10, '0') << " 00000 n\r\n"
"trailer\n"
"<</Size " << obj_index << "/Root " << new_root << " 0 R";
if(!IsNull(pdfinfo.trailer_info))
out << "/Info " << pdfinfo.trailer_info;
out <<
"/ID [ <" << Uuid::Create() << "> <" << Uuid::Create() << "> ]\n"
"/Prev " << pdfinfo.startxref
<< ">>\n"
"startxref\n"
<< xref_offset
<< "\n"
"%%EOF\n";
int end_offset = out.GetLength();
String rcl_fmt = NFormat("%d %d %d ]", cert_offset,
post_offset, end_offset - post_offset);
ASSERT(rcl_fmt.GetLength() <= RCL_CONT);
memcpy(out.Begin() + br_offset, rcl_fmt, rcl_fmt.GetLength());
Vector<const BYTE *> sections;
Vector<DWORD> sizes;
sections.SetCount(2);
sizes.SetCount(2);
sections[0] = (const BYTE *)out.Begin();
sizes[0] = cert_offset;
sections[1] = (const BYTE *)out.Begin() + post_offset;
sizes[1] = end_offset - post_offset;
int sig_len = 2 * CertSignLength(cert_context, sections, sizes);
if(sig_len <= cert_length) {
String signature = BinHexEncode(CertSign(cert_context, sections, sizes));
ASSERT(signature.GetLength() <= cert_length);
memcpy(out.Begin() + cert_offset + 1, signature, signature.GetLength());
return out;
}
cert_length = sig_len;
}
}