Signing policies
A signing policy tells ADM what to do with the PDFs of a folder. There are three modes:
| Mode | What ADM does |
|---|---|
OFF |
Nothing. ADM does not classify the PDFs of the folder. |
WATCH |
ADM records where every PDF comes from. adm_pdf_api.get_provenance returns the status. ADM signs nothing. |
SIGN |
Like WATCH. ADM also signs the PDFs that it makes, with the key of the setting AUTO_SIGN_KEY_ID. |
WATCH is the default. No mode refuses an upload. A user can download a PDF, change it, and upload
it again. ADM does not sign such a file. It only records that the file is no longer the file that
ADM signed. For the statuses, see
See where a PDF comes from.
In a folder with the mode SIGN, ADM signs the results of the PDF operations and the saves of the
PDF editor. See Know what ADM signs.
Set the key and the default mode
Section titled “Set the key and the default mode”Two settings control the policies. Change them on the Settings page, which only administrators can open. See the settings reference.
| Key | Default | What it does |
|---|---|---|
AUTO_SIGN_KEY_ID |
empty | The ID of the signing key (adm_signing_keys.signing_key_id) that signs in folders with the mode SIGN. |
AUTO_SIGN_DEFAULT_MODE |
WATCH |
The mode of a folder that no policy covers. |
Register the key first. See Set up a signing key.
Use a key whose private key stays in OpenBao, Vault or OCI Vault. Anyone who can run code as the
owner of the ADM schema can sign with a key of the provider DATABASE.
A value of AUTO_SIGN_DEFAULT_MODE that is not OFF, WATCH or SIGN is an error. ADM does not
read it as OFF, because a typo must not stop signing without a message.
adm_signing_api.get_effective_policy raises adm_error.e_invalid_param for such a value.
An upload of a PDF does not fail because of it: ADM classifies the file.
Create a policy
Section titled “Create a policy”Only an administrator can create, change, switch and delete a policy. Every other user gets
adm_error.e_admin_required. A policy has a folder path pattern, a mode, an active flag and a
description. This block creates a policy for /contracts/ and one for /contracts/emea/:
declare l_policy_id adm_signing_policies.policy_id%type;begin adm_context_api.system_login;
l_policy_id := adm_signing_policy_api.create_policy( p_folder_path_pattern => '/contracts/' , p_signing_mode => adm_signing_policy_api.c_mode_sign , p_description => 'Sign what ADM makes' );
l_policy_id := adm_signing_policy_api.create_policy( p_folder_path_pattern => '/contracts/emea/' , p_signing_mode => adm_signing_policy_api.c_mode_watch , p_description => 'Record only' );end;/A policy with the mode SIGN needs a key that can sign. The setting AUTO_SIGN_KEY_ID must name a
key that exists, is active, and has a certificate that is valid now. If it does not, the call raises
adm_error.e_signing_key_unusable or adm_error.e_signing_key_not_found, and changes nothing. This
is the output of the first create_policy for /contracts/ in a schema where the setting is empty:
ORA-20272: Signing key of the setting AUTO_SIGN_KEY_ID cannot be used: the setting is empty or is not a numberupdate_policy and activate_policy make the same check when the mode is SIGN.
deactivate_policy does not, so an administrator can always switch a policy off.
Know which policy applies to a folder
Section titled “Know which policy applies to a folder”The pattern is a path prefix. It works like the pattern of a storage policy:
- A pattern starts with a slash and is written in lower case, because the path of a folder is in lower case. A pattern in capitals is refused.
- ADM adds a slash to the end of the path of the folder. A folder matches a pattern when its
path starts with the pattern.
/contracts/covers the folder/contractsand every folder below it. - If several patterns match, the longest pattern wins.
/contracts/emea/beats/contracts/for the folder/contracts/emea/2026. - A pattern without a slash at the end also covers the folders with a longer name.
/contractscovers/contracts-archive. End a pattern with a slash. - The characters
%and_in a pattern are normal characters. - An inactive policy is ignored.
- A folder that no policy covers has the mode of
AUTO_SIGN_DEFAULT_MODE.
adm_signing_api.get_effective_policy returns the mode, the key and the place that the mode comes
from. Any user can call it. This block asks for four folders and lists the policies. The folder
/contracts/emea/2026 was in /contracts/emea:
declare l_policy adm_signing_api.t_effective_policy;begin adm_context_api.system_login;
for r in ( select f.folder_id , f.folder_path from adm_folders f where f.folder_path in ('/contracts', '/contracts/emea', '/contracts/emea/2026', '/archive') order by f.folder_path ) loop l_policy := adm_signing_api.get_effective_policy(r.folder_id); dbms_output.put_line(rpad(r.folder_path, 22) || rpad(l_policy.signing_mode, 7) || l_policy.source); end loop;end;/Folder Mode Decided by/contracts SIGN POLICY/contracts/emea WATCH POLICY/contracts/emea/2026 WATCH POLICY/archive WATCH DEFAULTThe source is POLICY when a policy decided, and DEFAULT when the setting decided. The
member policy_id names the policy.
ADM reads the path of the folder at every call and keeps no cache. When an administrator moves a
folder, the folder has the policy of its new place. After the move of 2026 into /archive, the
folder /archive/2026 has the mode WATCH and the source DEFAULT. A lookup takes about 0.04 ms
with 21 policies (measured on Oracle 26ai Free, 20 000 calls).
Know what ADM signs
Section titled “Know what ADM signs”In a folder with the mode SIGN, ADM signs the result of a PDF operation before it stores it. The
document gets one version, and that version is signed. The target folder of the result decides, not
the folder of the source.
| Operation | Result in a SIGN folder |
|---|---|
merge, split, watermark, add_text, add_image, add_comments, add_page_numbers, fill_form, flatten_form, add_attachments, convert_to_pdf |
Signed with the key of AUTO_SIGN_KEY_ID. split signs every part. |
protect |
Not signed. AOP cannot sign an encrypted file. |
to_images, get_attachments |
Not signed. The results are images, or the files that are in the PDF. |
sign |
Signed with the key of the caller only. |
| A save in the PDF editor (a new version, or a new PDF) | Signed with the key of AUTO_SIGN_KEY_ID. See Sign the saves of the PDF editor. |
| An upload | Not signed. ADM records the status. |
ADM signs for the system, not for a user. The user needs no grant for the key, and an embed session
can run the operations. There is no public call for this signature: ADM makes it inside the
operation, after it has checked the rights of the user. adm_pdf_api.sign stays the call for a
signature of a person.
Sign a PDF in a SIGN folder
Section titled “Sign a PDF in a SIGN folder”This block makes a watermark of offer.pdf as the user JDOE. The folder /users/jdoe/contracts
has the mode SIGN, and AUTO_SIGN_KEY_ID names the key ADM system. JDOE has no grant for the
key. The block shows the signature, the status of the new version, and the audit entry:
declare l_source adm_documents.document_id%type; l_result adm_documents.document_id%type; l_infos adm_pdf_api.t_signature_infos;begin adm_context_api.system_user_login('JDOE');
select d.document_id into l_source from adm_documents d join adm_folders f on f.folder_id = d.folder_id where f.folder_path = '/users/jdoe/contracts' and d.document_name = 'offer.pdf';
l_result := adm_pdf_api.watermark(l_source, 'DRAFT');
l_infos := adm_pdf_api.verify_signatures(l_result); dbms_output.put_line('signatures: ' || l_infos.count); dbms_output.put_line('key: ' || l_infos(1).signing_key_name); dbms_output.put_line('whole file: ' || case when l_infos(1).covers_whole_file then 'yes' else 'no' end); dbms_output.put_line('status: ' || adm_pdf_api.get_provenance(l_result));
for r in ( select a.action_details from adm_audit_log a where a.document_id = l_result and a.action_details like 'PDF %' ) loop dbms_output.put_line(replace(r.action_details, ', ', chr(10) || ' ')); end loop;end;/signatures: 1key: ADM systemwhole file: yesstatus: ADM_SIGNEDPDF watermark signed by ADM with the key 3063 (ADM system provider DATABASE certificate 370090093DA92F98F5CB3BF77A61235CBDF0E21C30B00C3B02CD2E33907C5512 input SHA-256 B558E5EBE841BEE5CBDCB256755A4E87C9C713D2D4A13787EE290467673BDD04 output SHA-256 197D947076BC21451CDC9C06C0DBD194C2E4C45B001905BFB0D14030BA426CD3 signed digest FBEAE79CEBF63DBA5B752A051FE2CB709E02614393C1B5666F0013C1F713F4A6 time stamp server none): offer.pdf -> offer (watermarked).pdfThe audit entry is the entry of the operation. It names the key, the SHA-256 of the file before the
signature (input) and after it (output), and the digest that the key signed. It holds no key
material. The recording used a key of the provider DATABASE.
Understand the checks of a signed operation
Section titled “Understand the checks of a signed operation”ADM fails closed. When it cannot sign, the operation fails, and ADM stores nothing:
- Before a file is read, ADM makes sure that
AUTO_SIGN_KEY_IDnames a key that is active and has a certificate that is valid now, and that AOP is 26.1.2 or later. These are the checks ofcreate_policy. Otherwise the call raisesadm_error.e_signing_key_unusable,adm_error.e_signing_key_not_foundoradm_error.e_aop_too_old. - After AOP has made the signed file, ADM makes sure that the new signature is valid for the
certificate of the key and covers the whole file. Otherwise the call raises
adm_error.e_signature_invalid.
A disabled key or an expired certificate stops every PDF operation in the SIGN folders. This is
the output of the watermark above when the key ADM system is disabled:
ORA-20272: Signing key ADM system cannot be used: it is disabledThe signatures of the sources follow these rules:
- A source that ADM signed (
ADM_SIGNED) needs nop_invalidate_signatures. The new signature replaces the old one. When the source has one signature, AOP gets the unsigned base: the bytes in front of that signature. So the result holds no data of the old signature. ADM uses the base only when the base has no signature. When the audit entry of the old signature exists, the SHA-256 of the base must also be theinput SHA-256of that entry. If a check fails, AOP gets the signed file, and the audit entry says(1 signature(s) invalidated). - A source with a signature of another certificate, or a source that was changed after ADM signed
it (
MODIFIED), is refused withadm_error.e_pdf_signed, as in every folder. Passp_invalidate_signatures => trueto accept that its signature is lost. ADM signs the result. - A source with two or more valid signatures is refused before it goes to AOP, also with
p_invalidate_signatures => true. AOP cannot sign a changed copy of such a file.
This is the error for a PDF with two valid signatures, from the same recorded run:
ORA-20290: Document 15090451595079264306803316911089 has 2 valid signatures. ADM cannot sign a changed copy of a PDF with two or more valid signatures, and the signing policy of the target folder requires a signature.The audit entry of an operation with a replaced signature says (1 ADM signature(s) replaced).
After the store, ADM runs the hook AFTER_PDF_SIGNED, see
Run code after ADM signs a PDF.
Sign the saves of the PDF editor
Section titled “Sign the saves of the PDF editor”The PDF editor on the file details page (pages 10 and 13) saves a new file. It does not change the
old file. In a folder with the mode SIGN, ADM signs the saved file with the key of
AUTO_SIGN_KEY_ID before it stores the new version. A new PDF that a user makes in the editor is
signed in the same way. The rules are the rules of a PDF operation:
- ADM fails closed. A key that cannot sign, or a new signature that is not valid, makes the save fail, and ADM stores no version.
- The audit entry names the key and the hashes. It starts with
PDF editor save. - After the store, ADM runs the hook
AFTER_PDF_SIGNEDwith the ID of the new version. - The merge window of
VERSION_MERGE_WINDOW_MINUTESapplies. A second save of the same user in the window replaces the version of the first save. ADM signs each save, so the latest version is always signed.
The browser writes the file again before it sends it. ADM cannot see what the user changed. So ADM decides about the signatures from the version that the editor opened, not from the saved file:
| The opened version | What the editor and ADM do |
|---|---|
| No signature | ADM signs the save. |
ADM_SIGNED |
The editor deletes the signature fields after the load, before the user edits. The save has no data of the old signature. ADM signs it, and the audit entry says (1 ADM signature(s) replaced). |
A signature of another certificate, or MODIFIED |
The editor shows a warning that the signature is lost. It keeps the fields. ADM signs the save, and the audit entry says (1 signature(s) invalidated). |
| Two or more valid signatures | The editor shows a warning that a save fails. A save raises adm_error.e_auto_sign_multi_signed, and ADM signs nothing. |
The editor deletes only the signature fields. The other form fields of the PDF stay. In a folder with
the mode OFF or WATCH, the editor deletes nothing and ADM signs nothing. The editor shows the
warning for every signed PDF, because the save makes the signature invalid. Only a user who can edit
the document sees a warning.
adm_pdf_api.get_editor_signature_info returns the facts that the editor page reads. Any user with
view rights can call it.
Know what the signature proves
Section titled “Know what the signature proves”The signature proves that the key signed these exact bytes. A change of a signed byte makes the
check fail. The signature does not prove that the content is true, or who wrote the inputs. A file
can get more bytes behind the signature. Then the signature is still valid for its range, and
covers_whole_file of verify_signatures is false. Require a valid signature that covers the whole
file.
With the provider DATABASE, everyone who can run code as the owner of the ADM schema can sign with
the key. Then a signature of ADM proves little. Use a key of Transit (OpenBao, Vault) or OCI Vault
when the signature must prove that ADM made the file.
Find the PDFs that changed after ADM signed them
Section titled “Find the PDFs that changed after ADM signed them”The view adm_report_pdf_provenance_v
has one row for each version of a PDF document. A row gives the status of the version, the status of
the version before it, and the signing mode of the folder. For the statuses, see
See where a PDF comes from.
The column no_longer_pure_adm is Y in two cases:
- The version is
MODIFIED. An ADM signature is in the file, and a check of it fails. - The version before was
ADM_SIGNED, and this version is not. For example, a user downloaded a signed PDF, changed it, and uploaded it again.
This query lists the documents whose latest version is no longer the file that ADM signed:
select v.folder_path , v.document_name , v.version_number , v.previous_provenance , v.pdf_provenance , v.signing_mode , v.version_created_by , v.version_created_date from adm_report_pdf_provenance_v v where v.no_longer_pure_adm = 'Y' and v.version_status = 'CURRENT' and v.deleted_flag = 'N' order by v.version_created_date desc;Remove the condition on version_status to also get the older versions. version_created_by is the
user who stored the version.
The view reads stored columns. It does not read a file and does not call AOP. A version that ADM did
not classify has the status UNKNOWN. These are the versions from before ADM kept the status, and the
versions in a folder with the mode OFF. adm_pdf_provenance_api.backfill classifies the older
versions. The column
signing_mode is the mode of the folder now, found like adm_signing_api.get_effective_policy finds
it. When a folder moves, its mode can change, but the status of a version does not change.
Change and delete policies
Section titled “Change and delete policies”| Call | Does |
|---|---|
update_policy(p_policy_id, p_folder_path_pattern, p_signing_mode, p_description) |
Changes pattern, mode and description. |
deactivate_policy(p_policy_id) |
The policy is ignored. The folders use the next shorter pattern, or the default mode. |
activate_policy(p_policy_id) |
The policy applies again. |
delete_policy(p_policy_id) |
Deletes the policy. |
list_policies |
Returns all policies with the longest pattern first. |
A pattern that another policy has raises adm_error.e_signing_policy_exists. An ID that no policy has
raises adm_error.e_signing_policy_not_found.
Read the audit entries
Section titled “Read the audit entries”A trigger on adm_signing_policies writes an entry to the audit trail for every change, also
for a change that someone makes with SQL. An update that changes nothing writes no entry. The entries
of the three calls above, and a change of the mode of /contracts/emea/ to OFF, look like this:
CREATE_SIGNING_POLICY: Signing policy 253 created: pattern /contracts/ -> SIGN; active: YCREATE_SIGNING_POLICY: Signing policy 254 created: pattern /contracts/emea/ -> WATCH; active: YUPDATE_SIGNING_POLICY: Signing policy 254 (pattern /contracts/emea/) updated: mode WATCH -> OFF; description changed