mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
+plugin/wincert: basic Win32 certificate manipulation and PDF digital signing git-svn-id: svn://ultimatepp.org/upp/trunk@3715 f0d560ea-af0d-0410-9eb7-867de7ffcac7
363 lines
8.4 KiB
C++
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;
|
|
}
|
|
}
|