summaryrefslogtreecommitdiff
path: root/source/pdf/pdf-pkcs7.c
diff options
context:
space:
mode:
authorPaul Gardiner <paulg.artifex@glidos.net>2013-08-13 13:44:40 +0100
committerPaul Gardiner <paulg.artifex@glidos.net>2013-08-13 16:36:18 +0100
commit00fbe2446c7ca2c291b785da99f6e0b293574001 (patch)
tree9e0e2c9e28c418abfb602368483a80128fb3ec3e /source/pdf/pdf-pkcs7.c
parent7b11a823f9e35c971cc4f1719a6f9c3947a2ba47 (diff)
downloadmupdf-00fbe2446c7ca2c291b785da99f6e0b293574001.tar.xz
Signature creation
Diffstat (limited to 'source/pdf/pdf-pkcs7.c')
-rw-r--r--source/pdf/pdf-pkcs7.c392
1 files changed, 392 insertions, 0 deletions
diff --git a/source/pdf/pdf-pkcs7.c b/source/pdf/pdf-pkcs7.c
index 6515abc6..23e8af84 100644
--- a/source/pdf/pdf-pkcs7.c
+++ b/source/pdf/pdf-pkcs7.c
@@ -10,6 +10,7 @@
#include "openssl/objects.h"
#include "openssl/pem.h"
#include "openssl/pkcs7.h"
+#include "openssl/pkcs12.h"
enum
{
@@ -279,6 +280,8 @@ static int verify_sig(char *sig, int sig_len, char *file, int (*byte_range)[2],
goto exit;
bdata = BIO_new(BIO_s_file());
+ if (bdata == NULL)
+ goto exit;
BIO_read_filename(bdata, file);
bsegs = BIO_new(BIO_f_segments());
@@ -339,6 +342,335 @@ exit:
return res;
}
+typedef struct pdf_designated_name_openssl_s
+{
+ pdf_designated_name base;
+ fz_context *ctx;
+ char buf[8192];
+} pdf_designated_name_openssl;
+
+struct pdf_signer_s
+{
+ fz_context *ctx;
+ int refs;
+ X509 *x509;
+ EVP_PKEY *pkey;
+};
+
+void pdf_free_designated_name(pdf_designated_name *dn)
+{
+ if (dn)
+ fz_free(((pdf_designated_name_openssl *)dn)->ctx, dn);
+}
+
+
+static void add_from_bags(X509 **pX509, EVP_PKEY **pPkey, STACK_OF(PKCS12_SAFEBAG) *bags, char *pw);
+
+static void add_from_bag(X509 **pX509, EVP_PKEY **pPkey, PKCS12_SAFEBAG *bag, char *pw)
+{
+ EVP_PKEY *pkey = NULL;
+ X509 *x509 = NULL;
+ PKCS8_PRIV_KEY_INFO *p8 = NULL;
+ switch (M_PKCS12_bag_type(bag))
+ {
+ case NID_keyBag:
+ p8 = bag->value.keybag;
+ pkey = EVP_PKCS82PKEY(p8);
+ break;
+
+ case NID_pkcs8ShroudedKeyBag:
+ p8 = PKCS12_decrypt_skey(bag, pw, (int)strlen(pw));
+ if (p8)
+ {
+ pkey = EVP_PKCS82PKEY(p8);
+ PKCS8_PRIV_KEY_INFO_free(p8);
+ }
+ break;
+
+ case NID_certBag:
+ if (M_PKCS12_cert_bag_type(bag) == NID_x509Certificate)
+ x509 = PKCS12_certbag2x509(bag);
+ break;
+
+ case NID_safeContentsBag:
+ add_from_bags(pX509, pPkey, bag->value.safes, pw);
+ break;
+ }
+
+ if (pkey)
+ {
+ if (!*pPkey)
+ *pPkey = pkey;
+ else
+ EVP_PKEY_free(pkey);
+ }
+
+ if (x509)
+ {
+ if (!*pX509)
+ *pX509 = x509;
+ else
+ X509_free(x509);
+ }
+}
+
+static void add_from_bags(X509 **pX509, EVP_PKEY **pPkey, STACK_OF(PKCS12_SAFEBAG) *bags, char *pw)
+{
+ int i;
+
+ for (i = 0; i < sk_PKCS12_SAFEBAG_num(bags); i++)
+ add_from_bag(pX509, pPkey, sk_PKCS12_SAFEBAG_value(bags, i), pw);
+}
+
+pdf_signer *pdf_read_pfx(fz_context *ctx, char *pfile, char *pw)
+{
+ BIO *pfxbio = NULL;
+ PKCS12 *p12 = NULL;
+ STACK_OF(PKCS7) *asafes;
+ pdf_signer *signer = NULL;
+ int i;
+
+ fz_var(pfxbio);
+ fz_var(p12);
+ fz_var(signer);
+ fz_try(ctx)
+ {
+ signer = fz_malloc_struct(ctx, pdf_signer);
+ signer->ctx = ctx;
+ signer->refs = 1;
+
+ OpenSSL_add_all_algorithms();
+
+ EVP_add_digest(EVP_md5());
+ EVP_add_digest(EVP_sha1());
+
+ ERR_load_crypto_strings();
+
+ ERR_clear_error();
+
+ pfxbio = BIO_new_file(pfile, "r");
+ if (pfxbio == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Can't open pfx file: %s", pfile);
+
+ p12 = d2i_PKCS12_bio(pfxbio, NULL);
+ if (p12 == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid pfx file: %s", pfile);
+
+ asafes = PKCS12_unpack_authsafes(p12);
+ if (asafes == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid pfx file: %s", pfile);
+
+ /* Nothing in this for loop can fz_throw */
+ for (i = 0; i < sk_PKCS7_num(asafes); i++)
+ {
+ PKCS7 *p7;
+ STACK_OF(PKCS12_SAFEBAG) *bags;
+ int bagnid;
+
+ p7 = sk_PKCS7_value(asafes, i);
+ bagnid = OBJ_obj2nid(p7->type);
+ switch (bagnid)
+ {
+ case NID_pkcs7_data:
+ bags = PKCS12_unpack_p7data(p7);
+ break;
+ case NID_pkcs7_encrypted:
+ bags = PKCS12_unpack_p7encdata(p7, pw, (int)strlen(pw));
+ break;
+ default:
+ continue;
+ }
+
+ if (bags)
+ {
+ add_from_bags(&signer->x509, &signer->pkey, bags, pw);
+ sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free);
+ }
+ }
+ sk_PKCS7_pop_free (asafes, PKCS7_free);
+
+ if (signer->pkey == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to obtain public key");
+
+ if (signer->x509 == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to obtain certificate");
+ }
+ fz_always(ctx)
+ {
+ BIO_free(pfxbio);
+ PKCS12_free(p12);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_signer(signer);
+ fz_rethrow(ctx);
+ }
+
+ return signer;
+}
+
+pdf_signer *pdf_keep_signer(pdf_signer *signer)
+{
+ if (signer)
+ signer->refs++;
+
+ return signer;
+}
+
+void pdf_drop_signer(pdf_signer *signer)
+{
+ if (signer)
+ {
+ if (--signer->refs == 0)
+ {
+ X509_free(signer->x509);
+ EVP_PKEY_free(signer->pkey);
+ fz_free(signer->ctx, signer);
+ }
+ }
+}
+
+pdf_designated_name *pdf_signer_designated_name(pdf_signer *signer)
+{
+ fz_context *ctx = signer->ctx;
+ pdf_designated_name_openssl *dn = fz_malloc_struct(ctx, pdf_designated_name_openssl);
+ char *p;
+
+ dn->ctx = ctx;
+ X509_NAME_oneline(X509_get_subject_name(signer->x509), dn->buf, sizeof(dn->buf));
+ p = strstr(dn->buf, "/CN=");
+ if (p) dn->base.cn = p+4;
+ p = strstr(dn->buf, "/O=");
+ if (p) dn->base.o = p+3;
+ p = strstr(dn->buf, "/OU=");
+ if (p) dn->base.ou = p+4;
+ p = strstr(dn->buf, "/emailAddress=");
+ if (p) dn->base.email = p+14;
+ p = strstr(dn->buf, "/C=");
+ if (p) dn->base.c = p+3;
+
+ for (p = dn->buf; *p; p++)
+ if (*p == '/')
+ *p = 0;
+
+ return (pdf_designated_name *)dn;
+}
+
+void pdf_write_digest(pdf_document *doc, char *filename, pdf_obj *byte_range, int digest_offset, int digest_length, pdf_signer *signer)
+{
+ fz_context *ctx = doc->ctx;
+ BIO *bdata = NULL;
+ BIO *bsegs = NULL;
+ BIO *bp7in = NULL;
+ BIO *bp7 = NULL;
+ PKCS7 *p7 = NULL;
+ PKCS7_SIGNER_INFO *si;
+ FILE *f = NULL;
+
+ int (*brange)[2] = NULL;
+ int brange_len = pdf_array_len(byte_range)/2;
+
+ fz_var(bdata);
+ fz_var(bsegs);
+ fz_var(bp7in);
+ fz_var(bp7);
+ fz_var(p7);
+ fz_var(f);
+
+ fz_try(ctx)
+ {
+ unsigned char *p7_ptr;
+ int p7_len;
+ int i;
+
+ brange = fz_calloc(ctx, brange_len, sizeof(*brange));
+ for (i = 0; i < brange_len; i++)
+ {
+ brange[i][0] = pdf_to_int(pdf_array_get(byte_range, 2*i));
+ brange[i][1] = pdf_to_int(pdf_array_get(byte_range, 2*i+1));
+ }
+
+ bdata = BIO_new(BIO_s_file());
+ if (bdata == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create file BIO");
+ BIO_read_filename(bdata, filename);
+
+ bsegs = BIO_new(BIO_f_segments());
+ if (bsegs == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create segment filter");
+
+ bsegs->next_bio = bdata;
+ BIO_set_segments(bsegs, brange, brange_len);
+
+ p7 = PKCS7_new();
+ if (p7 == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create p7 object");
+
+ PKCS7_set_type(p7, NID_pkcs7_signed);
+ si = PKCS7_add_signature(p7, signer->x509, signer->pkey, EVP_sha1());
+ if (si == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to add signature");
+
+ PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data));
+ PKCS7_add_certificate(p7, signer->x509);
+
+ PKCS7_content_new(p7, NID_pkcs7_data);
+ PKCS7_set_detached(p7, 1);
+
+ bp7in = PKCS7_dataInit(p7, NULL);
+ if (bp7in == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to write to digest");
+
+ while(1)
+ {
+ char buf[4096];
+ int n = BIO_read(bsegs, buf, sizeof(buf));
+ if (n <= 0)
+ break;
+ BIO_write(bp7in, buf, n);
+ }
+
+ if (!PKCS7_dataFinal(p7, bp7in))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to write to digest");
+
+ BIO_free(bsegs);
+ bsegs = NULL;
+ BIO_free(bdata);
+ bdata = NULL;
+
+ bp7 = BIO_new(BIO_s_mem());
+ if (bp7 == NULL || !i2d_PKCS7_bio(bp7, p7))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create memory buffer for digest");
+
+ p7_len = BIO_get_mem_data(bp7, &p7_ptr);
+ if (p7_len*2 + 2 > digest_length)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Insufficient space for digest");
+
+ f = fopen(filename, "rb+");
+ if (f == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to write digest");
+
+ fseek(f, digest_offset+1, SEEK_SET);
+
+ for (i = 0; i < p7_len; i++)
+ fprintf(f, "%02x", p7_ptr[i]);
+ }
+ fz_always(ctx)
+ {
+ PKCS7_free(p7);
+ BIO_free(bsegs);
+ BIO_free(bdata);
+ BIO_free(bp7in);
+ BIO_free(bp7);
+ if (f)
+ fclose(f);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
int pdf_check_signature(pdf_document *doc, pdf_widget *widget, char *file, char *ebuf, int ebufsize)
{
fz_context *ctx = doc->ctx;
@@ -387,6 +719,53 @@ int pdf_check_signature(pdf_document *doc, pdf_widget *widget, char *file, char
return res;
}
+void pdf_sign_signature(pdf_document *doc, pdf_widget *widget, char *sigfile, char *password)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_signer *signer = pdf_read_pfx(ctx, sigfile, password);
+ pdf_designated_name *dn = NULL;
+ fz_buffer *fzbuf = NULL;
+
+ fz_try(ctx)
+ {
+ unsigned char *dn_str;
+ int first = 1;
+
+ pdf_signature_set_value(doc, ((pdf_annot *)widget)->obj, signer);
+ dn = pdf_signer_designated_name(signer);
+ fzbuf = fz_new_buffer(ctx, 256);
+ if (!dn->cn)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Certificat has no common name");
+
+ fz_buffer_printf(ctx, fzbuf, "cn=%s", dn->cn);
+
+ if (dn->o)
+ fz_buffer_printf(ctx, fzbuf, ", o=%s", dn->o);
+
+ if (dn->ou)
+ fz_buffer_printf(ctx, fzbuf, ", ou=%s", dn->ou);
+
+ if (dn->email)
+ fz_buffer_printf(ctx, fzbuf, ", email=%s", dn->email);
+
+ if (dn->c)
+ fz_buffer_printf(ctx, fzbuf, ", c=%s", dn->c);
+
+ (void)fz_buffer_storage(ctx, fzbuf, &dn_str);
+ pdf_set_signature_appearance(doc, (pdf_annot *)widget, dn->cn, dn_str, NULL);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_signer(signer);
+ pdf_free_designated_name(dn);
+ fz_drop_buffer(ctx, fzbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
#else /* HAVE_OPENSSL */
int pdf_check_signature(pdf_document *doc, pdf_widget *widget, char *file, char *ebuf, int ebufsize)
@@ -395,4 +774,17 @@ int pdf_check_signature(pdf_document *doc, pdf_widget *widget, char *file, char
return 0;
}
+pdf_signer *pdf_keep_signer(pdf_signer *signer)
+{
+ return NULL;
+}
+
+void pdf_drop_signer(pdf_signer *signer)
+{
+}
+
+void pdf_write_digest(pdf_document *doc, char *filename, pdf_obj *byte_range, int digest_offset, int digest_length, pdf_signer *signer)
+{
+}
+
#endif /* HAVE_OPENSSL */