summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAna Gelez <ana@gelez.xyz>2024-08-08 15:11:15 +0200
committerAna Gelez <ana@gelez.xyz>2024-08-08 15:11:15 +0200
commit28905fedc1e994bfd2ec16e50711639aa1247c28 (patch)
tree78ce92dfca57b9273bb940b7b5f41dd4d73b2156
parentc4dd6fa06227b8c4a04cf488f371f52d5379ef9b (diff)
WIP: digital signatures
-rw-r--r--Cargo.lock369
-rw-r--r--Cargo.toml9
-rw-r--r--crates/typst-pdf/Cargo.toml7
-rw-r--r--crates/typst-pdf/src/catalog.rs17
-rw-r--r--crates/typst-pdf/src/lib.rs6
-rw-r--r--crates/typst-pdf/src/signature.rs168
6 files changed, 565 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4c2e4d38..7dddab6a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,6 +9,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -142,6 +153,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
name = "biblatex"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -206,6 +223,24 @@ dependencies = [
]
[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -230,6 +265,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "cc"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -304,6 +348,16 @@ dependencies = [
]
[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
name = "citationberg"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -373,6 +427,18 @@ dependencies = [
]
[[package]]
+name = "cms"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730"
+dependencies = [
+ "const-oid",
+ "der",
+ "spki",
+ "x509-cert",
+]
+
+[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -424,6 +490,12 @@ dependencies = [
]
[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -449,6 +521,15 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -498,6 +579,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -525,6 +616,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "der_derive",
+ "flagset",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "der_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -545,6 +660,18 @@ dependencies = [
]
[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -702,6 +829,12 @@ dependencies = [
]
[[package]]
+name = "flagset"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
+
+[[package]]
name = "flate2"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -792,6 +925,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -874,6 +1017,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
name = "hypher"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1137,6 +1289,16 @@ dependencies = [
]
[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1240,6 +1402,15 @@ dependencies = [
]
[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
+
+[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1450,6 +1621,23 @@ dependencies = [
]
[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1476,12 +1664,24 @@ dependencies = [
]
[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
@@ -1648,10 +1848,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
+[[package]]
name = "pdf-writer"
version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af6a7882fda7808481d43c51cadfc3ec934c6af72612a1fe6985ce329a2f0469"
dependencies = [
"bitflags 2.6.0",
"itoa",
@@ -1660,6 +1868,15 @@ dependencies = [
]
[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1723,6 +1940,44 @@ dependencies = [
]
[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs5"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+dependencies = [
+ "aes",
+ "cbc",
+ "der",
+ "pbkdf2",
+ "scrypt",
+ "sha2",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "pkcs5",
+ "rand_core",
+ "spki",
+]
+
+[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1859,6 +2114,8 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
+ "libc",
+ "rand_chacha",
"rand_core",
]
@@ -1877,6 +2134,9 @@ name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
[[package]]
name = "rayon"
@@ -2000,6 +2260,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
+name = "rsa"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2056,6 +2336,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2080,6 +2369,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "pbkdf2",
+ "salsa20",
+ "sha2",
+]
+
+[[package]]
name = "security-framework"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2185,12 +2485,33 @@ dependencies = [
]
[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core",
+]
+
+[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2239,6 +2560,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2311,6 +2642,12 @@ version = "0.11.0"
source = "git+https://github.com/typst/subsetter?rev=4e0058b#4e0058b4b9a0948a5f79894111948d95e59ba350"
[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
name = "svg2pdf"
version = "0.11.0"
source = "git+https://github.com/typst/svg2pdf?rev=39f8ad3#39f8ad3b35e14cfcabf3d5d916899f7ac78790f7"
@@ -2589,6 +2926,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
name = "typst"
version = "0.11.0"
dependencies = [
@@ -2777,6 +3120,7 @@ dependencies = [
"arrayvec",
"base64",
"bytemuck",
+ "cms",
"comemo",
"ecow",
"image",
@@ -2784,6 +3128,10 @@ dependencies = [
"miniz_oxide",
"once_cell",
"pdf-writer",
+ "pkcs8",
+ "rand",
+ "rsa",
+ "sha2",
"subsetter",
"svg2pdf",
"ttf-parser",
@@ -3407,6 +3755,17 @@ dependencies = [
]
[[package]]
+name = "x509-cert"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
+dependencies = [
+ "const-oid",
+ "der",
+ "spki",
+]
+
+[[package]]
name = "xattr"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3529,6 +3888,12 @@ dependencies = [
]
[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
name = "zerotrie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 773544eb..474e56c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,6 +39,7 @@ ciborium = "0.2.1"
clap = { version = "4.4", features = ["derive", "env"] }
clap_complete = "4.2.1"
clap_mangen = "0.2.10"
+cms = "0.2.3"
ctrlc = "3.4.1"
codespan-reporting = "0.11"
comemo = "0.4"
@@ -78,16 +79,19 @@ pathdiff = "0.2"
pdf-writer = "0.10.0"
phf = { version = "0.11", features = ["macros"] }
pixglyph = "0.4"
+pkcs8 = { version = "0.10.2", features = ["pkcs5", "encryption"] }
png = "0.17"
portable-atomic = "1.6"
proc-macro2 = "1"
pulldown-cmark = "0.9"
quote = "1"
qcms = "0.3.0"
+rand = "0.8.5"
rayon = "1.7.0"
regex = "1"
resvg = { version = "0.42", default-features = false, features = ["raster-images"] }
roxmltree = "0.20"
+rsa = "0.9.6"
rustybuzz = "0.14"
same-file = "1"
self-replace = "1.3.7"
@@ -95,6 +99,7 @@ semver = "1"
serde = { version = "1.0.184", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
+sha2 = { version = "0.10.8", featurs = ["oid"] }
shell-escape = "0.1.5"
siphasher = "1"
smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] }
@@ -126,6 +131,7 @@ xmlparser = "0.13.5"
xmlwriter = "0.1.0"
xmp-writer = "0.2"
xz2 = { version = "0.1", features = ["static"] }
+x509-cert = "0.2.5"
yaml-front-matter = "0.1"
zip = { version = "2", default-features = false, features = ["deflate"] }
@@ -142,3 +148,6 @@ strip = true
[workspace.lints.clippy]
uninlined_format_args = "warn"
blocks_in_conditions = "allow"
+
+[patch.crates-io]
+pdf-writer = { path = "../pdf-writer" }
diff --git a/crates/typst-pdf/Cargo.toml b/crates/typst-pdf/Cargo.toml
index cdd65e82..8c53493b 100644
--- a/crates/typst-pdf/Cargo.toml
+++ b/crates/typst-pdf/Cargo.toml
@@ -17,8 +17,10 @@ typst = { workspace = true }
typst-assets = { workspace = true }
typst-macros = { workspace = true }
typst-timing = { workspace = true }
+arrayvec = { workspace = true }
base64 = { workspace = true }
bytemuck = { workspace = true }
+cms = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
image = { workspace = true }
@@ -26,7 +28,10 @@ indexmap = { workspace = true }
miniz_oxide = { workspace = true }
once_cell = { workspace = true }
pdf-writer = { workspace = true }
-arrayvec = { workspace = true }
+pkcs8 = { workspace = true, features = ["pkcs5", "encryption"] }
+rand = { workspace = true }
+rsa = { workspace = true }
+sha2 = { workspace = true, features = ["oid"] }
subsetter = { workspace = true }
svg2pdf = { workspace = true }
ttf-parser = { workspace = true }
diff --git a/crates/typst-pdf/src/catalog.rs b/crates/typst-pdf/src/catalog.rs
index 7d52cc58..517d9ee1 100644
--- a/crates/typst-pdf/src/catalog.rs
+++ b/crates/typst-pdf/src/catalog.rs
@@ -10,17 +10,17 @@ use typst::foundations::{Datetime, Smart};
use typst::layout::Dir;
use typst::text::Lang;
-use crate::WithEverything;
use crate::{hash_base64, outline, page::PdfPageLabel};
+use crate::{signature, WithEverything};
/// Write the document catalog.
pub fn write_catalog(
ctx: WithEverything,
ident: Smart<&str>,
timestamp: Option<Datetime>,
- pdf: &mut Pdf,
+ mut pdf: Pdf,
alloc: &mut Ref,
-) {
+) -> Vec<u8> {
let lang = ctx
.resources
.languages
@@ -35,10 +35,10 @@ pub fn write_catalog(
};
// Write the outline tree.
- let outline_root_id = outline::write_outline(pdf, alloc, &ctx);
+ let outline_root_id = outline::write_outline(&mut pdf, alloc, &ctx);
// Write the page labels.
- let page_labels = write_page_labels(pdf, alloc, &ctx);
+ let page_labels = write_page_labels(&mut pdf, alloc, &ctx);
// Write the document information.
let info_ref = alloc.bump();
@@ -132,6 +132,9 @@ pub fn write_catalog(
.pair(Name(b"Type"), Name(b"Metadata"))
.pair(Name(b"Subtype"), Name(b"XML"));
+ // Prepare digital signatures
+ let (signature_range, signature_form_ref) = signature::prepare(alloc, &mut pdf);
+
// Write the document catalog.
let catalog_ref = alloc.bump();
let mut catalog = pdf.catalog(catalog_ref);
@@ -167,7 +170,11 @@ pub fn write_catalog(
catalog.lang(TextStr(lang.as_str()));
}
+ catalog.insert(Name(b"AcroForm")).primitive(signature_form_ref);
+
catalog.finish();
+
+ signature::write(signature_range, pdf.finish())
}
/// Write the page labels.
diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs
index 9af830bd..ea8f6648 100644
--- a/crates/typst-pdf/src/lib.rs
+++ b/crates/typst-pdf/src/lib.rs
@@ -13,6 +13,7 @@ mod outline;
mod page;
mod pattern;
mod resources;
+mod signature;
use std::collections::HashMap;
use std::hash::Hash;
@@ -348,10 +349,9 @@ impl<S> PdfBuilder<S> {
process: P,
) -> Vec<u8>
where
- P: Fn(S, Smart<&str>, Option<Datetime>, &mut Pdf, &mut Ref),
+ P: Fn(S, Smart<&str>, Option<Datetime>, Pdf, &mut Ref) -> Vec<u8>,
{
- process(self.state, ident, timestamp, &mut self.pdf, &mut self.alloc);
- self.pdf.finish()
+ process(self.state, ident, timestamp, self.pdf, &mut self.alloc)
}
}
diff --git a/crates/typst-pdf/src/signature.rs b/crates/typst-pdf/src/signature.rs
new file mode 100644
index 00000000..92c2c674
--- /dev/null
+++ b/crates/typst-pdf/src/signature.rs
@@ -0,0 +1,168 @@
+use pkcs8::DecodePrivateKey;
+use sha2::{
+ digest::const_oid::db::rfc5912::{ID_SHA_512, SHA_512_WITH_RSA_ENCRYPTION},
+ Digest,
+};
+use std::ops::Range;
+
+use cms::{
+ cert::{
+ x509::{
+ der::{
+ asn1::{OctetString, SetOfVec},
+ oid::db::rfc5911::{ID_DATA, ID_SIGNED_DATA},
+ Any, AnyRef, Encode,
+ },
+ spki::AlgorithmIdentifier,
+ Certificate,
+ },
+ CertificateChoices, IssuerAndSerialNumber,
+ },
+ content_info::{CmsVersion, ContentInfo},
+ signed_data::{
+ CertificateSet, EncapsulatedContentInfo, SignedData, SignerIdentifier,
+ SignerInfo, SignerInfos,
+ },
+};
+use pdf_writer::{
+ types::{FieldType, SigFlags},
+ writers::{Field, Form},
+ Finish, Name, Pdf, Primitive, Ref, Str,
+};
+use rsa::{traits::SignatureScheme, Pkcs1v15Sign, RsaPrivateKey};
+use sha2::Sha512;
+
+const SIG_SIZE: usize = 1024 * 4;
+
+pub fn prepare(alloc: &mut Ref, pdf: &mut Pdf) -> (Range<usize>, Ref) {
+ let form_ref = alloc.bump();
+ let signature_field_ref = alloc.bump();
+
+ let mut signature_field: Field = pdf.indirect(signature_field_ref).start();
+ signature_field.field_type(FieldType::Signature);
+ let mut signature_dict = signature_field.insert(Name(b"V")).dict();
+ signature_dict.pair(Name(b"Type"), Name(b"Sig"));
+ signature_dict.pair(Name(b"Filter"), Name(b"Adobe.PPKLite"));
+ signature_dict.pair(Name(b"SubFilter"), Name(b"adbe.pkcs7.detached"));
+ let mut placeholder = [0; SIG_SIZE];
+ placeholder[0] = 255; // Make sure pdf-writer writes this array as binary
+ let sig_end = signature_dict
+ .pair(Name(b"Contents"), Str(&placeholder))
+ .current_len();
+ let sig_start = sig_end
+ - SIG_SIZE * 2 // 2 chars to write each byte
+ - 2; // take < and > into account;
+ signature_dict
+ .insert(Name(b"ByteRange"))
+ .array()
+ .items([0, sig_start as i32, sig_end as i32])
+ .item(Str(b"typst-document-size"));
+ signature_dict.finish();
+ signature_field.finish();
+
+ let mut form: Form = pdf.indirect(form_ref).start();
+ form.fields([signature_field_ref]);
+ form.sig_flags(SigFlags::SIGNATURES_EXIST);
+
+ (sig_start..sig_end, form_ref)
+}
+
+pub fn write(range: Range<usize>, mut bytes: Vec<u8>) -> Vec<u8> {
+ let needle = b"(typst-document-size)";
+ let doc_size_start = bytes[range.end..]
+ .windows(needle.len())
+ .position(|x| x == needle)
+ .unwrap();
+ let doc_size_range = doc_size_start..(doc_size_start + needle.len());
+ dbg!(&range, &doc_size_range);
+ let mut actual_size = Vec::new();
+ <i32 as pdf_writer::Primitive>::write(bytes.len() as i32, &mut actual_size);
+ actual_size.extend(std::iter::repeat(b' ').take(needle.len() - actual_size.len()));
+ bytes.splice(
+ doc_size_range.start + range.end..doc_size_range.end + range.end,
+ actual_size,
+ );
+
+ let mut hasher = Sha512::new();
+ hasher.update(&bytes[0..range.start]);
+ hasher.update(&bytes[range.end..]);
+ let hashed = hasher.finalize();
+
+ let priv_key =
+ RsaPrivateKey::from_pkcs8_encrypted_pem(include_str!("../../../key.pem"), "abcd")
+ .unwrap();
+ let signer = Pkcs1v15Sign::new::<Sha512>();
+ let signature =
+ signer.sign(Some(&mut rand::rngs::OsRng), &priv_key, &hashed).unwrap();
+
+ let pem_chain =
+ Certificate::load_pem_chain(include_bytes!("../../../cert.pem")).unwrap();
+ let sig_data = ContentInfo {
+ content_type: ID_SIGNED_DATA,
+ content: Any::from(
+ AnyRef::try_from(
+ SignedData {
+ version: CmsVersion::V0,
+ digest_algorithms: SetOfVec::try_from(vec![AlgorithmIdentifier {
+ oid: SHA_512_WITH_RSA_ENCRYPTION,
+ parameters: None,
+ }])
+ .unwrap(),
+ encap_content_info: EncapsulatedContentInfo {
+ econtent_type: ID_DATA,
+ econtent: None,
+ },
+ certificates: Some(CertificateSet(
+ SetOfVec::from_iter(
+ pem_chain
+ .clone()
+ .into_iter()
+ .map(CertificateChoices::Certificate),
+ )
+ .unwrap(),
+ )),
+ crls: None,
+ signer_infos: SignerInfos(
+ SetOfVec::from_iter(pem_chain.into_iter().map(|cert| {
+ SignerInfo {
+ version: CmsVersion::V1,
+ sid: SignerIdentifier::IssuerAndSerialNumber(
+ IssuerAndSerialNumber {
+ issuer: cert.tbs_certificate.issuer,
+ serial_number: cert.tbs_certificate.serial_number,
+ },
+ ),
+ digest_alg: AlgorithmIdentifier {
+ oid: ID_SHA_512,
+ parameters: None,
+ },
+ signed_attrs: None, // TODO: should contain revocation information (see section 12.8.3.3.2)
+ signature_algorithm: AlgorithmIdentifier {
+ oid: SHA_512_WITH_RSA_ENCRYPTION,
+ parameters: None,
+ },
+ signature: OctetString::new(&signature[..]).unwrap(),
+ unsigned_attrs: None, // TODO: should contain timestamp
+ }
+ }))
+ .unwrap(),
+ ),
+ }
+ .to_der()
+ .unwrap()
+ .as_slice(),
+ )
+ .unwrap(),
+ ),
+ };
+ let mut sig = sig_data.to_der().unwrap();
+ // pad with 0 to keep the ranges correct
+ sig.extend(std::iter::repeat(0).take(SIG_SIZE - sig.len()));
+ let mut encoded_sig = Vec::with_capacity(sig.len() * 2);
+ Str(&sig).write(&mut encoded_sig);
+
+ dbg!(range.len(), encoded_sig.len());
+ bytes.splice(range, encoded_sig);
+
+ bytes
+}