Servei de Signatura Electrònica basada en certificats digitals
Per a poder utilitzar el servei és necessari donar-se d'alta previament, per a fer-ho és necessari facilitar la següent informació:
Existeixen dues formes d'integrar-se amb el servei, la part inicial és comú en ambdues, només canvia la forma en què l'aplicació recupera la signatura. Ambdues formes es descriuen més avall en aquest mateix document. Per tal de donar context i entendre els dos mètodes de funcionament del servei, a continuació es mostren els diagrames de flux d'operació d'una aplicació contra el Signador per a intentar il·lustrar les crides i el funcionament del mateix per ambdós casos:
Per a utilizar el servei de signatura serà necessari la realització de les següents crides:
Cada operació de signatura requerirà d'un token
per tal de poder iniciar el procés. El procés de signatura des del punt de vista de l'aplicació client és un procès asíncron per tant aquest token
servirà per lligar després la signatura resultant amb el procés intern que l'ha requerit dins de l'aplicació client. Aquest token
també identificarà la signatura a nivell intern del servei de Signador per tal de poder per exemple gestionar els errors si fos el cas, etc.
Per tal d'aconseguir el token
s'ha de fer una crida al servei REST ubicat al següent endpoint:
La crida és simplement un GET amb el qual s'han d'enviar obligatòriament les següents capçaleres http (No confondre amb els paràmetres del GET):
dd/MM/yyyy HH:mm
(Exemple: 28/05/2016 13:21)La resposta del servei REST tindrà el següent format:
{
"status": "",
"token": "",
"message": ""
}
Els possibles valors dels camps són:
Es comprovarà que la data proporcionada a la capçalera Date estigui dins del rang now -1 hora < Date < now +1 hora
Nota: Donat que es tracta d'una autenticació aquesta crida no suporta el CORS i per tant la crida s'ha de fer des del backend.
Per a calcular la capçalera d'autorització es fa servir el Message Authentication Code (MAC) basat en una funció de resum criptogràfic (HMAC), en aquest cas com a funció de Hash farem servir SHA256.
En aquest cas les dades a processar serà el mateix nom del domini tal i com s'ha especificat a l'alta, concatenat amb el caràcter underscore _
i la data proporcionada a la capçalera date (exemple http://ajuntament.cat_28/05/2016 13:21
). El secret per a procesar aquesta dada amb l'algoritme HMAC_SHA256
serà la clau que s'ha triat també durant el procés d'alta.
A continuació és mostra un exemple simplificat de com es podria generar la capçalera d'autenticació amb Groovy:
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
def clau = 'changeit'
def algoritme = 'HmacSHA256'
def mac = Mac.getInstance(algoritme)
def domini = 'http://ajuntament.cat'
def date = new Date().format('dd/MM/yyyy HH:mm')
def dades = "${domini}_${date}"
def secretKeySpec = new SecretKeySpec(clau.getBytes(), algoritme)
mac.init(secretKeySpec)
byte[] digest = mac.doFinal(dades.getBytes())
digest.encodeBase64().toString()
De la mateixa forma en Java:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64.Encoder;
import java.text.SimpleDateFormat;
import java.util.Date;
public class BustiaNotificacionsController {
public static void main(String args[]){
String domini = "http://ajuntament.cat";
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
String date = sdf.format(new Date());
String dades = domini + "_" + date;
String clau = "legalizeit";
String algoritme = "HmacSHA256";
Mac mac = Mac.getInstance(algoritme);
SecretKeySpec secretKeySpec = new SecretKeySpec(clau.getBytes(), algoritme);
mac.init(secretKeySpec);
byte[] digest = mac.doFinal(dades.getBytes());
Encoder encoder = java.util.Base64.getEncoder();
System.out.println(encoder.encodeToString(digest));
}
}
Nota: La classe java.util.Base64
existeix a partir de la versió 8 de Java, si es desenvolupa amb una altre versió és pot utilitzat qualsevol altre codificador en Base64 com per exemple el javax.xml.bind.DatatypeConverter
que es troba dins de la versió 6 i 7 de Java. O el org.apache.commons.codec.binary.Base64
del Apache Commons Codec, o tants d'altres.
Proveïm aquests codis a tall d'exemple, per veure exemples en altres llenguatges de programació de com calcular el HMAC podeu consultar el següent recurs
Podeu trobar també un exemple complet en Groovy de com invocar el /initProcess
per aconseguir el token de l'operació aqui
Un cop és diposa del token
per a l'operació de signatura, es pot iniciar el procés. Per tal de fer-ho es necessari associar la configuració de signatura que realitzarà l'usuari amb el token
d'operació obtingut.
Per a fer-ho, s'ha de realitzar una crida al servei REST al següent endpoint:
En aquesta crida també és necessari afegir la capçalera http Origin amb el nom del domini. Si la crida és fa des de Javascript utilitzant domini registrat els pròpis navegadors per un tema de seguretat ja afegeixen la capçalera a la crida, veure CORS.
La crida consisteix en un POST on s'envia un objecte JSON, aquest objecte per a iniciar el procés de signatura amb l'applet té la següent forma. És important remarcar que en funció de si s'informa el camp callbackUrl
o redirectUrl
canviarà la gestió del flux del usuari i la recuperació de la signatura per part de l'aplicació client.
{
"callbackUrl": "" o "redirectUrl": "", // S'ha d'informar o un o l'altre
"token": "",
"descripcio": "",
"responseB64": "",
"applet_cfg":{
"keystore_type": "",
"signature_mode": "",
"doc_type": "",
"doc_name": "",
"document_to_sign": "",
"hash_algorithm": "",
"pkcs11_files": "",
"multiple": "",
"pdf_cfg": {
"pdf_visible_signature": "",
"pdf_reserved_space": "",
"pdf_signature_field": "",
"pdf_certification_level": "",
"pdf_reason": "",
"pdf_location": "",
"pdf_signature_image": "",
"pdf_signature_rectangle": "",
"pdf_signature_page_number": "",
"pdf_signature_rotation": "",
"pdf_show_adobe_sign_validation": "".
"pdf_signature_rendering_mode": ""
},
"certs_cfg": {
"allowed_CAs": "",
"allowed_OIDs": "",
"selected_alias": "",
"selected_CN": "",
"subject_Text": "",
"required_nif": "",
"psis_validation": "",
"keyUsage": ""
},
"xml_cfg": {
"n_enveloping": "",
"n_detached": "",
"uris_to_be_signed": "",
"includeXMLTimestamp": "",
"xmlts_tsa_url": "",
"canonicalizationWithComments": "",
"protectKeyInfo": ""
},
"cms_cfg": {
"timeStamp_CMS_signature": "",
"cmsts_tsa_url": ""
},
"ades_cfg": {
"commitment_identifier": "",
"commitment_description": "",
"commitment_object_reference": "",
"signer_role": "",
"signature_policy": "",
"signature_policy_hash": "",
"signature_policy_qualifier": "",
"signature_policy_hash_algorithm": ""
}
}
}
Al següent apartat és descriu amb més detall l'ús de cadascún d'aquests camps, notis només que la gran part dels mateixos és opcional i no és necessari enviar-los per a poder iniciar el procés correctament.
Podeu trobar també un exemple simple en Groovy de com invocar el /startSignProcess
passant una configuració de signatura d'exemple aqui
Per al cas d'iniciar el procés per a carregar l'applet de PSA, l' objecte JSON a enviar té la següent forma. És important remarcar que en funció de si s'informa el camp callbackUrl
o redirectUrl
canviarà la gestió del flux del usuari i la recuperació de la signatura per part de l'aplicació client.
L'Applet de PSA, també disposa de dos modes de funcionament. Un mode per a recuperar un certificat de clau pública que és retornarà en base64, o el mode de signatura de hashos. El camp que indica quina operació és vol realitzar és el camp modeFuncionament.
{
"callbackUrl": "" o "redirectUrl": "", // S'ha d'informar o un o l'altre
"token": "",
"descripcio": "",
"responseB64": "",
"applet_apsa_cfg": {
"keystore_type": "",
"doc_name": "",
"modeFuncionament":"",
"hash_a_xifrar": "",
"signingCertificate": ""
}
}
Descripció dels camps JSON comuns de la configuració:
query strings
.true
o false
. Per defecte aquest paràmetre pren el valor true
. No és obligatori.Nota: És obligatori informar el camp callbackUrl
o el redirectUrl
, no s'han d'informar els dos.
Descripció dels camps JSON de la configuració de l'applet:
0
. Camp obligatori.SHA-1
. Altres possibles valors: SHA-256
i SHA-512
. Camp no obligatori.pkcs11_files
: Indica la ruta dels drivers necessaris d'un o varis dispositius PKCS11 per a que l'aplicació carregui les claus d'aquests. Les rutes dels drivers s'han d’especificar de la següent manera: pathDriver1,ID1;pathDriver2,ID2;.... El pathDriver és la ruta absoluta del controlador del dispositiu PKCS11. L'ID és una cadena de text arbitrària que l'aplicació utilitza internament per a diferenciar els diferents dispositius. Aquest ID també és el que es mostrarà al popup que demana el PIN amb el text: Introduïu el PIN per a (ID):
[ ]
en el ordre establert fins que en pugui carregar una, un cop carregada la resta ja no es provaran. Per a la configuració es poden combinar les dues formes, per exemple: [pathDriverAVersio1,pathDriverAVersio2],IDA;pathB,IDB. Camp no obligatori.;
(al camp document_to_sign
) amb els seus respectius noms també separat per ;
(al camp doc_name
). El número d'elements d'aquests dos camps ha de coincidir. Els noms dels documents no poden coincidir.Descripció dels camps JSON de la configuració de l'apsa:
En cas que es vulgui signar més d'un document o hash el servei ho permet, posant els diferents documents o hashos separats per ;
(al camp hash_a_xifrar
) amb els seus respectius noms també separat per ;
(al camp doc_name
). El número d'elements d'aquests dos camps ha de coincidir. Els noms dels documents no poden coincidir.
Els possibles valors acceptats del keystore_type són:
Els possibles valors acceptats del signature_mode són:
Els possibles valors acceptats del doc_type són:
L'objecte certs_cfg és opcional i permet especificar filtratges a l'hora de seleccionar el certificat per part de l'usuari. Si el filtre és prou específic (exceptuant el paràmetre keyUsage) per donar només una coincidència; a l'usuari no se li mostrarà el diàleg de selecció de certificats i se seleccionarà de forma automàtica la coincidència, en cas que n'hi hagi més d'un és mostrarà el diàleg de selecció amb els certificats que coincideixin amb el criteri proporcionat:
;
. El filtre és case insensitive. Exemple: “EC-SAFP;EC-idCAT”
.;
.“Director General”
keyUsage: Permet filtrar per l'ús de la clau del certificat. Els possibles valors d'aquest atribut són:
Es poden indicar múltiples entrades separades per punts i comes ;
. El filtre es case insensitive. Per defecte filtra per les dues opcions. Exemple: "NR; FD"
.
false
). Per defecte el valor és true
(visible). Si hi ha camps de signatura, aquest paràmetre no té afectació i es signarà en els camp/s buits de signatura.KBytes
. Per defecte, el valor que pren aquest paràmetre en funció del tipus de signatura és:
100 100 200 200
. Les coordenades s'indiquen de forma numèrica i separades per espais, el valor de les mateixes es correspon a: LowerLeftX LowerLeftY UpperRightX UpperRightY
.0
, o també és possible indicar que sigui visible a totes les planes del mateix especificant el valor -1
.90
,180
,270
(per defecte pren el valor 0
). El gir es fa en sentit anti-horari el nombre de graus especificats.true
o false
. Per defecte aquest paràmetre pren el valor false
i per tant aquesta informació no es mostra.true
. Per defecte el valor és false
. true
. Per defecte el valor és false
. ;
i cal que es corresponguin amb el valor del atribut id
dels <nodes>
del document sobre els que es vol realitzar la signatura. En cas que s'especifiquin identificadors de nodes que no existeixen al document, retornarà un missatge d'error indicant que els atributs no existeixen al document a signar.XMLTimeStamp
o EncapsulatedTimeStamp
. Per defecte aquest paràmetre pren el valor true
que indica que el segell serà de tipus XMLTimeStamp
, si es vol que el tipus sigui EncapsulatedTimeStamp
s'ha de posar el valor del paràmetre a false
.XMLTimestamp
. El seu valor per defecte és el servei de segellat de temps de PSIS per segells de temps de format XML segons l'estàndard definit per OASIS al protocol DSS: http://psis.catcert.net/psis/catcert/dss. És molt important tenir en compte que, en cas de modificar el valor d'aquest paràmetre, cal garantir que el servei de segellat que estem seleccionant treballi segons el protocol corresponent. false
, i per tant ometrà els comentaris. En cas de voler el contrari, el valor del paràmetre a de ser true
.<KeyInfo>
amb la informació de la clau amb la que s'ha realitzat la signatura. Per defecte pren el valor false
.true
. Per defecte el valor és false
.SHA-1
, SHA-256
, SHA-512
.Aquesta crida permet afegir documents a processos de signatura de tipus Applet inicialitzats amb el flag multiple = "true" pels quals encara no s'ha efectuat la signatura. El procés de signatura pot contenir més d'un document, especificat durant la inicialització o bé afegit amb "addDocumentToSign". El nou document a afegir haurà de respectar el format indicat al camp doc_type especificat a la inicialització del procés de signatura, que només pot ser per a aquests casos 3(hashDoc), 4 (B64fileContent) o 6 urlFile. Aquest servei pot ser executat múltiples vegades mentre no es realitzi la signatura. La crida consisteix en un PUT on s'envia un objecte JSON amb la següent forma:
{
"token_id": "",
"document_to_sign": "",
"doc_name": ""
}
Descripció dels camps JSON de la configuració del servei de signatura múltiple:
En aquesta crida també és necessari afegir la capçalera http Origin amb el nom del domini. Si la crida és fa des de Javascript utilitzant domini registrat els pròpis navegadors per un tema de seguretat ja afegeixen la capçalera a la crida, veure CORS.
La resposta del servei REST a aquestes crides tindrà el següent format:
{
"status": "OK/KO",
"token": "12345",
"message": ""
}
Els possibles valors dels camps:
Un cop s'ha aconseguit el token
i creada la configuració de signatura vinculada al mateix, l'aplicació client ha de redirigir l'usuari a la web del Signador per tal de que aquest pugui acabar realitzant la signatura. Per tal de fer-ho s'ha de realitzar un GET passant com a paràmetre un id
amd el valor del token
a la següent URL:
Aquesta plana s'encarregarà de la creació de signatura per part de l'usuari a través d'un JNLP o d'una aplicació nativa que properament estarà disponible.
Si l'usuari té instal·lada l'aplicació nativa la signatura és realitzara amb aquest component, cas que no la signatura es farà a través del JNLP
El temps màxim permès per processar la petició és de 5 minuts. Si el client no ha generat la signatura passat aquest temps, la petició es donarà per finalitzada amb error de timeout.
En l'apartat de compatibilitat s'explica les compatibilitat i el funcionament d'aquests dos mètodes per a realitzar la signatura.
Un cop el client hagi realitzat la signatura a través del JNLP, el servei del signador rebrà la signatura i en funció de la configuració retornarà la signatura d'una forma o un altre. Els paràmetres que marquen la configuració del retorn són callbackUrl
o redirectUrl
, la diferència s'explica a continuació.
redirectUrl
: Redirecció GETEn cas que en el /StartSignProcess
s'hagi informat el paràmetre redirectUrl
, l'aplicació del signador farà una redirecció a la url informada retornant el flux a l'aplicació client. En la url de redirecció, s'afegira el paràmetre token_id
amb el valor del token perquè l'aplicació pugui saber de quina operació és tracta, per exemple https://applicacio/redirect?token_id=bec40de2-510f-4f19-bdfd-2a6595d708b7
.
Un cop la aplicació client prengui el control podrà demanar la resposta de l'operació a través del servei REST /getSignature
descrit a continuació.
getSignature
: Servei REST per consultar el resultat de l'operacióPer tal d'obtenir la resposta de la signatura s'ha de fer una crida al servei REST ubicat a la següent URL:
La crida és simplement un GET passant com a paràmetre un identificador
amb el valor del token
rebut en la url de redirecció, igual que la resta de crides també ha d'incloure les següents capçaleres:
dd/MM/yyyy HH:mm
(Exemple: 28/05/2016 13:21)La resposta d'aquest servei tindrà el següent format.
{
"status": "OK/KO",
"token": "id del token",
"signResult": "resultat de la signatura",
"type": "XML/CMS/PDF/HASH/TXT/ZIP/CERT",
"error": "motiu de l'error",
"urlRedirect" : "http://redirect.cat?token=XXXXXX"
}
Els possibles valors dels camps:
token
informat.En cas que l'operació sigui de Multisignatura, es a dir que el client faci varies signatures en una mateixa operació, la resposta del servei tindrà una unica resposta amb el token
igual que es fa amb signatures simples. La diferència serà que en aquesta cas la resposta serà un document ZIP que contindrà les diferents signatures generades.
L'altre cas singular, és el de l'Applet de PSA en mode CERTIFICAT, en el qual el type tindrà el valor CERT i en el signResult recuperarem el certificat seleccionat per l'usuari en base64.
NOTES:
callbackUrl
: Callback POSTA diferència de l'opció 1, en cas que l'aplicació client hagi informat el paràmetre callbackURL
, quan l'usuari hagi finalitzat la signatura el servei respondrà a l'aplicació client utilitzant la URL de callback que s'hagi informat en els paràmetres de configuració i facilitarà la signatura en aquell endpoint via POST. El servei retornarà la resposta amb la signatura generada en cas que hagi anat bé o el motiu de l'error en cas que no.
El format del JSON que enviarem a l'endpoint informat será el següent:
{
"status": "OK/KO",
"token": "id del token",
"signResult": "resultat de la signatura",
"type": "XML/CMS/PDF/HASH/TXT/ZIP/CERT",
"error": "motiu de l'error"
}
Els possibles valors dels camps:
Serà necessari per tant per part de l'aplicació client d'implementar un endpoint que accepti rebre un POST amb el contingut del JSON especificat en aquesta punt. Amb la resposta anirà la capçalera http Content-Type: application/json
.
En cas que l'operació sigui de Multisignatura, es a dir que el client faci varies signatures en una mateixa operació, l'aplicació rebrà una unica resposta amb el token
igual que es fa amb signatures simples. La diferència serà que en aquesta cas la resposta serà un document ZIP que contindrà les diferents signatures generades.
NOTES:
Per alleugerir el pes de la response a getSignature
per l'opció 1: redirectUrl
, o del POST en el cas de l'opció 2: callbackUrl
és possible iniciar el procés indicant en el paràmetre responseB64
amb valor false
, d'aquesta forma en la resposta de l'operació es rebrà en el signResult
una URL en comptes del resultat en Base64, amb la qual es podrà descarregar la resposta realitzant simplement un GET incloent les següents capçaleres http:
dd/MM/yyyy HH:mm
(Exemple: 28/05/2016 13:21)D'aquesta forma és podrà reduïr en els casos necessaris el pes de la resposta i agilitzar la comunicació.
NOTES:
La primera solució implementada va ser la Opcio 2: callbackUrl
: Callback POST, desprès però de veure les necessitats de les aplicacions, la problemàtica que genera aquesta solució (possibles errors de timeout en el POST de resposta, polling ajax de l'aplicació client per tal de mantenir l'estat de l'operació, ...) i el fet de que alguns clients ens han traslladat el seu neguit al respecte s'ha decidit implementar l'altre via: Opcio 1: redirectUrl
: Redirecció GET aquesta via és més neta, genera menys trafic i per tant té un millor rendiment, i permet un millor flux de cara a l'usuari per a la gestió de la signatura. Per tant recomanem en la mesura del possible utilitzar la opció del redirectUrl
.
Podeu veure una Demo d'una integració del servei amb les dues modalitats als següents enllaços:
Opcio 1: redirectUrl
: Redirecció GET
Opcio 2: callbackUrl
: Callback POST
A banda de la Demo a tall d'exemple també es mostren els enllaços del Signasuite que és un servei de validació/creació de signatures etc. que properament estarà també integrat amb el servei del signador:
25
per operació de signatura, o fins un màxim de 50
en cas que aquest siguin resums criptogràfics (doc_type=3
).7MB
. Per tant s'ha de tenir en compte aquesta restricció a l'hora de passar documents grans codificats en base64 dins del camp document_to_sign
.En aquest apartat podreu trobar els enllaços a la informació sobre la pròpia aplicació així com les eines que portaran a terme la signatura en la màquina d'usuari, en aquestes guies s'explica la compatibilitat del servei i de les eines pel que fa a versions de sistemes operatius, navegadors, etc que suporta:
Per a facilitar el procés d'integració posem a disposició dels integradors una llibreria feta en Javascript per a poder-se integrar en el servei. Trobareu més detall sobre la mateixa aqui.