add ability to use timestamp server
This commit is contained in:
@@ -71,6 +71,14 @@ module Accounts
|
||||
end
|
||||
end
|
||||
|
||||
def load_timeserver_url(account)
|
||||
if Docuseal.multitenant?
|
||||
Docuseal::TIMESERVER_URL
|
||||
else
|
||||
EncryptedConfig.find_by(account:, key: EncryptedConfig::TIMESTAMP_SERVER_URL_KEY)&.value
|
||||
end.presence
|
||||
end
|
||||
|
||||
def can_send_emails?(_account)
|
||||
return true if Docuseal.multitenant?
|
||||
return true if ENV['SMTP_ADDRESS'].present?
|
||||
|
||||
@@ -28,6 +28,7 @@ module Docuseal
|
||||
end
|
||||
|
||||
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
||||
TIMESERVER_URL = ENV.fetch('TIMESERVER_URL', nil)
|
||||
|
||||
DEFAULT_URL_OPTIONS = {
|
||||
host: HOST,
|
||||
|
||||
@@ -32,6 +32,7 @@ module Submissions
|
||||
def call(submission)
|
||||
account = submission.template.account
|
||||
pkcs = Accounts.load_signing_pkcs(account)
|
||||
tsa_url = Accounts.load_timeserver_url(account)
|
||||
verify_url = Rails.application.routes.url_helpers.settings_esign_url(**Docuseal.default_url_options)
|
||||
|
||||
composer = HexaPDF::Composer.new(skip_page_creation: true)
|
||||
@@ -254,10 +255,16 @@ module Submissions
|
||||
|
||||
composer.document.trailer.info[:Creator] = INFO_CREATOR
|
||||
|
||||
composer.document.sign(io, reason: SIGN_REASON,
|
||||
certificate: pkcs.certificate,
|
||||
key: pkcs.key,
|
||||
certificate_chain: pkcs.ca_certs || [])
|
||||
sign_params = {
|
||||
reason: SIGN_REASON,
|
||||
certificate: pkcs.certificate,
|
||||
key: pkcs.key,
|
||||
certificate_chain: pkcs.ca_certs || []
|
||||
}
|
||||
|
||||
sign_params[:timestamp_handler] = Submissions::TimestampHandler.new(tsa_url:) if tsa_url
|
||||
|
||||
composer.document.sign(io, **sign_params)
|
||||
|
||||
ActiveStorage::Attachment.create!(
|
||||
blob: ActiveStorage::Blob.create_and_upload!(
|
||||
|
||||
@@ -29,6 +29,7 @@ module Submissions
|
||||
|
||||
account = submitter.submission.template.account
|
||||
pkcs = Accounts.load_signing_pkcs(account)
|
||||
tsa_url = Accounts.load_timeserver_url(account)
|
||||
|
||||
pdfs_index = build_pdfs_index(submitter)
|
||||
|
||||
@@ -190,7 +191,9 @@ module Submissions
|
||||
submitter.submission.template_schema.map do |item|
|
||||
pdf = pdfs_index[item['attachment_uuid']]
|
||||
|
||||
attachment = save_signed_pdf(pdf:, submitter:, pkcs:, uuid: item['attachment_uuid'], name: item['name'])
|
||||
attachment = save_signed_pdf(pdf:, submitter:, pkcs:, tsa_url:,
|
||||
uuid: item['attachment_uuid'],
|
||||
name: item['name'])
|
||||
|
||||
image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
||||
|
||||
@@ -217,15 +220,21 @@ module Submissions
|
||||
end
|
||||
# rubocop:enable Metrics
|
||||
|
||||
def save_signed_pdf(pdf:, submitter:, pkcs:, uuid:, name:)
|
||||
def save_signed_pdf(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:)
|
||||
io = StringIO.new
|
||||
|
||||
pdf.trailer.info[:Creator] = info_creator
|
||||
|
||||
pdf.sign(io, reason: sign_reason(submitter.email),
|
||||
certificate: pkcs.certificate,
|
||||
key: pkcs.key,
|
||||
certificate_chain: pkcs.ca_certs || [])
|
||||
sign_params = {
|
||||
reason: sign_reason(submitter.email),
|
||||
certificate: pkcs.certificate,
|
||||
key: pkcs.key,
|
||||
certificate_chain: pkcs.ca_certs || []
|
||||
}
|
||||
|
||||
sign_params[:timestamp_handler] = Submissions::TimestampHandler.new(tsa_url:) if tsa_url
|
||||
|
||||
pdf.sign(io, **sign_params)
|
||||
|
||||
ActiveStorage::Attachment.create!(
|
||||
uuid:,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submissions
|
||||
class TimestampHandler
|
||||
HASH_ALGORITHM = 'SHA512'
|
||||
|
||||
TimestampError = Class.new(StandardError)
|
||||
|
||||
attr_reader :tsa_url
|
||||
|
||||
def initialize(tsa_url:)
|
||||
@tsa_url = tsa_url
|
||||
end
|
||||
|
||||
def finalize_objects(_signature_field, signature)
|
||||
signature.document.version = '2.0'
|
||||
|
||||
signature[:Type] = :DocTimeStamp
|
||||
signature[:Filter] = :'Adobe.PPKLite'
|
||||
signature[:SubFilter] = :'ETSI.RFC3161'
|
||||
end
|
||||
|
||||
def sign(io, byte_range)
|
||||
digest = OpenSSL::Digest.new(HASH_ALGORITHM)
|
||||
|
||||
io.pos = byte_range[0]
|
||||
digest << io.read(byte_range[1])
|
||||
io.pos = byte_range[2]
|
||||
digest << io.read(byte_range[3])
|
||||
|
||||
uri = Addressable::URI.parse(tsa_url)
|
||||
|
||||
conn = Faraday.new(uri.origin) do |c|
|
||||
c.basic_auth(uri.user, uri.password) if uri.password.present?
|
||||
end
|
||||
|
||||
response = conn.post(uri.path, build_payload(digest.digest),
|
||||
'content-type' => 'application/timestamp-query')
|
||||
|
||||
raise TimestampError if response.status != 200 || response.body.blank?
|
||||
|
||||
OpenSSL::Timestamp::Response.new(response.body).token.to_der
|
||||
end
|
||||
|
||||
def build_payload(digest)
|
||||
req = OpenSSL::Timestamp::Request.new
|
||||
req.algorithm = HASH_ALGORITHM
|
||||
req.message_imprint = digest
|
||||
|
||||
req.to_der
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user