Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
V
Vereign Client Library
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Code
Vereign Client Library
Commits
2ee83d93
Commit
2ee83d93
authored
6 years ago
by
Damyan Mitev
Committed by
Sasha Ilieva
6 years ago
Browse files
Options
Downloads
Patches
Plain Diff
Revert "Make ES Lint happy"
This reverts commit
bc806cba
.
parent
2d1ecb82
Branches
Branches containing commit
Tags
Tags containing commit
3 merge requests
!48
269 implement workflow for signing already uploaded document dev
,
!47
269 implement workflow for signing already uploaded document
,
!41
268 convert odt file to pdf
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
javascript/src/utilities/pdfUtilities.js
+569
-574
569 additions, 574 deletions
javascript/src/utilities/pdfUtilities.js
with
569 additions
and
574 deletions
javascript/src/utilities/pdfUtilities.js
+
569
−
574
View file @
2ee83d93
...
...
@@ -9,362 +9,367 @@
*/
import
{
ObjectIdentifier
,
UTCTime
,
OctetString
ObjectIdentifier
,
UTCTime
,
OctetString
}
from
'
asn1js
'
;
import
{
ContentInfo
,
SignedData
,
Attribute
,
SignerInfo
,
IssuerAndSerialNumber
,
SignedAndUnsignedAttributes
,
EncapsulatedContentInfo
,
getCrypto
ContentInfo
,
SignedData
,
Attribute
,
SignerInfo
,
IssuerAndSerialNumber
,
SignedAndUnsignedAttributes
,
EncapsulatedContentInfo
,
getCrypto
}
from
'
pkijs
'
;
import
{
parseCertificate
,
parsePrivateKey
parseCertificate
,
parsePrivateKey
,
}
from
'
./signingUtilities
'
;
//keep this import to include the patched version of pdfjs library
import
{
PDFJS
}
from
"
../lib/pdfjs.parser.js
"
;
function
createXrefTable
(
xrefEntries
)
{
xrefEntries
=
sortOnKeys
(
xrefEntries
);
let
retVal
=
'
xref
\n
'
;
let
last
=
-
2
;
for
(
let
i
in
xrefEntries
)
{
i
=
parseInt
(
i
);
if
(
typeof
xrefEntries
[
i
].
offset
===
'
undefined
'
)
{
continue
;
}
retVal
+=
calcFlow
(
i
,
last
,
xrefEntries
);
const
offset
=
xrefEntries
[
i
].
offset
;
retVal
+=
pad10
(
offset
)
+
'
'
+
pad5
(
xrefEntries
[
i
].
gen
)
+
'
'
+
(
xrefEntries
[
i
].
free
?
'
f
'
:
'
n
'
)
+
'
\n
'
;
last
=
i
;
}
return
retVal
;
xrefEntries
=
sortOnKeys
(
xrefEntries
);
var
retVal
=
'
xref
\n
'
;
var
last
=
-
2
;
for
(
var
i
in
xrefEntries
)
{
i
=
parseInt
(
i
);
if
(
typeof
xrefEntries
[
i
].
offset
===
'
undefined
'
)
{
continue
;
}
retVal
+=
calcFlow
(
i
,
last
,
xrefEntries
);
var
offset
=
xrefEntries
[
i
].
offset
;
retVal
+=
pad10
(
offset
)
+
'
'
+
pad5
(
xrefEntries
[
i
].
gen
)
+
'
'
+
(
xrefEntries
[
i
].
free
?
'
f
'
:
'
n
'
)
+
'
\n
'
;
last
=
i
;
}
return
retVal
;
}
function
calcFlow
(
i
,
last
,
xrefEntries
)
{
if
(
last
+
1
===
i
)
{
return
''
;
}
let
count
=
1
;
while
(
typeof
xrefEntries
[
i
+
count
]
!==
'
undefined
'
&&
typeof
xrefEntries
[
i
+
count
].
offset
!==
'
undefined
'
)
{
count
++
;
}
return
i
+
'
'
+
count
+
'
\n
'
;
if
(
last
+
1
===
i
)
{
return
''
;}
var
count
=
1
;
while
(
typeof
xrefEntries
[
(
i
+
count
)
]
!==
'
undefined
'
&&
typeof
xrefEntries
[
(
i
+
count
)
].
offset
!==
'
undefined
'
)
{
count
++
;}
return
i
+
'
'
+
count
+
'
\n
'
;
}
function
createTrailer
(
topDict
,
startxref
,
sha256Hex
,
size
,
prev
)
{
let
retVal
=
'
trailer <<
\n
'
;
retVal
+=
'
/Size
'
+
size
+
'
\n
'
;
const
refRoot
=
topDict
.
getRaw
(
'
Root
'
);
if
(
typeof
refRoot
!==
'
undefined
'
)
{
retVal
+=
'
/Root
'
+
refRoot
.
num
+
'
'
+
refRoot
.
gen
+
'
R
\n
'
;
}
const
refInfo
=
topDict
.
getRaw
(
'
Info
'
);
if
(
typeof
refInfo
!==
'
undefined
'
)
{
retVal
+=
'
/Info
'
+
refInfo
.
num
+
'
'
+
refInfo
.
gen
+
'
R
\n
'
;
}
retVal
+=
'
/ID [<
'
+
sha256Hex
.
substring
(
0
,
32
)
+
'
><
'
+
sha256Hex
.
substring
(
32
,
64
)
+
'
>]
\n
'
;
if
(
typeof
prev
!==
'
undefined
'
)
{
retVal
+=
'
/Prev
'
+
prev
+
'
\n
'
;
}
retVal
+=
'
>>
\n
'
;
retVal
+=
'
startxref
\n
'
;
retVal
+=
startxref
+
'
\n
'
;
retVal
+=
'
%%EOF
\n
'
;
return
retVal
;
var
retVal
=
'
trailer <<
\n
'
;
retVal
+=
'
/Size
'
+
(
size
)
+
'
\n
'
;
var
refRoot
=
topDict
.
getRaw
(
'
Root
'
);
if
(
typeof
refRoot
!==
'
undefined
'
)
{
retVal
+=
'
/Root
'
+
refRoot
.
num
+
'
'
+
refRoot
.
gen
+
'
R
\n
'
;
}
var
refInfo
=
topDict
.
getRaw
(
'
Info
'
);
if
(
typeof
refInfo
!==
'
undefined
'
)
{
retVal
+=
'
/Info
'
+
refInfo
.
num
+
'
'
+
refInfo
.
gen
+
'
R
\n
'
;
}
retVal
+=
'
/ID [<
'
+
sha256Hex
.
substring
(
0
,
32
)
+
'
><
'
+
sha256Hex
.
substring
(
32
,
64
)
+
'
>]
\n
'
;
if
(
typeof
prev
!==
'
undefined
'
)
{
retVal
+=
'
/Prev
'
+
prev
+
'
\n
'
;
}
retVal
+=
'
>>
\n
'
;
retVal
+=
'
startxref
\n
'
;
retVal
+=
startxref
+
'
\n
'
;
retVal
+=
'
%%EOF
\n
'
;
return
retVal
;
}
function
createXrefTableAppend
(
xrefEntries
)
{
xrefEntries
=
sortOnKeys
(
xrefEntries
);
let
retVal
=
'
xref
\n
'
;
let
last
=
-
2
;
for
(
let
i
in
xrefEntries
)
{
i
=
parseInt
(
i
);
if
(
typeof
xrefEntries
[
i
].
offset
===
'
undefined
'
)
{
continue
;
}
retVal
+=
calcFlow
(
i
,
last
,
xrefEntries
);
const
offset
=
xrefEntries
[
i
].
offset
;
retVal
+=
pad10
(
offset
)
+
'
'
+
pad5
(
xrefEntries
[
i
].
gen
)
+
'
'
+
(
xrefEntries
[
i
].
free
?
'
f
'
:
'
n
'
)
+
'
\n
'
;
last
=
i
;
}
return
retVal
;
xrefEntries
=
sortOnKeys
(
xrefEntries
);
var
retVal
=
'
xref
\n
'
;
var
last
=
-
2
;
for
(
var
i
in
xrefEntries
)
{
i
=
parseInt
(
i
);
if
(
typeof
xrefEntries
[
i
].
offset
===
'
undefined
'
)
{
continue
;
}
retVal
+=
calcFlow
(
i
,
last
,
xrefEntries
);
var
offset
=
xrefEntries
[
i
].
offset
;
retVal
+=
pad10
(
offset
)
+
'
'
+
pad5
(
xrefEntries
[
i
].
gen
)
+
'
'
+
(
xrefEntries
[
i
].
free
?
'
f
'
:
'
n
'
)
+
'
\n
'
;
last
=
i
;
}
return
retVal
;
}
//http://stackoverflow.com/questions/10946880/sort-a-dictionary-or-whatever-key-value-data-structure-in-js-on-word-number-ke
function
sortOnKeys
(
dict
)
{
const
sorted
=
[];
for
(
const
key
in
dict
)
{
sorted
[
sorted
.
length
]
=
key
;
}
sorted
.
sort
();
var
sorted
=
[];
for
(
var
key
in
dict
)
{
sorted
[
sorted
.
length
]
=
key
;
}
sorted
.
sort
();
const
tempDict
=
{};
for
(
let
i
=
0
;
i
<
sorted
.
length
;
i
++
)
{
tempDict
[
sorted
[
i
]]
=
dict
[
sorted
[
i
]];
}
var
tempDict
=
{};
for
(
var
i
=
0
;
i
<
sorted
.
length
;
i
++
)
{
tempDict
[
sorted
[
i
]]
=
dict
[
sorted
[
i
]];
}
return
tempDict
;
return
tempDict
;
}
function
removeFromArray
(
array
,
from
,
to
)
{
const
cutlen
=
to
-
from
;
const
buf
=
new
Uint8Array
(
array
.
length
-
cutlen
);
var
cutlen
=
to
-
from
;
var
buf
=
new
Uint8Array
(
array
.
length
-
cutlen
);
for
(
let
i
=
0
;
i
<
from
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
for
(
let
i
=
to
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
buf
[
i
-
cutlen
]
=
array
[
i
];
}
return
buf
;
for
(
var
i
=
0
;
i
<
from
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
for
(
var
i
=
to
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
buf
[
i
-
cutlen
]
=
array
[
i
];
}
return
buf
;
}
function
findXrefBlocks
(
xrefBlocks
)
{
const
num
=
xrefBlocks
.
length
/
2
;
const
retVal
=
[];
for
(
let
i
=
0
;
i
<
num
;
i
++
)
{
retVal
.
push
({
start
:
xrefBlocks
[
i
],
end
:
xrefBlocks
[
i
+
num
]});
}
return
retVal
;
var
num
=
xrefBlocks
.
length
/
2
;
var
retVal
=
[];
for
(
var
i
=
0
;
i
<
num
;
i
++
)
{
retVal
.
push
({
start
:
xrefBlocks
[
i
],
end
:
xrefBlocks
[
i
+
num
]});
}
return
retVal
;
}
function
convertUint8ArrayToBinaryString
(
u8Array
)
{
let
i
,
len
=
u8Array
.
length
,
b
S
tr
=
""
;
for
(
i
=
0
;
i
<
len
;
i
++
)
{
b
S
tr
+=
String
.
fromCharCode
(
u8Array
[
i
]);
}
return
b
S
tr
;
var
i
,
len
=
u8Array
.
length
,
b
_s
tr
=
""
;
for
(
i
=
0
;
i
<
len
;
i
++
)
{
b
_s
tr
+=
String
.
fromCharCode
(
u8Array
[
i
]);
}
return
b
_s
tr
;
}
function
arrayObjectIndexOf
(
array
,
start
,
end
,
orig
)
{
for
(
let
i
=
0
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
if
((
array
[
i
].
start
===
start
)
&&
(
array
[
i
].
end
===
end
)
&&
(
array
[
i
].
orig
===
orig
))
{
return
i
;
for
(
var
i
=
0
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
if
((
array
[
i
].
start
===
start
)
&&
(
array
[
i
].
end
===
end
)
&&
(
array
[
i
].
orig
===
orig
))
{
return
i
;
}
}
}
return
-
1
;
return
-
1
;
}
function
pad10
(
num
)
{
const
s
=
"
000000000
"
+
num
;
return
s
.
substr
(
s
.
length
-
10
);
var
s
=
"
000000000
"
+
num
;
return
s
.
substr
(
s
.
length
-
10
);
}
function
pad5
(
num
)
{
const
s
=
"
0000
"
+
num
;
return
s
.
substr
(
s
.
length
-
5
);
var
s
=
"
0000
"
+
num
;
return
s
.
substr
(
s
.
length
-
5
);
}
function
pad2
(
num
)
{
const
s
=
"
0
"
+
num
;
return
s
.
substr
(
s
.
length
-
2
);
var
s
=
"
0
"
+
num
;
return
s
.
substr
(
s
.
length
-
2
);
}
function
findRootEntry
(
xref
)
{
const
rootNr
=
xref
.
root
.
objId
.
substring
(
0
,
xref
.
root
.
objId
.
length
-
1
);
return
xref
.
entries
[
rootNr
];
var
rootNr
=
xref
.
root
.
objId
.
substring
(
0
,
xref
.
root
.
objId
.
length
-
1
);
return
xref
.
entries
[
rootNr
];
}
function
findSuccessorEntry
(
xrefEntries
,
current
)
{
//find it first
const
currentOffset
=
current
.
offset
;
let
currentMin
=
Number
.
MAX_SAFE_INTEGER
;
let
currentMinIndex
=
-
1
;
for
(
const
i
in
xrefEntries
)
{
if
(
xrefEntries
[
i
].
offset
>
currentOffset
)
{
if
(
xrefEntries
[
i
].
offset
<
currentMin
)
{
currentMin
=
xrefEntries
[
i
].
offset
;
currentMinIndex
=
i
;
}
}
}
if
(
currentMinIndex
===
-
1
)
{
return
current
;
}
return
xrefEntries
[
currentMinIndex
];
//find it first
var
currentOffset
=
current
.
offset
;
var
currentMin
=
Number
.
MAX_SAFE_INTEGER
;
var
currentMinIndex
=
-
1
;
for
(
var
i
in
xrefEntries
)
{
if
(
xrefEntries
[
i
].
offset
>
currentOffset
)
{
if
(
xrefEntries
[
i
].
offset
<
currentMin
)
{
currentMin
=
xrefEntries
[
i
].
offset
;
currentMinIndex
=
i
;
}
}
}
if
(
currentMinIndex
===
-
1
)
{
return
current
;
}
return
xrefEntries
[
currentMinIndex
];
}
function
updateArray
(
array
,
pos
,
str
)
{
const
upd
=
stringToUint8Array
(
str
);
for
(
let
i
=
0
,
len
=
upd
.
length
;
i
<
len
;
i
++
)
{
array
[
i
+
pos
]
=
upd
[
i
];
}
return
array
;
var
upd
=
stringToUint8Array
(
str
);
for
(
var
i
=
0
,
len
=
upd
.
length
;
i
<
len
;
i
++
)
{
array
[
i
+
pos
]
=
upd
[
i
];
}
return
array
;
}
function
copyToEnd
(
array
,
from
,
to
)
{
const
buf
=
new
Uint8Array
(
array
.
length
+
(
to
-
from
));
for
(
let
i
=
0
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
var
buf
=
new
Uint8Array
(
array
.
length
+
(
to
-
from
));
for
(
var
i
=
0
,
len
=
array
.
length
;
i
<
len
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
for
(
let
i
=
0
,
len
=
to
-
from
;
i
<
len
;
i
++
)
{
buf
[
array
.
length
+
i
]
=
array
[
from
+
i
];
}
return
buf
;
for
(
var
i
=
0
,
len
=
(
to
-
from
)
;
i
<
len
;
i
++
)
{
buf
[
array
.
length
+
i
]
=
array
[
from
+
i
];
}
return
buf
;
}
function
insertIntoArray
(
array
,
pos
,
str
)
{
const
ins
=
stringToUint8Array
(
str
);
const
buf
=
new
Uint8Array
(
array
.
length
+
ins
.
length
);
for
(
let
i
=
0
;
i
<
pos
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
for
(
let
i
=
0
;
i
<
ins
.
length
;
i
++
)
{
buf
[
pos
+
i
]
=
ins
[
i
];
}
for
(
let
i
=
pos
;
i
<
array
.
length
;
i
++
)
{
buf
[
ins
.
length
+
i
]
=
array
[
i
];
}
return
buf
;
var
ins
=
stringToUint8Array
(
str
);
var
buf
=
new
Uint8Array
(
array
.
length
+
ins
.
length
);
for
(
var
i
=
0
;
i
<
pos
;
i
++
)
{
buf
[
i
]
=
array
[
i
];
}
for
(
var
i
=
0
;
i
<
ins
.
length
;
i
++
)
{
buf
[
pos
+
i
]
=
ins
[
i
];
}
for
(
var
i
=
pos
;
i
<
array
.
length
;
i
++
)
{
buf
[
ins
.
length
+
i
]
=
array
[
i
];
}
return
buf
;
}
function
stringToUint8Array
(
str
)
{
const
buf
=
new
Uint8Array
(
str
.
length
);
for
(
let
i
=
0
,
strLen
=
str
.
length
;
i
<
strLen
;
i
++
)
{
buf
[
i
]
=
str
.
charCodeAt
(
i
);
}
return
buf
;
var
buf
=
new
Uint8Array
(
str
.
length
);
for
(
var
i
=
0
,
strLen
=
str
.
length
;
i
<
strLen
;
i
++
)
{
buf
[
i
]
=
str
.
charCodeAt
(
i
);
}
return
buf
;
}
function
uint8ArrayToString
(
buf
,
from
,
to
)
{
if
(
typeof
from
!==
'
undefined
'
&&
typeof
to
!==
'
undefined
'
)
{
let
s
=
''
;
for
(
let
i
=
from
;
i
<
to
;
i
++
)
{
s
=
s
+
String
.
fromCharCode
(
buf
[
i
]);
if
(
typeof
from
!==
'
undefined
'
&&
typeof
to
!==
'
undefined
'
)
{
var
s
=
''
;
for
(
var
i
=
from
;
i
<
to
;
i
++
)
{
s
=
s
+
String
.
fromCharCode
(
buf
[
i
]);
}
return
s
;
}
return
s
;
}
return
String
.
fromCharCode
.
apply
(
null
,
buf
);
return
String
.
fromCharCode
.
apply
(
null
,
buf
);
}
function
findFreeXrefNr
(
xrefEntries
,
used
)
{
used
=
typeof
used
!==
'
undefined
'
?
used
:
[];
let
inc
=
used
.
length
;
for
(
let
i
=
1
;
i
<
xrefEntries
.
length
;
i
++
)
{
const
index
=
used
.
indexOf
(
i
);
const
entry
=
xrefEntries
[
""
+
i
];
if
(
index
===
-
1
&&
(
typeof
entry
===
'
undefined
'
||
entry
.
free
))
{
return
i
;
}
if
(
index
!==
-
1
)
{
inc
--
;
function
findFreeXrefNr
(
xrefEntries
,
used
)
{
used
=
typeof
used
!==
'
undefined
'
?
used
:
[];
var
inc
=
used
.
length
;
for
(
var
i
=
1
;
i
<
xrefEntries
.
length
;
i
++
)
{
var
index
=
used
.
indexOf
(
i
);
var
entry
=
xrefEntries
[
""
+
i
];
if
(
index
===
-
1
&&
(
typeof
entry
===
'
undefined
'
||
entry
.
free
))
{
return
i
;
}
if
(
index
!==
-
1
)
{
inc
--
;
}
}
}
return
xrefEntries
.
length
+
inc
;
return
xrefEntries
.
length
+
inc
;
}
function
find
(
uint8
,
needle
,
start
,
limit
)
{
start
=
typeof
start
!==
'
undefined
'
?
start
:
0
;
limit
=
typeof
limit
!==
'
undefined
'
?
limit
:
Number
.
MAX_SAFE_INTEGER
;
const
search
=
stringToUint8Array
(
needle
);
let
match
=
0
;
for
(
let
i
=
start
;
i
<
uint8
.
length
&&
i
<
limit
;
i
++
)
{
if
(
uint8
[
i
]
===
search
[
match
])
{
match
++
;
}
else
{
match
=
0
;
if
(
uint8
[
i
]
===
search
[
match
])
{
match
++
;
}
}
if
(
match
===
search
.
length
)
{
return
(
i
+
1
)
-
match
;
start
=
typeof
start
!==
'
undefined
'
?
start
:
0
;
limit
=
typeof
limit
!==
'
undefined
'
?
limit
:
Number
.
MAX_SAFE_INTEGER
;
var
search
=
stringToUint8Array
(
needle
);
var
match
=
0
;
for
(
var
i
=
start
;
i
<
uint8
.
length
&&
i
<
limit
;
i
++
)
{
if
(
uint8
[
i
]
===
search
[
match
])
{
match
++
;
}
else
{
match
=
0
;
if
(
uint8
[
i
]
===
search
[
match
])
{
match
++
;
}
}
if
(
match
===
search
.
length
)
{
return
(
i
+
1
)
-
match
;
}
}
}
return
-
1
;
return
-
1
;
}
function
findBackwards
(
uint8
,
needle
,
start
,
limit
)
{
start
=
typeof
start
!==
'
undefined
'
?
start
:
uint8
.
length
;
limit
=
typeof
limit
!==
'
undefined
'
?
limit
:
Number
.
MAX_SAFE_INTEGER
;
const
search
=
stringToUint8Array
(
needle
);
let
match
=
search
.
length
-
1
;
for
(
let
i
=
start
;
i
>=
0
&&
i
<
limit
;
i
--
)
{
if
(
uint8
[
i
]
===
search
[
match
])
{
match
--
;
}
else
{
match
=
search
.
length
-
1
;
if
(
uint8
[
i
]
===
search
[
match
])
{
match
--
;
}
}
if
(
match
===
0
)
{
return
i
-
1
;
start
=
typeof
start
!==
'
undefined
'
?
start
:
uint8
.
length
;
limit
=
typeof
limit
!==
'
undefined
'
?
limit
:
Number
.
MAX_SAFE_INTEGER
;
var
search
=
stringToUint8Array
(
needle
);
var
match
=
search
.
length
-
1
;
for
(
var
i
=
start
;
i
>=
0
&&
i
<
limit
;
i
--
)
{
if
(
uint8
[
i
]
===
search
[
match
])
{
match
--
;
}
else
{
match
=
search
.
length
-
1
;
if
(
uint8
[
i
]
===
search
[
match
])
{
match
--
;
}
}
if
(
match
===
0
)
{
return
i
-
1
;
}
}
}
return
-
1
;
return
-
1
;
}
function
strHex
(
s
)
{
let
a
=
""
;
for
(
let
i
=
0
;
i
<
s
.
length
;
i
++
)
{
a
=
a
+
pad2
(
s
.
charCodeAt
(
i
).
toString
(
16
));
}
return
a
;
var
a
=
""
;
for
(
var
i
=
0
;
i
<
s
.
length
;
i
++
)
{
a
=
a
+
pad2
(
s
.
charCodeAt
(
i
).
toString
(
16
));
}
return
a
;
}
async
function
sha256
(
array
)
{
const
cryptoLib
=
getCrypto
();
const
digestTmpBuf
=
await
cryptoLib
.
digest
({
name
:
"
SHA-256
"
},
array
);
const
digestTmpArray
=
new
Uint8Array
(
digestTmpBuf
);
const
digestTmpStr
=
uint8ArrayToString
(
digestTmpArray
);
const
sha256Hex
=
strHex
(
digestTmpStr
);
return
sha256Hex
;
const
cryptoLib
=
getCrypto
();
const
digestTmpBuf
=
await
cryptoLib
.
digest
({
name
:
"
SHA-256
"
},
array
);
const
digestTmpArray
=
new
Uint8Array
(
digestTmpBuf
);
const
digestTmpStr
=
uint8ArrayToString
(
digestTmpArray
);
const
sha256Hex
=
strHex
(
digestTmpStr
);
return
sha256Hex
;
}
function
isSigInRoot
(
pdf
)
{
if
(
typeof
pdf
.
acroForm
===
'
undefined
'
)
{
return
false
;
}
return
pdf
.
acroForm
.
get
(
'
SigFlags
'
)
===
3
;
if
(
typeof
pdf
.
acroForm
===
'
undefined
'
)
{
return
false
;
}
return
pdf
.
acroForm
.
get
(
'
SigFlags
'
)
===
3
;
}
function
updateXrefOffset
(
xref
,
offset
,
offsetDelta
)
{
for
(
const
i
in
xref
.
entries
)
{
if
(
xref
.
entries
[
i
].
offset
>=
offset
)
{
xref
.
entries
[
i
].
offset
+=
offsetDelta
;
for
(
var
i
in
xref
.
entries
)
{
if
(
xref
.
entries
[
i
].
offset
>=
offset
)
{
xref
.
entries
[
i
].
offset
+=
offsetDelta
;
}
}
}
for
(
const
i
in
xref
.
xrefBlocks
)
{
if
(
xref
.
xrefBlocks
[
i
]
>
=
offset
)
{
xref
.
xrefBlocks
[
i
]
+=
offsetDelta
;
for
(
var
i
in
xref
.
xrefBlocks
)
{
if
(
xref
.
xrefBlocks
[
i
]
>=
offset
)
{
xref
.
xrefBlocks
[
i
]
+
=
offset
Delta
;
}
}
}
}
function
updateXrefBlocks
(
xrefBlocks
,
offset
,
offsetDelta
)
{
for
(
const
i
in
xrefBlocks
)
{
if
(
xrefBlocks
[
i
].
start
>=
offset
)
{
xrefBlocks
[
i
].
start
+=
offsetDelta
;
}
if
(
xrefBlocks
[
i
].
end
>=
offset
)
{
xrefBlocks
[
i
].
end
+=
offsetDelta
;
for
(
var
i
in
xrefBlocks
)
{
if
(
xrefBlocks
[
i
].
start
>=
offset
)
{
xrefBlocks
[
i
].
start
+=
offsetDelta
;
}
if
(
xrefBlocks
[
i
].
end
>=
offset
)
{
xrefBlocks
[
i
].
end
+=
offsetDelta
;
}
}
}
}
function
updateOffset
(
pos
,
offset
,
offsetDelta
)
{
if
(
pos
>=
offset
)
{
return
pos
+
offsetDelta
;
}
return
pos
;
if
(
pos
>=
offset
)
{
return
pos
+
offsetDelta
;
}
return
pos
;
}
function
round256
(
x
)
{
return
(
Math
.
ceil
(
x
/
256
)
*
256
)
-
1
;
return
(
Math
.
ceil
(
x
/
256
)
*
256
)
-
1
;
}
/**
...
...
@@ -382,359 +387,349 @@ function round256(x) {
* mm shall be the absolute value of the offset from UT in minutes (00–59)
*/
function
now
(
date
)
{
//date = typeof date !== 'undefined' ? date : new Date();
const
yyyy
=
date
.
getFullYear
().
toString
();
const
MM
=
pad2
(
date
.
getMonth
()
+
1
);
const
dd
=
pad2
(
date
.
getDate
());
const
hh
=
pad2
(
date
.
getHours
());
const
mm
=
pad2
(
date
.
getMinutes
());
const
ss
=
pad2
(
date
.
getSeconds
());
return
yyyy
+
MM
+
dd
+
hh
+
mm
+
ss
+
createOffset
(
date
);
//date = typeof date !== 'undefined' ? date : new Date();
var
yyyy
=
date
.
getFullYear
().
toString
();
var
MM
=
pad2
(
date
.
getMonth
()
+
1
);
var
dd
=
pad2
(
date
.
getDate
());
var
hh
=
pad2
(
date
.
getHours
());
var
mm
=
pad2
(
date
.
getMinutes
());
var
ss
=
pad2
(
date
.
getSeconds
());
return
yyyy
+
MM
+
dd
+
hh
+
mm
+
ss
+
createOffset
(
date
);
}
function
createOffset
(
date
)
{
const
sign
=
date
.
getTimezoneOffset
()
>
0
?
"
-
"
:
"
+
"
;
const
offset
=
Math
.
abs
(
date
.
getTimezoneOffset
());
const
hours
=
pad2
(
Math
.
floor
(
offset
/
60
));
const
minutes
=
pad2
(
offset
%
60
);
return
sign
+
hours
+
"
'
"
+
minutes
;
var
sign
=
(
date
.
getTimezoneOffset
()
>
0
)
?
"
-
"
:
"
+
"
;
var
offset
=
Math
.
abs
(
date
.
getTimezoneOffset
());
var
hours
=
pad2
(
Math
.
floor
(
offset
/
60
));
var
minutes
=
pad2
(
offset
%
60
);
return
sign
+
hours
+
"
'
"
+
minutes
;
}
async
function
newSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
)
{
// {annotEntry} is the ref to the annot widget. If we enlarge the array, make sure all the offsets
// after the modification will be updated -> xref table and startxref
const
annotEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
);
// we'll store all the modifications we make, as we need to adjust the offset in the PDF
const
offsetForm
=
find
(
pdf
.
stream
.
bytes
,
'
<<
'
,
root
.
offset
,
rootSuccessor
.
offset
)
+
2
;
//first we need to find the root element and add the following:
//
// /AcroForm<</Fields[{annotEntry} 0 R] /SigFlags 3>>
//
const
appendAcroForm
=
'
/AcroForm<</Fields[
'
+
annotEntry
+
'
0 R] /SigFlags 3>>
'
;
//before we insert the acroform, we find the right place for annotentry
//we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Contents[
const
pages
=
pdf
.
catalog
.
catDict
.
get
(
'
Pages
'
);
//get first page, we have hidden sig, so don't bother
const
ref
=
pages
.
get
(
'
Kids
'
)[
0
];
const
xref
=
pdf
.
xref
.
fetch
(
ref
);
const
offsetContentEnd
=
xref
.
get
(
'
#Contents_offset
'
);
//we now search backwards, this is safe as we don't expect user content here
let
offsetContent
=
findBackwards
(
pdf
.
stream
.
bytes
,
'
/Contents
'
,
offsetContentEnd
);
const
appendAnnots
=
'
/Annots[
'
+
annotEntry
+
'
0 R]
\n
'
;
//now insert string into stream
let
array
=
insertIntoArray
(
pdf
.
stream
.
bytes
,
offsetForm
,
appendAcroForm
);
//recalculate the offsets in the xref table, only update those that are affected
updateXrefOffset
(
pdf
.
xref
,
offsetForm
,
appendAcroForm
.
length
);
offsetContent
=
updateOffset
(
offsetContent
,
offsetForm
,
appendAcroForm
.
length
);
array
=
insertIntoArray
(
array
,
offsetContent
,
appendAnnots
);
updateXrefOffset
(
pdf
.
xref
,
offsetContent
,
appendAnnots
.
length
);
offsetContent
=
-
1
;
//not needed anymore, don't update when offset changes
//Then add to the next free object (annotEntry)
//add right before the xref table or stream
//if its a table, place element before the xref table
//
// sigEntry is the ref to the signature content. Next we need the signature object
const
sigEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
,
[
annotEntry
]);
//
// {annotEntry} 0 obj
// <</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature)/V Y 0 R>>
// endobj
//
const
append
=
annotEntry
+
'
0 obj
\n
<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature
'
+
annotEntry
+
'
)/V
'
+
sigEntry
+
'
0 R>>
\n
endobj
\n\n
'
;
// we want the offset just before the last xref table or entry
const
blocks
=
findXrefBlocks
(
pdf
.
xref
.
xrefBlocks
);
let
offsetAnnot
=
blocks
[
0
].
start
;
array
=
insertIntoArray
(
array
,
offsetAnnot
,
append
);
//no updateXrefOffset, as the next entry will be following
//
// {sigEntry} 0 obj
// <</Contents <0481801e6d931d561563fb254e27c846e08325570847ed63d6f9e35 ... b2c8788a5>
// /Type/Sig/SubFilter/adbe.pkcs7.detached/Location(Ghent)/M(D:20120928104114+02'00')
// /ByteRange [A B C D]/Filter/Adobe.PPKLite/Reason(Test)/ContactInfo()>>
// endobj
//
//the next entry goes below the above
let
offsetSig
=
offsetAnnot
+
append
.
length
;
// Both {annotEntry} and {sigEntry} objects need to be added to the last xref table. The byte range needs
// to be adjusted. Since the signature will always be in a gap, use first an empty sig
// to check the size, add ~25% size, then calculate the signature and place in the empty
// space.
const
start
=
sigEntry
+
'
0 obj
\n
<</Contents <
'
;
const
dummy
=
await
signPki
(
signingCert
,
certificateChain
,
privateKey
,
stringToUint8Array
(
'
A
'
),
date
);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
const
crypto
=
new
Array
(
round256
(
dummy
.
length
*
2
)).
join
(
'
0
'
);
const
middle
=
'
>
\n
/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:
'
+
now
(
date
)
+
'
\'
)
\n
/ByteRange
'
;
let
byteRange
=
'
[0000000000 0000000000 0000000000 0000000000]
'
;
const
end
=
'
/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>
\n
endobj
\n\n
'
;
//all together
const
append2
=
start
+
crypto
+
middle
+
byteRange
+
end
;
const
offsetByteRange
=
start
.
length
+
crypto
.
length
+
middle
.
length
;
array
=
insertIntoArray
(
array
,
offsetSig
,
append2
);
updateXrefOffset
(
pdf
.
xref
,
offsetAnnot
,
append2
.
length
+
append
.
length
);
//find the xref tables, remove them and also the EOF, as we'll write a new table
const
xrefBlocks
=
findXrefBlocks
(
pdf
.
xref
.
xrefBlocks
);
for
(
const
i
in
xrefBlocks
)
{
const
oldSize
=
array
.
length
;
array
=
removeFromArray
(
array
,
xrefBlocks
[
i
].
start
,
xrefBlocks
[
i
].
end
);
const
length
=
array
.
length
-
oldSize
;
updateXrefOffset
(
pdf
.
xref
,
xrefBlocks
[
i
].
start
,
length
);
//check for %%EOF and remove it as well
const
offsetEOF
=
find
(
array
,
'
%%EOF
'
,
xrefBlocks
[
i
].
start
,
xrefBlocks
[
i
].
start
+
20
);
if
(
offsetEOF
>
0
)
{
const
lengthEOF
=
'
%%EOF
'
.
length
;
array
=
removeFromArray
(
array
,
offsetEOF
,
offsetEOF
+
lengthEOF
);
updateXrefOffset
(
pdf
.
xref
,
offsetEOF
,
-
lengthEOF
);
updateXrefBlocks
(
xrefBlocks
,
offsetEOF
,
-
lengthEOF
);
offsetAnnot
=
updateOffset
(
offsetAnnot
,
offsetEOF
,
-
lengthEOF
);
offsetSig
=
updateOffset
(
offsetSig
,
offsetEOF
,
-
lengthEOF
);
}
updateXrefBlocks
(
xrefBlocks
,
xrefBlocks
[
i
].
start
,
length
);
offsetAnnot
=
updateOffset
(
offsetAnnot
,
xrefBlocks
[
i
].
start
,
length
);
offsetSig
=
updateOffset
(
offsetSig
,
xrefBlocks
[
i
].
start
,
length
);
}
const
sha256Hex
=
await
sha256
(
array
);
//add the new entries to the xref
pdf
.
xref
.
entries
[
annotEntry
]
=
{
offset
:
offsetAnnot
,
gen
:
0
,
free
:
false
};
pdf
.
xref
.
entries
[
sigEntry
]
=
{
offset
:
offsetSig
,
gen
:
0
,
free
:
false
};
let
xrefTable
=
createXrefTable
(
pdf
.
xref
.
entries
);
//also empty entries count as in the PDF spec, page 720 (example)
xrefTable
+=
createTrailer
(
pdf
.
xref
.
topDict
,
array
.
length
,
sha256Hex
,
pdf
.
xref
.
entries
.
length
);
array
=
insertIntoArray
(
array
,
array
.
length
,
xrefTable
);
//since we consolidate, no prev! [adjust /Prev -> rawparsing + offset]
const
from1
=
0
;
const
to1
=
offsetSig
+
start
.
length
;
const
from2
=
to1
+
crypto
.
length
;
const
to2
=
(
array
.
length
-
from2
)
-
1
;
byteRange
=
'
[
'
+
pad10
(
from1
)
+
'
'
+
pad10
(
to1
-
1
)
+
'
'
+
pad10
(
from2
+
1
)
+
'
'
+
pad10
(
to2
)
+
'
]
'
;
array
=
updateArray
(
array
,
offsetSig
+
offsetByteRange
,
byteRange
);
const
data
=
removeFromArray
(
array
,
to1
-
1
,
from2
+
1
);
const
crypto2
=
await
signPki
(
signingCert
,
certificateChain
,
privateKey
,
data
.
buffer
,
date
);
array
=
updateArray
(
array
,
to1
,
crypto2
);
return
array
;
// {annotEntry} is the ref to the annot widget. If we enlarge the array, make sure all the offsets
// after the modification will be updated -> xref table and startxref
var
annotEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
);
// we'll store all the modifications we make, as we need to adjust the offset in the PDF
var
offsetForm
=
find
(
pdf
.
stream
.
bytes
,
'
<<
'
,
root
.
offset
,
rootSuccessor
.
offset
)
+
2
;
//first we need to find the root element and add the following:
//
// /AcroForm<</Fields[{annotEntry} 0 R] /SigFlags 3>>
//
var
appendAcroForm
=
'
/AcroForm<</Fields[
'
+
annotEntry
+
'
0 R] /SigFlags 3>>
'
;
//before we insert the acroform, we find the right place for annotentry
//we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Contents[
var
pages
=
pdf
.
catalog
.
catDict
.
get
(
'
Pages
'
);
//get first page, we have hidden sig, so don't bother
var
ref
=
pages
.
get
(
'
Kids
'
)[
0
];
var
xref
=
pdf
.
xref
.
fetch
(
ref
);
var
offsetContentEnd
=
xref
.
get
(
'
#Contents_offset
'
);
//we now search backwards, this is safe as we don't expect user content here
var
offsetContent
=
findBackwards
(
pdf
.
stream
.
bytes
,
'
/Contents
'
,
offsetContentEnd
);
var
appendAnnots
=
'
/Annots[
'
+
annotEntry
+
'
0 R]
\n
'
;
//now insert string into stream
var
array
=
insertIntoArray
(
pdf
.
stream
.
bytes
,
offsetForm
,
appendAcroForm
);
//recalculate the offsets in the xref table, only update those that are affected
updateXrefOffset
(
pdf
.
xref
,
offsetForm
,
appendAcroForm
.
length
);
offsetContent
=
updateOffset
(
offsetContent
,
offsetForm
,
appendAcroForm
.
length
);
var
array
=
insertIntoArray
(
array
,
offsetContent
,
appendAnnots
);
updateXrefOffset
(
pdf
.
xref
,
offsetContent
,
appendAnnots
.
length
);
offsetContent
=
-
1
;
//not needed anymore, don't update when offset changes
//Then add to the next free object (annotEntry)
//add right before the xref table or stream
//if its a table, place element before the xref table
//
// sigEntry is the ref to the signature content. Next we need the signature object
var
sigEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
,
[
annotEntry
]);
//
// {annotEntry} 0 obj
// <</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature)/V Y 0 R>>
// endobj
//
var
append
=
annotEntry
+
'
0 obj
\n
<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature
'
+
annotEntry
+
'
)/V
'
+
sigEntry
+
'
0 R>>
\n
endobj
\n\n
'
;
// we want the offset just before the last xref table or entry
var
blocks
=
findXrefBlocks
(
pdf
.
xref
.
xrefBlocks
);
var
offsetAnnot
=
blocks
[
0
].
start
;
array
=
insertIntoArray
(
array
,
offsetAnnot
,
append
);
//no updateXrefOffset, as the next entry will be following
//
// {sigEntry} 0 obj
// <</Contents <0481801e6d931d561563fb254e27c846e08325570847ed63d6f9e35 ... b2c8788a5>
// /Type/Sig/SubFilter/adbe.pkcs7.detached/Location(Ghent)/M(D:20120928104114+02'00')
// /ByteRange [A B C D]/Filter/Adobe.PPKLite/Reason(Test)/ContactInfo()>>
// endobj
//
//the next entry goes below the above
var
offsetSig
=
offsetAnnot
+
append
.
length
;
// Both {annotEntry} and {sigEntry} objects need to be added to the last xref table. The byte range needs
// to be adjusted. Since the signature will always be in a gap, use first an empty sig
// to check the size, add ~25% size, then calculate the signature and place in the empty
// space.
var
start
=
sigEntry
+
'
0 obj
\n
<</Contents <
'
;
var
dummy
=
await
sign_pki
(
signingCert
,
certificateChain
,
privateKey
,
stringToUint8Array
(
'
A
'
),
date
);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
var
crypto
=
new
Array
(
round256
(
dummy
.
length
*
2
)).
join
(
'
0
'
);
var
middle
=
'
>
\n
/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:
'
+
now
(
date
)
+
'
\'
)
\n
/ByteRange
'
;
var
byteRange
=
'
[0000000000 0000000000 0000000000 0000000000]
'
;
var
end
=
'
/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>
\n
endobj
\n\n
'
;
//all together
var
append2
=
start
+
crypto
+
middle
+
byteRange
+
end
;
var
offsetByteRange
=
start
.
length
+
crypto
.
length
+
middle
.
length
;
array
=
insertIntoArray
(
array
,
offsetSig
,
append2
);
updateXrefOffset
(
pdf
.
xref
,
offsetAnnot
,
append2
.
length
+
append
.
length
);
//find the xref tables, remove them and also the EOF, as we'll write a new table
var
xrefBlocks
=
findXrefBlocks
(
pdf
.
xref
.
xrefBlocks
);
for
(
var
i
in
xrefBlocks
)
{
var
oldSize
=
array
.
length
;
array
=
removeFromArray
(
array
,
xrefBlocks
[
i
].
start
,
xrefBlocks
[
i
].
end
);
var
length
=
array
.
length
-
oldSize
;
updateXrefOffset
(
pdf
.
xref
,
xrefBlocks
[
i
].
start
,
length
);
//check for %%EOF and remove it as well
var
offsetEOF
=
find
(
array
,
'
%%EOF
'
,
xrefBlocks
[
i
].
start
,
xrefBlocks
[
i
].
start
+
20
);
if
(
offsetEOF
>
0
)
{
var
lengthEOF
=
'
%%EOF
'
.
length
;
array
=
removeFromArray
(
array
,
offsetEOF
,
offsetEOF
+
lengthEOF
);
updateXrefOffset
(
pdf
.
xref
,
offsetEOF
,
-
lengthEOF
);
updateXrefBlocks
(
xrefBlocks
,
offsetEOF
,
-
lengthEOF
);
offsetAnnot
=
updateOffset
(
offsetAnnot
,
offsetEOF
,
-
lengthEOF
);
offsetSig
=
updateOffset
(
offsetSig
,
offsetEOF
,
-
lengthEOF
);
}
updateXrefBlocks
(
xrefBlocks
,
xrefBlocks
[
i
].
start
,
length
);
offsetAnnot
=
updateOffset
(
offsetAnnot
,
xrefBlocks
[
i
].
start
,
length
);
offsetSig
=
updateOffset
(
offsetSig
,
xrefBlocks
[
i
].
start
,
length
);
}
var
sha256Hex
=
await
sha256
(
array
);
//add the new entries to the xref
pdf
.
xref
.
entries
[
annotEntry
]
=
{
offset
:
offsetAnnot
,
gen
:
0
,
free
:
false
};
pdf
.
xref
.
entries
[
sigEntry
]
=
{
offset
:
offsetSig
,
gen
:
0
,
free
:
false
};
var
xrefTable
=
createXrefTable
(
pdf
.
xref
.
entries
);
//also empty entries count as in the PDF spec, page 720 (example)
xrefTable
+=
createTrailer
(
pdf
.
xref
.
topDict
,
array
.
length
,
sha256Hex
,
pdf
.
xref
.
entries
.
length
);
array
=
insertIntoArray
(
array
,
array
.
length
,
xrefTable
);
//since we consolidate, no prev! [adjust /Prev -> rawparsing + offset]
var
from1
=
0
;
var
to1
=
offsetSig
+
start
.
length
;
var
from2
=
to1
+
crypto
.
length
;
var
to2
=
(
array
.
length
-
from2
)
-
1
;
var
byteRange
=
'
[
'
+
pad10
(
from1
)
+
'
'
+
pad10
(
to1
-
1
)
+
'
'
+
pad10
(
from2
+
1
)
+
'
'
+
pad10
(
to2
)
+
'
]
'
;
array
=
updateArray
(
array
,
(
offsetSig
+
offsetByteRange
),
byteRange
);
var
data
=
removeFromArray
(
array
,
to1
-
1
,
from2
+
1
);
var
crypto2
=
await
sign_pki
(
signingCert
,
certificateChain
,
privateKey
,
data
.
buffer
,
date
);
array
=
updateArray
(
array
,
to1
,
crypto2
);
return
array
;
}
async
function
appendSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
)
{
//copy root and the entry with contents to the end
const
startRoot
=
pdf
.
stream
.
bytes
.
length
+
1
;
let
array
=
copyToEnd
(
pdf
.
stream
.
bytes
,
root
.
offset
-
1
,
rootSuccessor
.
offset
);
//since we signed the first one, we know how the pdf has to look like:
const
offsetAcroForm
=
find
(
array
,
'
/AcroForm<</Fields
'
,
startRoot
);
const
endOffsetAcroForm
=
find
(
array
,
'
]
'
,
offsetAcroForm
);
const
annotEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
);
const
sigEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
,
[
annotEntry
]);
const
appendAnnot
=
'
'
+
annotEntry
+
'
0 R
'
;
array
=
insertIntoArray
(
array
,
endOffsetAcroForm
,
appendAnnot
);
//we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Annots
const
pages
=
pdf
.
catalog
.
catDict
.
get
(
'
Pages
'
);
//get first page, we have hidden sig, so don't bother
const
contentRef
=
pages
.
get
(
'
Kids
'
)[
0
];
const
xref
=
pdf
.
xref
.
fetch
(
contentRef
);
const
offsetAnnotEnd
=
xref
.
get
(
'
#Annots_offset
'
);
//we now search ], this is safe as we signed it previously
const
endOffsetAnnot
=
find
(
array
,
'
]
'
,
offsetAnnotEnd
);
const
xrefEntry
=
pdf
.
xref
.
getEntry
(
contentRef
.
num
);
const
xrefEntrySuccosser
=
findSuccessorEntry
(
pdf
.
xref
.
entries
,
xrefEntry
);
const
offsetAnnotRelative
=
endOffsetAnnot
-
xrefEntrySuccosser
.
offset
;
const
startContent
=
array
.
length
;
array
=
copyToEnd
(
array
,
xrefEntry
.
offset
,
xrefEntrySuccosser
.
offset
);
array
=
insertIntoArray
(
array
,
array
.
length
+
offsetAnnotRelative
,
appendAnnot
);
const
startAnnot
=
array
.
length
;
const
append
=
annotEntry
+
'
0 obj
\n
<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature
'
+
annotEntry
+
'
)/V
'
+
sigEntry
+
'
0 R>>
\n
endobj
\n\n
'
;
array
=
insertIntoArray
(
array
,
startAnnot
,
append
);
const
startSig
=
array
.
length
;
const
start
=
sigEntry
+
'
0 obj
\n
<</Contents <
'
;
const
dummy
=
await
signPki
(
signingCert
,
certificateChain
,
privateKey
,
stringToUint8Array
(
'
A
'
),
date
);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
const
crypto
=
new
Array
(
round256
(
dummy
.
length
*
2
)).
join
(
'
0
'
);
const
middle
=
'
>
\n
/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:
'
+
now
(
date
)
+
'
\'
)
\n
/ByteRange
'
;
let
byteRange
=
'
[0000000000 0000000000 0000000000 0000000000]
'
;
const
end
=
'
/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>
\n
endobj
\n\n
'
;
//all together
const
append2
=
start
+
crypto
+
middle
+
byteRange
+
end
;
array
=
insertIntoArray
(
array
,
startSig
,
append2
);
const
sha256Hex
=
await
sha256
(
array
);
const
prev
=
pdf
.
xref
.
xrefBlocks
[
0
];
const
startxref
=
array
.
length
;
const
xrefEntries
=
[];
xrefEntries
[
0
]
=
{
offset
:
0
,
gen
:
65535
,
free
:
true
};
xrefEntries
[
pdf
.
xref
.
topDict
.
getRaw
(
'
Root
'
).
num
]
=
{
offset
:
startRoot
,
gen
:
0
,
free
:
false
};
xrefEntries
[
contentRef
.
num
]
=
{
offset
:
startContent
,
gen
:
0
,
free
:
false
};
xrefEntries
[
annotEntry
]
=
{
offset
:
startAnnot
,
gen
:
0
,
free
:
false
};
xrefEntries
[
sigEntry
]
=
{
offset
:
startSig
,
gen
:
0
,
free
:
false
};
let
xrefTable
=
createXrefTableAppend
(
xrefEntries
);
xrefTable
+=
createTrailer
(
pdf
.
xref
.
topDict
,
startxref
,
sha256Hex
,
xrefEntries
.
length
,
prev
);
array
=
insertIntoArray
(
array
,
array
.
length
,
xrefTable
);
const
from1
=
0
;
const
to1
=
startSig
+
start
.
length
;
const
from2
=
to1
+
crypto
.
length
;
const
to2
=
(
array
.
length
-
from2
)
-
1
;
byteRange
=
'
[
'
+
pad10
(
from1
)
+
'
'
+
pad10
(
to1
-
1
)
+
'
'
+
pad10
(
from2
+
1
)
+
'
'
+
pad10
(
to2
)
+
'
]
'
;
array
=
updateArray
(
array
,
from2
+
middle
.
length
,
byteRange
);
//now sign from1-to1 / from2-to2 and update byterange
const
data
=
removeFromArray
(
array
,
to1
-
1
,
from2
+
1
);
const
crypto2
=
await
signPki
(
signingCert
,
certificateChain
,
privateKey
,
data
.
buffer
,
date
);
array
=
updateArray
(
array
,
to1
,
crypto2
);
return
array
;
//copy root and the entry with contents to the end
var
startRoot
=
pdf
.
stream
.
bytes
.
length
+
1
;
var
array
=
copyToEnd
(
pdf
.
stream
.
bytes
,
root
.
offset
-
1
,
rootSuccessor
.
offset
);
//since we signed the first one, we know how the pdf has to look like:
var
offsetAcroForm
=
find
(
array
,
'
/AcroForm<</Fields
'
,
startRoot
);
var
endOffsetAcroForm
=
find
(
array
,
'
]
'
,
offsetAcroForm
);
var
annotEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
);
var
sigEntry
=
findFreeXrefNr
(
pdf
.
xref
.
entries
,
[
annotEntry
]);
var
appendAnnot
=
'
'
+
annotEntry
+
'
0 R
'
;
array
=
insertIntoArray
(
array
,
endOffsetAcroForm
,
appendAnnot
);
//we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Annots
var
pages
=
pdf
.
catalog
.
catDict
.
get
(
'
Pages
'
);
//get first page, we have hidden sig, so don't bother
var
contentRef
=
pages
.
get
(
'
Kids
'
)[
0
];
var
xref
=
pdf
.
xref
.
fetch
(
contentRef
);
var
offsetAnnotEnd
=
xref
.
get
(
'
#Annots_offset
'
);
//we now search ], this is safe as we signed it previously
var
endOffsetAnnot
=
find
(
array
,
'
]
'
,
offsetAnnotEnd
);
var
xrefEntry
=
pdf
.
xref
.
getEntry
(
contentRef
.
num
);
var
xrefEntrySuccosser
=
findSuccessorEntry
(
pdf
.
xref
.
entries
,
xrefEntry
);
var
offsetAnnotRelative
=
endOffsetAnnot
-
xrefEntrySuccosser
.
offset
;
var
startContent
=
array
.
length
;
array
=
copyToEnd
(
array
,
xrefEntry
.
offset
,
xrefEntrySuccosser
.
offset
);
array
=
insertIntoArray
(
array
,
array
.
length
+
offsetAnnotRelative
,
appendAnnot
);
var
startAnnot
=
array
.
length
;
var
append
=
annotEntry
+
'
0 obj
\n
<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature
'
+
annotEntry
+
'
)/V
'
+
sigEntry
+
'
0 R>>
\n
endobj
\n\n
'
;
array
=
insertIntoArray
(
array
,
startAnnot
,
append
);
var
startSig
=
array
.
length
;
var
start
=
sigEntry
+
'
0 obj
\n
<</Contents <
'
;
var
dummy
=
await
sign_pki
(
signingCert
,
certificateChain
,
privateKey
,
stringToUint8Array
(
'
A
'
),
date
);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
var
crypto
=
new
Array
(
round256
(
dummy
.
length
*
2
)).
join
(
'
0
'
);
var
middle
=
'
>
\n
/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:
'
+
now
(
date
)
+
'
\'
)
\n
/ByteRange
'
;
var
byteRange
=
'
[0000000000 0000000000 0000000000 0000000000]
'
;
var
end
=
'
/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>
\n
endobj
\n\n
'
;
//all together
var
append2
=
start
+
crypto
+
middle
+
byteRange
+
end
;
array
=
insertIntoArray
(
array
,
startSig
,
append2
);
var
sha256Hex
=
await
sha256
(
array
);
var
prev
=
pdf
.
xref
.
xrefBlocks
[
0
];
var
startxref
=
array
.
length
;
var
xrefEntries
=
[];
xrefEntries
[
0
]
=
{
offset
:
0
,
gen
:
65535
,
free
:
true
};
xrefEntries
[
pdf
.
xref
.
topDict
.
getRaw
(
'
Root
'
).
num
]
=
{
offset
:
startRoot
,
gen
:
0
,
free
:
false
};
xrefEntries
[
contentRef
.
num
]
=
{
offset
:
startContent
,
gen
:
0
,
free
:
false
};
xrefEntries
[
annotEntry
]
=
{
offset
:
startAnnot
,
gen
:
0
,
free
:
false
};
xrefEntries
[
sigEntry
]
=
{
offset
:
startSig
,
gen
:
0
,
free
:
false
};
var
xrefTable
=
createXrefTableAppend
(
xrefEntries
);
xrefTable
+=
createTrailer
(
pdf
.
xref
.
topDict
,
startxref
,
sha256Hex
,
xrefEntries
.
length
,
prev
);
array
=
insertIntoArray
(
array
,
array
.
length
,
xrefTable
);
var
from1
=
0
;
var
to1
=
startSig
+
start
.
length
;
var
from2
=
to1
+
crypto
.
length
;
var
to2
=
(
array
.
length
-
from2
)
-
1
;
var
byteRange
=
'
[
'
+
pad10
(
from1
)
+
'
'
+
pad10
(
to1
-
1
)
+
'
'
+
pad10
(
from2
+
1
)
+
'
'
+
pad10
(
to2
)
+
'
]
'
;
array
=
updateArray
(
array
,
from2
+
middle
.
length
,
byteRange
);
//now sign from1-to1 / from2-to2 and update byterange
var
data
=
removeFromArray
(
array
,
to1
-
1
,
from2
+
1
);
var
crypto2
=
await
sign_pki
(
signingCert
,
certificateChain
,
privateKey
,
data
.
buffer
,
date
);
array
=
updateArray
(
array
,
to1
,
crypto2
);
return
array
;
}
function
loadPdf
(
pdfArray
)
{
const
pdf
=
new
pdfjsCoreDocument
.
PDFDocument
(
false
,
pdfArray
,
''
);
pdf
.
parseStartXRef
();
pdf
.
parse
();
return
pdf
;
var
pdf
=
new
pdfjsCoreDocument
.
PDFDocument
(
false
,
pdfArray
,
''
);
pdf
.
parseStartXRef
();
pdf
.
parse
();
return
pdf
;
}
//data must be Uint8Array
async
function
signPki
(
signingCert
,
certificateChain
,
privateKey
,
data
,
date
)
{
const
crypto
=
getCrypto
();
//date = typeof date !== 'undefined' ? date : new Date();
const
hashAlg
=
"
SHA-256
"
;
const
digest
=
await
crypto
.
digest
({
name
:
hashAlg
},
data
);
const
signedAttr
=
[];
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.3
"
,
values
:
[
new
ObjectIdentifier
({
value
:
"
1.2.840.113549.1.7.1
"
})
]
}));
// contentType
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.4
"
,
values
:
[
new
OctetString
({
valueHex
:
digest
})
]
}));
// messageDigest
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.5
"
,
values
:
[
new
UTCTime
({
valueDate
:
date
})
]
}));
// signingTime
const
cmsSignedSimpl
=
new
SignedData
({
version
:
1
,
encapContentInfo
:
new
EncapsulatedContentInfo
({
eContentType
:
"
1.2.840.113549.1.7.1
"
// "data" content type
}),
signerInfos
:
[
new
SignerInfo
({
async
function
sign_pki
(
signingCert
,
certificateChain
,
privateKey
,
data
,
date
)
{
const
crypto
=
getCrypto
();
//date = typeof date !== 'undefined' ? date : new Date();
const
hashAlg
=
"
SHA-256
"
;
const
digest
=
await
crypto
.
digest
({
name
:
hashAlg
},
data
);
const
signedAttr
=
[];
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.3
"
,
values
:
[
new
ObjectIdentifier
({
value
:
"
1.2.840.113549.1.7.1
"
})
]
}));
// contentType
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.4
"
,
values
:
[
new
OctetString
({
valueHex
:
digest
})
]
}));
// messageDigest
signedAttr
.
push
(
new
Attribute
({
type
:
"
1.2.840.113549.1.9.5
"
,
values
:
[
new
UTCTime
({
valueDate
:
date
})
]
}));
// signingTime
const
cmsSignedSimpl
=
new
SignedData
({
version
:
1
,
sid
:
new
IssuerAndSerialNumber
({
issuer
:
signingCert
.
issuer
,
serialNumber
:
signingCert
.
serialNumber
encapContentInfo
:
new
EncapsulatedContentInfo
({
eContentType
:
"
1.2.840.113549.1.7.1
"
// "data" content type
}),
signedAttrs
:
new
SignedAndUnsignedAttributes
({
type
:
0
,
attributes
:
signedAttr
})
})
],
certificates
:
certificateChain
});
signerInfos
:
[
new
SignerInfo
({
version
:
1
,
sid
:
new
IssuerAndSerialNumber
({
issuer
:
signingCert
.
issuer
,
serialNumber
:
signingCert
.
serialNumber
}),
signedAttrs
:
new
SignedAndUnsignedAttributes
({
type
:
0
,
attributes
:
signedAttr
})
})
],
certificates
:
certificateChain
});
await
cmsSignedSimpl
.
sign
(
privateKey
,
0
,
hashAlg
,
data
.
buffer
);
const
signatureBuffer
=
await
cmsSignedSimpl
.
sign
(
privateKey
,
0
,
hashAlg
,
data
.
buffer
);
const
cmsSignedSchema
=
cmsSignedSimpl
.
toSchema
(
true
);
const
cmsSignedSchema
=
cmsSignedSimpl
.
toSchema
(
true
);
const
cmsContentSimp
=
new
ContentInfo
({
contentType
:
"
1.2.840.113549.1.7.2
"
,
content
:
cmsSignedSchema
});
const
cmsContentSimp
=
new
ContentInfo
({
contentType
:
"
1.2.840.113549.1.7.2
"
,
content
:
cmsSignedSchema
});
const
_cmsSignedSchema
=
cmsContentSimp
.
toSchema
();
const
_cmsSignedSchema
=
cmsContentSimp
.
toSchema
();
//region Make length of some elements in "indefinite form"
_cmsSignedSchema
.
lenBlock
.
isIndefiniteForm
=
true
;
//region Make length of some elements in "indefinite form"
_cmsSignedSchema
.
lenBlock
.
isIndefiniteForm
=
true
;
const
block1
=
_cmsSignedSchema
.
valueBlock
.
value
[
1
];
block1
.
lenBlock
.
isIndefiniteForm
=
true
;
const
block1
=
_cmsSignedSchema
.
valueBlock
.
value
[
1
];
block1
.
lenBlock
.
isIndefiniteForm
=
true
;
const
block2
=
block1
.
valueBlock
.
value
[
0
];
block2
.
lenBlock
.
isIndefiniteForm
=
true
;
const
block2
=
block1
.
valueBlock
.
value
[
0
];
block2
.
lenBlock
.
isIndefiniteForm
=
true
;
//endregion
//endregion
cons
t
cmsSignedBuffer
=
_cmsSignedSchema
.
toBER
(
false
);
le
t
cmsSignedBuffer
=
_cmsSignedSchema
.
toBER
(
false
);
const
cmsSignedArray
=
new
Uint8Array
(
cmsSignedBuffer
);
const
cmsSignedString
=
uint8ArrayToString
(
cmsSignedArray
);
const
hex
=
strHex
(
cmsSignedString
);
return
hex
;
const
cmsSignedArray
=
new
Uint8Array
(
cmsSignedBuffer
);
const
cmsSignedString
=
uint8ArrayToString
(
cmsSignedArray
);
const
hex
=
strHex
(
cmsSignedString
);
return
hex
;
}
async
function
signPdfObjects
(
pdfRaw
,
signingCert
,
certificateChain
,
privateKey
,
date
)
{
date
=
typeof
date
!==
'
undefined
'
?
date
:
new
Date
();
if
(
pdfRaw
instanceof
ArrayBuffer
)
{
pdfRaw
=
new
Uint8Array
(
pdfRaw
);
}
const
pdf
=
loadPdf
(
pdfRaw
);
const
root
=
findRootEntry
(
pdf
.
xref
);
const
rootSuccessor
=
findSuccessorEntry
(
pdf
.
xref
.
entries
,
root
);
if
(
!
isSigInRoot
(
pdf
))
{
return
await
newSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
);
}
else
{
return
await
appendSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
);
}
async
function
signPdfObjects
(
pdfRaw
,
signingCert
,
certificateChain
,
privateKey
,
date
)
{
date
=
typeof
date
!==
'
undefined
'
?
date
:
new
Date
();
if
(
pdfRaw
instanceof
ArrayBuffer
)
{
pdfRaw
=
new
Uint8Array
(
pdfRaw
);
}
var
pdf
=
loadPdf
(
pdfRaw
);
var
root
=
findRootEntry
(
pdf
.
xref
);
var
rootSuccessor
=
findSuccessorEntry
(
pdf
.
xref
.
entries
,
root
);
if
(
!
isSigInRoot
(
pdf
))
{
return
await
newSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
);
}
else
{
return
await
appendSig
(
pdf
,
root
,
rootSuccessor
,
date
,
signingCert
,
certificateChain
,
privateKey
);
}
}
export
function
signPdf
(
pdfRaw
,
signingCert
,
certificateChain
,
privateKey
)
{
const
signingCertObj
=
parseCertificate
(
signingCert
);
const
certificateChainObj
=
[];
certificateChainObj
[
0
]
=
parseCertificate
(
signingCert
);
for
(
let
i
=
0
;
i
<
certificateChain
.
length
;
i
++
)
{
certificateChainObj
[
i
+
1
]
=
parseCertificate
(
certificateChain
[
i
])
;
}
const
signingCertObj
=
parseCertificate
(
signingCert
);
const
certificateChainObj
=
[];
certificateChainObj
[
0
]
=
parseCertificate
(
signingCert
);
for
(
let
i
=
0
;
i
<
certificateChain
.
length
;
i
++
)
{
certificateChainObj
[
i
+
1
]
=
parseCertificate
(
certificateChain
[
i
])
}
return
parsePrivateKey
(
privateKey
).
then
(
privateKeyDecoded
=>
{
return
signPdfObjects
(
pdfRaw
,
signingCertObj
,
certificateChainObj
,
privateKeyDecoded
);
});
}
return
parsePrivateKey
(
privateKey
).
then
(
privateKeyDecoded
=>
{
return
signPdfObjects
(
pdfRaw
,
signingCertObj
,
certificateChainObj
,
privateKeyDecoded
);
});
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment