Source code for AIS.ais
# -*- coding: utf-8 -*-
"""
AIS.py - A Python interface for the Swisscom All-in Signing Service.
:copyright: (c) 2016 by Camptocamp
:license: AGPLv3, see README and LICENSE for more details
"""
import base64
import json
import uuid
import requests
from . import exceptions
from typing import Any
from typing import Dict
from typing import Optional
from typing import Sequence
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .pdf import PDF
url = 'https://ais.swisscom.com/AIS-Server/rs/v1.0/sign'
[docs]class AIS:
"""Client object holding connection information to the AIS service."""
last_request_id: Optional[str]
"""Contains the id of the last request made to the AIS API."""
def __init__(
self,
customer: str,
key_static: str,
cert_file: str,
cert_key: str
):
"""Initialize an AIS client with authentication information."""
self.customer = customer
self.key_static = key_static
self.cert_file = cert_file
self.cert_key = cert_key
self.last_request_id = None
def _request_id(self) -> str:
self.last_request_id = uuid.uuid4().hex
return self.last_request_id
[docs] def post(self, payload: str) -> Dict[str, Any]:
""" Do the post request for this payload and return the signature part
of the json response.
"""
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
}
cert = (self.cert_file, self.cert_key)
response = requests.post(url, data=payload, headers=headers,
cert=cert, timeout=(10, 5))
sign_resp = response.json()['SignResponse']
result = sign_resp['Result']
if 'Error' in result['ResultMajor']:
raise exceptions.error_for(response)
return sign_resp
[docs] def sign_batch(self, pdfs: Sequence['PDF']) -> None:
"""Sign a batch of files."""
# Let's just return if the batch is empty somehow
if not pdfs:
return
# Let's not be pedantic and allow a batch of size 1
if len(pdfs) == 1:
return self.sign_one_pdf(pdfs[0])
payload_documents = {
'DocumentHash': [
{
'@ID': index,
'dsig.DigestMethod': {
'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'
},
'dsig.DigestValue': pdf.digest()
}
for index, pdf in enumerate(pdfs)
]
}
payload = {
'SignRequest': {
'@RequestID': self._request_id(),
'@Profile': 'http://ais.swisscom.ch/1.1',
'OptionalInputs': {
'AddTimestamp': {
'@Type': 'urn:ietf:rfc:3161'
},
'AdditionalProfile': [
'http://ais.swisscom.ch/1.0/profiles/batchprocessing'
],
'ClaimedIdentity': {
'Name': ':'.join((self.customer, self.key_static)),
},
'SignatureType': 'urn:ietf:rfc:3369',
'sc.AddRevocationInformation': {
'@Type': 'BOTH'
},
},
'InputDocuments': payload_documents
}
}
payload_json = json.dumps(payload, indent=4)
sign_resp = self.post(payload_json)
other = sign_resp['SignatureObject']['Other']['sc.SignatureObjects']
for signature_object in other['sc.ExtendedSignatureObject']:
signature = base64.b64decode(
signature_object['Base64Signature']['$']
)
which_document = int(signature_object['@WhichDocument'])
pdfs[which_document].write_signature(signature)
[docs] def sign_one_pdf(self, pdf: 'PDF') -> None:
"""Sign the given pdf file."""
payload = {
'SignRequest': {
'@RequestID': self._request_id(),
'@Profile': 'http://ais.swisscom.ch/1.1',
'OptionalInputs': {
'AddTimestamp': {
'@Type': 'urn:ietf:rfc:3161'
},
'AdditionalProfile': [],
'ClaimedIdentity': {
'Name': ':'.join((self.customer, self.key_static)),
},
'SignatureType': 'urn:ietf:rfc:3369',
'sc.AddRevocationInformation': {
'@Type': 'BOTH'
},
},
'InputDocuments': {
'DocumentHash': [{
'dsig.DigestMethod': {
'@Algorithm':
'http://www.w3.org/2001/04/xmlenc#sha256'
},
'dsig.DigestValue': pdf.digest()
}],
}
}
}
sign_response = self.post(json.dumps(payload))
signature = base64.b64decode(
sign_response['SignatureObject']['Base64Signature']['$']
)
pdf.write_signature(signature)