BankID B2B Signer¶
Introduction¶
The B2B BankID Signer API is a RESTful API that allows you to sign documents using BankID merchant certificates. The API is designed for businesses that need to sign documents on behalf of their customers.
The merchant certificates are under the control of the BankID OIDC. An access token built from the BankID OIDC token endpoint with the scope "esign/b2b" is required to sign documents.
The API is versioned, and the current version is v0
.
The operations on the API come in different flavors: parameters containing only hashes of the documents to be signed or parameters containing documents to be signed.
SDOs returned will contain the signed document for the document case, and for the hash case, this document will of course be missing.
Access token requirements for BankID B2B Signing API¶
To access the B2B signing API, the security demands are higher than for starting a signing process for an individual. The security requirements are strengthened in two directions:
- A stolen access token should not be usable to start B2B signing.
- OIDC clients must prove that they own their
client_id
in a more secure way thanclient_secret
To meet these demands, the B2B API requires use of the DPoP protocol and the private_key_jwt
authentication method:
- To access the BankID B2B Signing API, the client must use the DPoP protocol, which is a security protocol that provides proof of possession of the access token.
- The access token must be obtained using the DPoP flow, and when used in the B2B Signing API, it must be accompanied by a DPoP proof.
- In the production environment, the access token must be obtained using the
private_key_jwt
method. BankID OIDC does not call out, so a public key must be registered for the client when ordering the OIDC client. - In the preprod environment, for now, the client can use the
client_secret_basic
method.
Using the DPoP protocol to obtain the access token is a requirement that is connected to the BankID OIDC client, and not the scopes asked for. The DPoP requirement will be registered on the OIDC client.
This implies that if the client is allowed to use the esign/b2b
scope, all other scopes also require the access token to be obtained using the DPoP flow.
BankID OIDC sets the type of the access token in the access token's typ
attribute, "typ":"DPOP"
or "typ":"Bearer"
, depending on the method used to obtain it.
See RFC9449 for more information about the DPoP protocol.
The private_key_jwt
is described in https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
The same applies to client authentication: all authentication for that client must be done using the private_key_jwt
method (in production).
Must DPoP be used against the Signdoc API?¶
- You may use the DPoP protocol for both SignDoc and B2B APIs, but it is only required for the B2B API.
- The B2B signing API requires that the access token is sent in the
Authorization
header as aDPoP
token together with aDPoP
header for the proof. - The Signdoc API allows the access token to be sent in the
Authorization
header as aBearer
token, even if it internally has"typ":"DPoP"
.
Do I need one OIDC client_id
for B2B and one for other operations?¶
Since B2B raises new security demands and there may be other applications working with Bearer and client_secret
instead of DPoP and private_key_jwt
,
it is certainly a solution to order one separate BankID OIDC client to be used for B2B.
Alternatively, security must be strengthened for all applications, but there is a little catch here:
- There is a possibility that some resource servers which depend on the access token will fail when receiving a DPoP token or a DPoP-based access token; this must be checked.
It is certainly easier and less error-prone to have one OIDC client for B2B usage and one for other "things". This B2B client should, of course, be mapped to the same BankID merchant certificate as the other OIDC client.
Example code¶
bankid-esign-b2b repository demonstrates how to use the BankID B2B Signing API.
Validation of SDOs¶
The NETS SDO validator will be able to validate SDOs produced containing the signed document. Validation will fail if the signed document is not part of the SDO.
The document itself is not part of the sealed or signed data in the SDO, only the hash of the document is. Therefore it is possible to add the document to the SDO after it has been signed.
To add the document to the SDO, the following outlines a solution (and the document should then validate in the NETS SDO validator):
public class Main {
static String[] bytesSigned = "ueaouea".getBytes(StandardCharsets.ISO_8859_1);
static String sdoWithoutDocumentB64 = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48U0RPTGlzdCB4bWxucz0iaHR0cDovL3d3dy5ucHQubm8vc2VpZC94bWxza2plbWEvU0RPX3YxLjAiIHhtbG5zOlhBZEVTPSJodHRwOi8vdXJpLmV0c2kub3JnLzAxOTAzL3YxLjIuMiMiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIj48U0RPPjxTRUlEU0RPVmVyc2lvbj4xLjA8L1NFSURTRE9WZXJzaW9uPjxTRE9EYXRhUGFydD48U2lnbmF0dXJlRWxlbWVudD48Q01TU2lnbmF0dXJlRWxlbWVudD48U0RPUHJvZmlsZT5TRUlELVNETy1CYXNpYy1WPC9TRE9Qcm9maWxlPjxTaWduYXR1cmVQb2xpY3lJZGVudGlmaWVyPjxTaWduYXR1cmVQb2xpY3lJZD48U2lnUG9saWN5SWQ+PFhBZEVTOklkZW50aWZpZXI+dXJuOm9pZDoyLjE2LjU3OC4xLjE2LjQuMTwvWEFkRVM6SWRlbnRpZmllcj48L1NpZ1BvbGljeUlkPjwvU2lnbmF0dXJlUG9saWN5SWQ+PC9TaWduYXR1cmVQb2xpY3lJZGVudGlmaWVyPjxTaWduZXJzRG9jdW1lbnRGb3JtYXQ+PE1pbWVUeXBlPnRleHQvcGxhaW48L01pbWVUeXBlPjwvU2lnbmVyc0RvY3VtZW50Rm9ybWF0PjxIYXNoZWREYXRhPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJTSEEtMjU2Ii8+PGRzOkRpZ2VzdFZhbHVlPnNlWkVEWWJyMkhEN0RjaVU4QVFlZjltTHB0Ky9nOWUxTFloL0J0S2lDTGM9PC9kczpEaWdlc3RWYWx1ZT48L0hhc2hlZERhdGE+PENNU1NpZ25hdHVyZT5NSUlPQkFZSktvWklodmNOQVFjQ29JSU45VENDRGZFQ0FRRXhEVEFMQmdsZ2hrZ0JaUU1FQWdFd0N3WUpLb1pJaHZjTkFRY0JvSUlMaHpDQ0JSa3dnZ01Cb0FNQ0FRSUNBeUhnNERBTkJna3Foa2lHOXcwQkFRc0ZBREJoTVFzd0NRWURWUVFHRXdKT1R6RVZNQk1HQTFVRUNnd01WR1Z6ZEVKaGJtc3hJRUZUTVJJd0VBWURWUVFMREFreE1qTTBOVFkzT0RreEp6QWxCZ05WQkFNTUhrSmhibXRKUkNBdElGUmxjM1JDWVc1ck1TQXRJRUpoYm1zZ1EwRWdNekFlRncweU1qQTFNVEl4TURRNE1UUmFGdzB5TmpBMU1USXhNRFE0TVRSYU1Ha3hHREFXQmdOVkJHRU1EMDVVVWs1UExUYzNOemMzTnpjM056RUxNQWtHQTFVRUJoTUNUazh4RlRBVEJnTlZCQW9NREVKaGJtdEpSQ0JVYjI5c2N6RVNNQkFHQTFVRUJSTUpOemMzTnpjM056YzNNUlV3RXdZRFZRUUREQXhDWVc1clNVUWdWRzl2YkhNd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUM3QnBPUGdMc2F1NVBObWZxblptMmtJNzZVRmVPN1NpWjUzZDAzdkgyakU1ZzFsYi9SdE83bnJiTmZ4MnZuSEloM1RITFJBRzc4QzVPNWUrZ2lMV3dnSjhDeDF2SVBZVUtOTFh1MXJUdHBoUm03M1F4M3RqWTBIOUJQRU1Fb3dPY3lNcEhYb3pjUnpUWC9zWmIrb2dwM1ZKSzZEcHVEbFFxdjVXOHpkWU1oY2dkOXJ5OGtFWlRhWC8wbVVlWjlzV2ZpTm1xczlKaTREM0dyK24zS3RqN3g0eDdWaXpaYzZ2RjJZemJzS3pQVDJ3T0cweFJaZFlXbU5XcHBFai9GelBoR21qeGRUb1ZObTJHTlZjR2pyUUYzTHNmSDlDWHRlbkxWT3c2VTRVVVlkc3JON2lENmpackNkaEpwdm9pL3lLQTYzNDN4MWVQSHBrb2RMVkRtUmJ3cEFnTUJBQUdqZ2RFd2djNHdFd1lIWUlSQ0FSQUNBUVFJTUFZRUJEazVPREF3RkFZSFlJUkNBUkFDQWdRSk1BY0VCVUpKVGtGVE1Ea0dDQ3NHQVFVRkJ3RUJCQzB3S3pBcEJnZ3JCZ0VGQlFjd0FZWWRhSFIwY0hNNkx5OTJZUzF3Y21Wd2NtOWtNUzVpWVc1cmFXUXVibTh3RmdZRFZSMGdCQTh3RFRBTEJnbGdoRUlCRUFFR0FRRXdEZ1lEVlIwUEFRSC9CQVFEQWdaQU1COEdBMVVkSXdRWU1CYUFGQUZwa3lDY0dEVUZPeHptNXN5elBDeXBXb1h1TUIwR0ExVWREZ1FXQkJUNHY4TnFWdThpc3o4dG5XYUJWUTJaUG9SVytEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFJc0x4UGtTYkhXVGpPclFkbjd4TXorRVgrbHRnaDZDUXVyV3lyZ2ZySDNOaVBhOGdrZzNMQlhTck8zWTE5aEZnaGZmRnNWMzBiR2dTeWxSM2IxbnVCUzAzU0FQTEhCMjBiQ09SNXo0SEgwU0VzbVRpYTBqWDBxQTlhZm5JSTUvUFdnSWZKcmNYMUdZQkZwa3Q5ZEM0WSt2aU9hSmkvZ3R1R1dkR3IwS1FpWDhUQU1iS1o5ZUZyTUlOOFczMVFMM2oyMkN1TlliVzVNVkJ1OEozVERLM1RENHUvVWVxRjlOVWRsRW5xWXpxaHNHNEdsRGdYWldMTVFST1dZYUhGQkJZb091cDRjNkJPN1JDMnVkczMvZmMxc1Fpc21waGlmQlpFTVBHZFE5UVc1RVFQV1FsSC9Td0pqREQ5Q2ErM1NCeGJGL2pjVENqdnhEMGJ5bE9NejhSbHBQY2NZdUtZNklLUzZCRVljZEFicGlWK1g0U2FMU05FZnJQaVdadHRIVGdQR2MxR2wzblFqQVFnMHA5R1lndkZ3bEQrU0FmSUtlN01ZR1pPdUhHa0xkNXhyQko5cXNCbHJFdC9HcUgvZHdGQzY4NVZUcFg0RlpIL3gwNUVXbXhvMnY3Ym5JcURhTjVlUUxRRnNhdWNEcXJoZDVoQklVOFoxcTRlNEtQL1o5Mi80eElwM0YzNG1pcXkyVEk4NUM3SWplTkZkanBwS05IMXRjcUN1QzU3WFRNNTJIdjBOR0dsMHBoOTZnQngxaXlFdTBMajJrOGc4N2p0UzVCMEN6U0NZVEVDL2o4VXE3bXEzdjUrNXBINmRTeGNyYTlJWFBZNGF1K1dZWTFubjNkM3ZWZkdmYklsWjIyemZXOUs0M1FyZHdkRDIwK1ErUDBLcXlsNC8rTVp1b3dnZ1ptTUlJRVRxQURBZ0VDQWdJRjN6QU5CZ2txaGtpRzl3MEJBUXNGQURCeE1Rc3dDUVlEVlFRR0V3Sk9UekVxTUNnR0ExVUVDZ3doVkVWVFZDQXRJRVpPU0NCdlp5QlRjR0Z5WldKaGJtdG1iM0psYm1sdVoyVnVNUll3RkFZRFZRUUxEQTFVUlZOVUlDMGdRbUZ1YTBsRU1SNHdIQVlEVlFRRERCVlVSVk5VSUMwZ1FtRnVhMGxFSUZKdmIzUWdRMEV3SGhjTk1UZ3hNVEl3TURreU5qUXlXaGNOTXpBeE1USXdNRGt5TmpReVdqQmhNUXN3Q1FZRFZRUUdFd0pPVHpFVk1CTUdBMVVFQ2d3TVZHVnpkRUpoYm1zeElFRlRNUkl3RUFZRFZRUUxEQWt4TWpNME5UWTNPRGt4SnpBbEJnTlZCQU1NSGtKaGJtdEpSQ0F0SUZSbGMzUkNZVzVyTVNBdElFSmhibXNnUTBFZ016Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU9MQko5ekVUYUpHQUpmaDMvWnprNVhwWHVpelhaRTYwZVVRa0x4S2VwdlJtSGhzbVFKQ3prV0NlK0R0ckNnbktwV2FRbFV6OVh2Qlh3SlpoQ3JpOFJHMGcrU1RPODFyNnNiYkN0SmxCZGV0VGZpdjY5Z3krRWx3T1ExRFFuR1FuaDc5eWJZM3doYjJxMEc3WW51R0pvOERBalVHMUVYb3hqS0pacXhjdWNSMUZIelpiUEowL05SQTdlYXV2N3E3N2NLZjFnRlY0bnU1L3lNcEtrNHBIaGRqNVIwdHc2d3B0V0sxQjVhU1VadzYzaHdVd0tYSXY1andRaDkxOXFFd1krenlqL2hGSTVDdUxaQWdtT1RMeXBVb1dybjR0MHdDeVErMTFYRlpaQWRpa2lHdjE1WXUrZXV3MFFraitIMHltQVNzaCtTTkl2NGVRallhV2d6TEJkQnZEMUZ5WW1zOExSNThaUHhNQUNvT240emZTQW5IM3gxejhib3UxM2grOG85WUxkeVVLWkVUZkkrZnUrSzFlWUp2RDJ5TExoWGhlRkRXZDlMcGFxdjdNdHlFYzRrUkdPTXR1cXVVMmk1SDRTeU9jbHcrVFlkK0d3cDBFcisxak85RnkzSUdWU3J6dDVpTlBIQTdkVlJTajkrVlhwWXJBNmw2Z1Z2cThMQTBaL2FhZ0hRelV6KzU2QnpqeVRKa0JlMDVsdTJsYkNNQTRQNEs4MkVTVXA2Y1hKZi94NlgxSFhxU1pzUU9hVld0WUNFck1Ha2hXUzNYOEh2ZDl2WDhsSjBYazNuVytDZ01HdmpmMzZ6VDUzbTVTbFZneW8wT3ZIVVh2WXlUQ1R0UjJMUlczZ2RhTWd5RkVib3N5TmdyNHowOTlteldlQUJseTBGK0hOYk1JckYvQWdNQkFBR2pnZ0VXTUlJQkVqQVNCZ05WSFJNQkFmOEVDREFHQVFIL0FnRUFNQlVHQTFVZElBUU9NQXd3Q2dZSVlJUkNBUkFCQXdFd1R3WUlLd1lCQlFVSEFRRUVRekJCTUQ4R0NDc0dBUVVGQnpBQ2hqTm9kSFJ3Y3pvdkwyTnlkQzF3Y21Wd2NtOWtMbUpoYm10cFpDNXVieTkwWlhOMExXSmhibXRwWkhKdmIzUmpZUzVqY25Rd0RnWURWUjBQQVFIL0JBUURBZ0VHTUI4R0ExVWRJd1FZTUJhQUZOTkdWeXQzMG1MdmE4d3p4eHhjZmpPVktxSWVNRVFHQTFVZEh3UTlNRHN3T2FBM29EV0dNMmgwZEhCek9pOHZZM0pzTFhCeVpYQnliMlF1WW1GdWEybGtMbTV2TDNSbGMzUXRZbUZ1YTJsa2NtOXZkR05oTG1OeWJEQWRCZ05WSFE0RUZnUVVBV21USUp3WU5RVTdIT2JtekxNOExLbGFoZTR3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUdZRUkvWi9uNnpOM3VoNXIwaW9JUkpFbno3amZaVlhyNWpQcit5Sys5VDFDZUhMN3VOWHA4dGxMUCtnVm9IK2h1aWw3TDh1cW5TR2c0cTBYZFVHZUhyTEZQUlhqSEdTSVRxQzZiZ2puamFMcWdwSDN2cVdLTGtFYlE3M21QcDhWQmY1ZE1kNVRNYnhFaVVuenBvZkRZdFFGWGFnVGN5WUlkMGlUbG5xeTF0cS9aR2FGUE5mUXRZUlFVQkZUMzRVWE9Qc212U2d1NnhDUk40a2hYZTViKzQzSnlySTRidGlrcktNTlEwTmdhMkhoNFByd1Z3a2xCNWllWTdPS2lUSE8yWTBUczYrTFVoa2l0dzlQV0pPTTNoOXpTMXZCQlNkSzYxNGNtakN6TG4zQXA5a1VsM3BCUEVEMHdsUlFpbjdJYWpYWGZuN3pWQ0VCeVFXWDh4TFROVUNqNHdJZlBqam55YzVPQnJOcUI1aU82VGxTUHdHZ0FWNi84aTRFd0NZTXJkamVkOFA0M01LRWY5Vlh5R1FoZkMxOFhQQVNZam9sSERQaHBsVVljWUJYNlZYUlYyeWFMTGx4cUF3Zy9YUkZURENZMUpRMnY2V25JbjNIMGpBWU9OZ0VTYWY0bWRpc2l0cUp3VWtrN3A5Y3kwTkJQNkQ1NUV2c3FHenhtQ0Y5dEpOdDBCNUc1anVBZGVSNjVpa29qTjBzY3I3RmFxL1llMEJCeW1GSFVBcEkxZVBRQlBmM09MMGo1eVFkKy9WVWxvYlhrTlExcTEwK1dHQkFmaUN2eVc1WklERHkyTTNPUlRta3h0ei9VblN0U05CTGVvUW5DU01CUUxvSHo3cnBnSnE0YzBQSnNoa0FOa0dGUEtVOXJsMW9BblU5UngxUVVQazBiUkd3SS9HTVlJQ1F6Q0NBajhDQVFFd2FEQmhNUXN3Q1FZRFZRUUdFd0pPVHpFVk1CTUdBMVVFQ2d3TVZHVnpkRUpoYm1zeElFRlRNUkl3RUFZRFZRUUxEQWt4TWpNME5UWTNPRGt4SnpBbEJnTlZCQU1NSGtKaGJtdEpSQ0F0SUZSbGMzUkNZVzVyTVNBdElFSmhibXNnUTBFZ013SURJZURnTUFzR0NXQ0dTQUZsQXdRQ0FhQ0JzVEFZQmdrcWhraUc5dzBCQ1FNeEN3WUpLb1pJaHZjTkFRY0JNQndHQ1NxR1NJYjNEUUVKQlRFUEZ3MHlOVEF5TWpjeE1USXdOVGxhTUM4R0NTcUdTSWIzRFFFSkJERWlCQ0N4NWtRTmh1dlljUHNOeUpUd0JCNS8yWXVtMzcrRDE3VXRpSDhHMHFJSXR6QkdCZ3NxaGtpRzl3MEJDUkFDRXpFM01EVXdNekF4TUM4d0N3WUpZSVpJQVdVREJBSUJCQ0N5WXMvdHp5UGw2SkFiM25xLzB3dE4yV3g0UndBQ0Fva2JVRFlJaS81aFZUQUxCZ2txaGtpRzl3MEJBUUVFZ2dFQW95QWpvbHFkUUcwcW8wbmZOUXVXNTVYYkg0d1kyK3EvTHYrQzQ4N01yaFkxYnNEOHlqcmZzcjZLS0FNdkdoQkNCUXBFTlpXTGlOYlRCWGVNcHkyUWhYekZwb1dUdjNnUnl6eDRPNkUrcWtQdXlzL05VWVdVUkJwS0NoMHlCeDMzK0FCMjQzVCtXMUNKTW41N1dLSnJhWEJzSDVKVmNFRFNTZFFJT0lkNEpiYzJQNjJ0dWhBSXQxMkc5N1VoZGtpRm85Wk5ZSkkxa0RrT3o1Y2habkdNYktoWUpnSVZwWFp5NHRQaVJZcm03Ly8zZEJROHhXRVdFRVVpSEVhdURZYU5QcGJtUVZXbjBuTDVBMVY0R1FUNG5SMmo3WE82eVBJdTRIekdGeDhTSGkwRFJEY3BLN292TkV3MEZFUjJXMUFNWXQrZU5iZkIrcWVMcFU0N1NIL29xZz09PC9DTVNTaWduYXR1cmU+PFVzZXJDZXJ0aWZpY2F0ZUFuZFJldm9jYXRpb25EYXRhPjxSZXZvY2F0aW9uVmFsdWVzPjxYQWRFUzpPQ1NQVmFsdWVzPjxYQWRFUzpFbmNhcHN1bGF0ZWRPQ1NQVmFsdWU+TUlJR3d6Q0I1cUZSTUU4eEN6QUpCZ05WQkFZVEFrNVBNUmN3RlFZRFZRUUtEQTVVUlZOVUlFSmhibXRKUkNCQlV6RU5NQXNHQTFVRUN3d0VWRVZUVkRFWU1CWUdBMVVFQXd3UFZHVnpkQ0JDWVc1clNVUWdWa0V6R0E4eU1ESTFNREl5TnpFeE1qRXdNRm93VVRCUE1Eb3dCd1lGS3c0REFob0VGRExCZFBCczNSaHFFd0w5cHk5WjIwYTJaV0x0QkJRQmFaTWduQmcxQlRzYzV1Yk1zendzcVZxRjdnSURJZURnZ0FBWUR6SXdNalV3TWpJM01URXlNVEF3V3FFdE1Dc3dLUVlKS3dZQkJRVUhNQUVDQkJ4blNVZHpWMDVpZUZaRFpFeHpORzlZVWlzd2RGcExXblpUTTI4OU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3JYS3M1amxrUFNSekR0VlJxZ1BrbnU1anR4N3JFMWxBTDgwNTZPNkx4Vnp2VUp1TDlVaU9yb3BhRGR0bDdMeUExWnhXTWU2ZjYyVmlKY1ZtN0RRQ0prR2tkOFFoN2gzdTVSdnM4T2gxNHdBZnNmQzFjQlh4NFpiOHVkVnJqMDcwSnJwVWVYcjJ1K1hnMWo5cmhxNi8yTXBQMEI0Tk9yVndUYUw4ZVg3K1JaY3dPZ2prakdvMVF5RTFRZGZWWTRBOU93dUFzMVRGZ0N3NmZUVitPUVJ3TFppczltYUFkVFJ0QXArb09Ed2t2YTF6dEdtbWZHMlVNYy90RUNKWnZRNENqT1BvTWdWenFFaS8wRVpMUVZwdzU1WTN3WFZHOWg4TmxPektaakhaNjhvazN5R1N0N0lVREJ3Q0RYY3J1a2hRbkdra2RydEhveHhqTE1BbzNnVEpZb0lJRXdqQ0NCTDR3Z2dTNk1JSUNvcUFEQWdFQ0FnSUpDVEFOQmdrcWhraUc5dzBCQVFzRkFEQnhNUXN3Q1FZRFZRUUdFd0pPVHpFcU1DZ0dBMVVFQ2d3aFZFVlRWQ0F0SUVaT1NDQnZaeUJUY0dGeVpXSmhibXRtYjNKbGJtbHVaMlZ1TVJZd0ZBWURWUVFMREExVVJWTlVJQzBnUW1GdWEwbEVNUjR3SEFZRFZRUUREQlZVUlZOVUlDMGdRbUZ1YTBsRUlGSnZiM1FnUTBFd0hoY05Nak13TVRFd01UUXlORFU1V2hjTk1qY3dNVEV3TVRReU5EVTVXakJQTVFzd0NRWURWUVFHRXdKT1R6RVhNQlVHQTFVRUNnd09WRVZUVkNCQ1lXNXJTVVFnUVZNeERUQUxCZ05WQkFzTUJGUkZVMVF4R0RBV0JnTlZCQU1NRDFSbGMzUWdRbUZ1YTBsRUlGWkJNekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFPTkhGZS90UXFDdlhmOHpwMGJTOVNjVkl6K3hiZlNsWEdhSkxFMlRkSE83OVQ1Mm5PSld2Nlk0M3ZDcHZraG5LaEc3NmNELzZyYjJra1VzVUVxalEwN0pTQkhWMytqS2FmUjZhWUpzaGZmQ3NYZEp4YWFlVFNZcXlNQjhHQ0E5UCswUjQwOFpGQlI1QUs4akR4WnlvdWRJcitHanprdmxtaWdJenpuRERVU1ZWRlFPZkJnQjB0Qm51RHpkeXhGbDdVVy9wdjRVK01yTjR4YTE2Y3EwZVowOTFoSGlkcEJxZjhOWVgyditUMktzelM3N0Fha3M2TW9HZjNyOTN0UmJLV0VJMEpRYnM4REk1Zkw0Wm5wajdSMU95UDh4OHZ6RjhmdkVmQ0UyR3Y4MThLTnlKUktROVcyUmRDNlYrQjZ3aVAyMi9MYmJQQUNtaEVocmg1Ynd4ak1DQXdFQUFhTitNSHd3RlFZRFZSMGdCQTR3RERBS0JnaGdoRUlCRUFFRkFUQU9CZ05WSFE4QkFmOEVCQU1DQnNBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3a3dId1lEVlIwakJCZ3dGb0FVMDBaWEszZlNZdTlyekRQSEhGeCtNNVVxb2g0d0hRWURWUjBPQkJZRUZKSlB0OHh3cGVCUDlGc0V1SGtORlptVUsycHZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUJQNTZNVS9RNXNQQ2RwOEtzL2pWZG1sbHlET1hYVkMzWVBFdG90QUZNZEVpR29GUHBuMzJoamNJSXo1QVBNaGc2VFh3RXo3UGR5ZmNtSmswUFdNbGxDdld2eERqeXNZQVlTRDUwa2l2bitpRmdLczdDcjEwME81QW4wMm8yaXd3dndQVnJPZ0dWdmlSRFJNWGpQeVlTdzQ5MXM4SjNucmhCOXVYZE15Qmxyb0EzT3RHU3lZTi9PSlJJQTVUcS9iUHc1SFVjdGQyU0FhdDlUNFRyd3dmRVlxbExMcHJGZElEOVRFbjFWaXVBZWZrcGd5T21lTTIrblg3SVBxcG5lUFNCKy9tN2dMMGl4a3JNdFBDaGNjSnQvU0tMSXE5UTJoZDBUYjlDTi9qVjcvYUF2Q0pWdTlsT3hwcTdCSVNBVTlzOTcvdE9SNVlHdkNOWW5hYmtJWDhqVkRWa1ZmOFY2bG9rdURuSVFiY1hFc3l6RXJjZldnRUpBU3ZxR3FsNnRJWm1wN3RZbkpaM2NDT0JtSEJoSGlWbThVOXlVWE50cXpYck0yQWNPOWhOeFZZSTNFUUR0d1NxOHpXeG95cHJyNDE2eUJQOFE1dzRlbGpEbXBoZzJjd0F6cVl5dFZuZWpKSjNGbkJseVZPTFJRYjlsNjVZd3Z6T0NrdmNBakJFM2pWTS9JOEV4eVhoMElhVTNZZWRhYWk5Y3FWVXc2NEc0TmE5TDFTaGlxWVNFUTFseEUrRmVyVTE5V3dESjB1TklzcmNpV0Eyc3MvT0NBY1NNZ3RwOE8yU0t6MjhYbGVFYmcvVUtoaDl5cjZLcEJYaGw4S25nWmNFR2NGckdkSTlqVXYyU0hYUUI0b3FrQ1RKNXVQTHk5RWthR0FkVkdXUWY2ZzlpYjdZei9vSkErQT09PC9YQWRFUzpFbmNhcHN1bGF0ZWRPQ1NQVmFsdWU+PC9YQWRFUzpPQ1NQVmFsdWVzPjwvUmV2b2NhdGlvblZhbHVlcz48L1VzZXJDZXJ0aWZpY2F0ZUFuZFJldm9jYXRpb25EYXRhPjwvQ01TU2lnbmF0dXJlRWxlbWVudD48L1NpZ25hdHVyZUVsZW1lbnQ+PC9TRE9EYXRhUGFydD48U0RPU2VhbD48U0RPU2lnbmF0dXJlPjxDTVNTaWduYXR1cmVFbGVtZW50PjxTRE9Qcm9maWxlPlNFSUQtU0RPLUJhc2ljLVY8L1NET1Byb2ZpbGU+PFNpZ25hdHVyZVBvbGljeUlkZW50aWZpZXI+PFNpZ25hdHVyZVBvbGljeUlkPjxTaWdQb2xpY3lJZD48WEFkRVM6SWRlbnRpZmllcj51cm46b2lkOjIuMTYuNTc4LjEuMTYuNC4xPC9YQWRFUzpJZGVudGlmaWVyPjwvU2lnUG9saWN5SWQ+PC9TaWduYXR1cmVQb2xpY3lJZD48L1NpZ25hdHVyZVBvbGljeUlkZW50aWZpZXI+PFNpZ25lcnNEb2N1bWVudEZvcm1hdD48TWltZVR5cGU+dGV4dC9wbGFpbjwvTWltZVR5cGU+PC9TaWduZXJzRG9jdW1lbnRGb3JtYXQ+PEhhc2hlZERhdGE+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09IlNIQS0yNTYiLz48ZHM6RGlnZXN0VmFsdWU+TFF6dmpWMW4rOXdpdTNQQXlGYTcxWU5aN21qa2VoNno1ck9GUlBsRkxsZz08L2RzOkRpZ2VzdFZhbHVlPjwvSGFzaGVkRGF0YT48Q01TU2lnbmF0dXJlPk1JSU9CQVlKS29aSWh2Y05BUWNDb0lJTjlUQ0NEZkVDQVFFeERUQUxCZ2xnaGtnQlpRTUVBZ0V3Q3dZSktvWklodmNOQVFjQm9JSUxoekNDQlJrd2dnTUJvQU1DQVFJQ0F5SGc0REFOQmdrcWhraUc5dzBCQVFzRkFEQmhNUXN3Q1FZRFZRUUdFd0pPVHpFVk1CTUdBMVVFQ2d3TVZHVnpkRUpoYm1zeElFRlRNUkl3RUFZRFZRUUxEQWt4TWpNME5UWTNPRGt4SnpBbEJnTlZCQU1NSGtKaGJtdEpSQ0F0SUZSbGMzUkNZVzVyTVNBdElFSmhibXNnUTBFZ016QWVGdzB5TWpBMU1USXhNRFE0TVRSYUZ3MHlOakExTVRJeE1EUTRNVFJhTUdreEdEQVdCZ05WQkdFTUQwNVVVazVQTFRjM056YzNOemMzTnpFTE1Ba0dBMVVFQmhNQ1RrOHhGVEFUQmdOVkJBb01ERUpoYm10SlJDQlViMjlzY3pFU01CQUdBMVVFQlJNSk56YzNOemMzTnpjM01SVXdFd1lEVlFRRERBeENZVzVyU1VRZ1ZHOXZiSE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQzdCcE9QZ0xzYXU1UE5tZnFuWm0ya0k3NlVGZU83U2laNTNkMDN2SDJqRTVnMWxiL1J0TzducmJOZngydm5ISWgzVEhMUkFHNzhDNU81ZStnaUxXd2dKOEN4MXZJUFlVS05MWHUxclR0cGhSbTczUXgzdGpZMEg5QlBFTUVvd09jeU1wSFhvemNSelRYL3NaYitvZ3AzVkpLNkRwdURsUXF2NVc4emRZTWhjZ2Q5cnk4a0VaVGFYLzBtVWVaOXNXZmlObXFzOUppNEQzR3IrbjNLdGo3eDR4N1ZpelpjNnZGMll6YnNLelBUMndPRzB4UlpkWVdtTldwcEVqL0Z6UGhHbWp4ZFRvVk5tMkdOVmNHanJRRjNMc2ZIOUNYdGVuTFZPdzZVNFVVWWRzck43aUQ2alpyQ2RoSnB2b2kveUtBNjM0M3gxZVBIcGtvZExWRG1SYndwQWdNQkFBR2pnZEV3Z2M0d0V3WUhZSVJDQVJBQ0FRUUlNQVlFQkRrNU9EQXdGQVlIWUlSQ0FSQUNBZ1FKTUFjRUJVSkpUa0ZUTURrR0NDc0dBUVVGQndFQkJDMHdLekFwQmdnckJnRUZCUWN3QVlZZGFIUjBjSE02THk5MllTMXdjbVZ3Y205a01TNWlZVzVyYVdRdWJtOHdGZ1lEVlIwZ0JBOHdEVEFMQmdsZ2hFSUJFQUVHQVFFd0RnWURWUjBQQVFIL0JBUURBZ1pBTUI4R0ExVWRJd1FZTUJhQUZBRnBreUNjR0RVRk94em01c3l6UEN5cFdvWHVNQjBHQTFVZERnUVdCQlQ0djhOcVZ1OGlzejh0bldhQlZRMlpQb1JXK0RBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQWdFQUlzTHhQa1NiSFdUak9yUWRuN3hNeitFWCtsdGdoNkNRdXJXeXJnZnJIM05pUGE4Z2tnM0xCWFNyTzNZMTloRmdoZmZGc1YzMGJHZ1N5bFIzYjFudUJTMDNTQVBMSEIyMGJDT1I1ejRISDBTRXNtVGlhMGpYMHFBOWFmbklJNS9QV2dJZkpyY1gxR1lCRnBrdDlkQzRZK3ZpT2FKaS9ndHVHV2RHcjBLUWlYOFRBTWJLWjllRnJNSU44VzMxUUwzajIyQ3VOWWJXNU1WQnU4SjNUREszVEQ0dS9VZXFGOU5VZGxFbnFZenFoc0c0R2xEZ1haV0xNUVJPV1lhSEZCQllvT3VwNGM2Qk83UkMydWRzMy9mYzFzUWlzbXBoaWZCWkVNUEdkUTlRVzVFUVBXUWxIL1N3SmpERDlDYSszU0J4YkYvamNUQ2p2eEQwYnlsT016OFJscFBjY1l1S1k2SUtTNkJFWWNkQWJwaVYrWDRTYUxTTkVmclBpV1p0dEhUZ1BHYzFHbDNuUWpBUWcwcDlHWWd2RndsRCtTQWZJS2U3TVlHWk91SEdrTGQ1eHJCSjlxc0JsckV0L0dxSC9kd0ZDNjg1VlRwWDRGWkgveDA1RVdteG8ydjdibklxRGFONWVRTFFGc2F1Y0RxcmhkNWhCSVU4WjFxNGU0S1AvWjkyLzR4SXAzRjM0bWlxeTJUSTg1QzdJamVORmRqcHBLTkgxdGNxQ3VDNTdYVE01Mkh2ME5HR2wwcGg5NmdCeDFpeUV1MExqMms4Zzg3anRTNUIwQ3pTQ1lURUMvajhVcTdtcTN2NSs1cEg2ZFN4Y3JhOUlYUFk0YXUrV1lZMW5uM2QzdlZmR2ZiSWxaMjJ6Zlc5SzQzUXJkd2REMjArUStQMEtxeWw0LytNWnVvd2dnWm1NSUlFVHFBREFnRUNBZ0lGM3pBTkJna3Foa2lHOXcwQkFRc0ZBREJ4TVFzd0NRWURWUVFHRXdKT1R6RXFNQ2dHQTFVRUNnd2hWRVZUVkNBdElFWk9TQ0J2WnlCVGNHRnlaV0poYm10bWIzSmxibWx1WjJWdU1SWXdGQVlEVlFRTERBMVVSVk5VSUMwZ1FtRnVhMGxFTVI0d0hBWURWUVFEREJWVVJWTlVJQzBnUW1GdWEwbEVJRkp2YjNRZ1EwRXdIaGNOTVRneE1USXdNRGt5TmpReVdoY05NekF4TVRJd01Ea3lOalF5V2pCaE1Rc3dDUVlEVlFRR0V3Sk9UekVWTUJNR0ExVUVDZ3dNVkdWemRFSmhibXN4SUVGVE1SSXdFQVlEVlFRTERBa3hNak0wTlRZM09Ea3hKekFsQmdOVkJBTU1Ia0poYm10SlJDQXRJRlJsYzNSQ1lXNXJNU0F0SUVKaGJtc2dRMEVnTXpDQ0FpSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBT0xCSjl6RVRhSkdBSmZoMy9aems1WHBYdWl6WFpFNjBlVVFrTHhLZXB2Um1IaHNtUUpDemtXQ2UrRHRyQ2duS3BXYVFsVXo5WHZCWHdKWmhDcmk4UkcwZytTVE84MXI2c2JiQ3RKbEJkZXRUZml2NjlneStFbHdPUTFEUW5HUW5oNzl5Ylkzd2hiMnEwRzdZbnVHSm84REFqVUcxRVhveGpLSlpxeGN1Y1IxRkh6WmJQSjAvTlJBN2VhdXY3cTc3Y0tmMWdGVjRudTUveU1wS2s0cEhoZGo1UjB0dzZ3cHRXSzFCNWFTVVp3NjNod1V3S1hJdjVqd1FoOTE5cUV3WSt6eWovaEZJNUN1TFpBZ21PVEx5cFVvV3JuNHQwd0N5USsxMVhGWlpBZGlraUd2MTVZdStldXcwUWtqK0gweW1BU3NoK1NOSXY0ZVFqWWFXZ3pMQmRCdkQxRnlZbXM4TFI1OFpQeE1BQ29PbjR6ZlNBbkgzeDF6OGJvdTEzaCs4bzlZTGR5VUtaRVRmSStmdStLMWVZSnZEMnlMTGhYaGVGRFdkOUxwYXF2N010eUVjNGtSR09NdHVxdVUyaTVINFN5T2NsdytUWWQrR3dwMEVyKzFqTzlGeTNJR1ZTcnp0NWlOUEhBN2RWUlNqOStWWHBZckE2bDZnVnZxOExBMFovYWFnSFF6VXorNTZCemp5VEprQmUwNWx1MmxiQ01BNFA0SzgyRVNVcDZjWEpmL3g2WDFIWHFTWnNRT2FWV3RZQ0VyTUdraFdTM1g4SHZkOXZYOGxKMFhrM25XK0NnTUd2amYzNnpUNTNtNVNsVmd5bzBPdkhVWHZZeVRDVHRSMkxSVzNnZGFNZ3lGRWJvc3lOZ3I0ejA5OW16V2VBQmx5MEYrSE5iTUlyRi9BZ01CQUFHamdnRVdNSUlCRWpBU0JnTlZIUk1CQWY4RUNEQUdBUUgvQWdFQU1CVUdBMVVkSUFRT01Bd3dDZ1lJWUlSQ0FSQUJBd0V3VHdZSUt3WUJCUVVIQVFFRVF6QkJNRDhHQ0NzR0FRVUZCekFDaGpOb2RIUndjem92TDJOeWRDMXdjbVZ3Y205a0xtSmhibXRwWkM1dWJ5OTBaWE4wTFdKaGJtdHBaSEp2YjNSallTNWpjblF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQjhHQTFVZEl3UVlNQmFBRk5OR1Z5dDMwbUx2YTh3enh4eGNmak9WS3FJZU1FUUdBMVVkSHdROU1Ec3dPYUEzb0RXR00yaDBkSEJ6T2k4dlkzSnNMWEJ5WlhCeWIyUXVZbUZ1YTJsa0xtNXZMM1JsYzNRdFltRnVhMmxrY205dmRHTmhMbU55YkRBZEJnTlZIUTRFRmdRVUFXbVRJSndZTlFVN0hPYm16TE04TEtsYWhlNHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBR1lFSS9aL242ek4zdWg1cjBpb0lSSkVuejdqZlpWWHI1alByK3lLKzlUMUNlSEw3dU5YcDh0bExQK2dWb0graHVpbDdMOHVxblNHZzRxMFhkVUdlSHJMRlBSWGpIR1NJVHFDNmJnam5qYUxxZ3BIM3ZxV0tMa0ViUTczbVBwOFZCZjVkTWQ1VE1ieEVpVW56cG9mRFl0UUZYYWdUY3lZSWQwaVRsbnF5MXRxL1pHYUZQTmZRdFlSUVVCRlQzNFVYT1BzbXZTZ3U2eENSTjRraFhlNWIrNDNKeXJJNGJ0aWtyS01OUTBOZ2EySGg0UHJ3VndrbEI1aWVZN09LaVRITzJZMFRzNitMVWhraXR3OVBXSk9NM2g5elMxdkJCU2RLNjE0Y21qQ3pMbjNBcDlrVWwzcEJQRUQwd2xSUWluN0lhalhYZm43elZDRUJ5UVdYOHhMVE5VQ2o0d0lmUGpqbnljNU9Cck5xQjVpTzZUbFNQd0dnQVY2LzhpNEV3Q1lNcmRqZWQ4UDQzTUtFZjlWWHlHUWhmQzE4WFBBU1lqb2xIRFBocGxVWWNZQlg2VlhSVjJ5YUxMbHhxQXdnL1hSRlREQ1kxSlEydjZXbkluM0gwakFZT05nRVNhZjRtZGlzaXRxSndVa2s3cDljeTBOQlA2RDU1RXZzcUd6eG1DRjl0Sk50MEI1RzVqdUFkZVI2NWlrb2pOMHNjcjdGYXEvWWUwQkJ5bUZIVUFwSTFlUFFCUGYzT0wwajV5UWQrL1ZVbG9iWGtOUTFxMTArV0dCQWZpQ3Z5VzVaSUREeTJNM09SVG1reHR6L1VuU3RTTkJMZW9RbkNTTUJRTG9IejdycGdKcTRjMFBKc2hrQU5rR0ZQS1U5cmwxb0FuVTlSeDFRVVBrMGJSR3dJL0dNWUlDUXpDQ0FqOENBUUV3YURCaE1Rc3dDUVlEVlFRR0V3Sk9UekVWTUJNR0ExVUVDZ3dNVkdWemRFSmhibXN4SUVGVE1SSXdFQVlEVlFRTERBa3hNak0wTlRZM09Ea3hKekFsQmdOVkJBTU1Ia0poYm10SlJDQXRJRlJsYzNSQ1lXNXJNU0F0SUVKaGJtc2dRMEVnTXdJREllRGdNQXNHQ1dDR1NBRmxBd1FDQWFDQnNUQVlCZ2txaGtpRzl3MEJDUU14Q3dZSktvWklodmNOQVFjQk1Cd0dDU3FHU0liM0RRRUpCVEVQRncweU5UQXlNamN4TVRJd05UbGFNQzhHQ1NxR1NJYjNEUUVKQkRFaUJDQXRETytOWFdmNzNDSzdjOERJVnJ2VmcxbnVhT1I2SHJQbXM0VkUrVVV1V0RCR0Jnc3Foa2lHOXcwQkNSQUNFekUzTURVd016QXhNQzh3Q3dZSllJWklBV1VEQkFJQkJDQ3lZcy90enlQbDZKQWIzbnEvMHd0TjJXeDRSd0FDQW9rYlVEWUlpLzVoVlRBTEJna3Foa2lHOXcwQkFRRUVnZ0VBWFRlVlpXS1JQY0pKd09WcmxaWlpaRHplMWVqaDNzOGhvZU1KZjZaZnFBZU1hSUtiOWUySlZpQ2lNNVRIVXpYVEJhZUEzNFJNRUNFem9uenJZVjlReXpJcGV6UTcrcjBiUTN5bU5zZllNQXFlSCtWRjl6VVA1TXgvNFBhSEZMb1g0TlAvRXQ0bzZHK092OGY3a2xRbjZEclEzaGlzMXUrM3ZNQnJGMGkyZHVpSXhzeFJaVGVUalZpWUFXV0IvUHJxcTEvTmMyQXczT2hmeHB1OW5TOUV4Ukt0UDN6blUvdFpiLzdpSWdOWklWeExET0FicUxkeHl3ei90Zm81eVpnYnljbU04SnhrOFVqZlVjSmtjRGRYaVBueVpQdHRQb1VFRVliNDJxNHo5VlAvWXBuRmp0UCtnQWxwQlQyYWxiZUd3RGpMVzZQRXBaSkxLYWE5UkZqcG5nPT08L0NNU1NpZ25hdHVyZT48VXNlckNlcnRpZmljYXRlQW5kUmV2b2NhdGlvbkRhdGE+PFJldm9jYXRpb25WYWx1ZXM+PFhBZEVTOk9DU1BWYWx1ZXM+PFhBZEVTOkVuY2Fwc3VsYXRlZE9DU1BWYWx1ZT5NSUlHd3pDQjVxRlJNRTh4Q3pBSkJnTlZCQVlUQWs1UE1SY3dGUVlEVlFRS0RBNVVSVk5VSUVKaGJtdEpSQ0JCVXpFTk1Bc0dBMVVFQ3d3RVZFVlRWREVZTUJZR0ExVUVBd3dQVkdWemRDQkNZVzVyU1VRZ1ZrRXpHQTh5TURJMU1ESXlOekV4TWpFd01Gb3dVVEJQTURvd0J3WUZLdzREQWhvRUZETEJkUEJzM1JocUV3TDlweTlaMjBhMlpXTHRCQlFCYVpNZ25CZzFCVHNjNXViTXN6d3NxVnFGN2dJREllRGdnQUFZRHpJd01qVXdNakkzTVRFeU1UQXdXcUV0TUNzd0tRWUpLd1lCQlFVSE1BRUNCQnhhYTB0alpVdFZZbk5uUzFwaWVIaGFNMFp4TnpGQmJIaGFOMVU5TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBblBGVEo3SjJWcFdNQm5HcTVJV1UxYTRUS3BWdXhMUUNzWVpJcFd0Q0FwWEFSZTdMYUR4K1hCWVVQTHFvZ3hLNGcxMUFKUFFoWEh3dW5uTzlpVHI4NXJZdGc2ZkhaTUpGZEhZVW03bkorNENZb01xcUdzcTlqNjR0d0hRZHczcGVQc1hEbk53M29QQUhuVjEzbGlhS3pzdythRWZ3ZGpoQWN6Y2lUTUQ2WGo2YU9JT2ZReVlGU0R1Y1VJRHdZeHlqZW9ZSDJGUWtJdHltQWtvKzRhVWlmR0JWQWdGME9QUEE3b0NmMjhCSmFHSXB1cnFkYml3algvUEN2Qm1meHorL3JGSTk0bXlCbmNDWXFWYlFnQkRSbzlVNjFlZXgxb213TnJSNzZJdWJDbGVIaDU1RGIrbWI3ajdqN3M5L0tBY0FOMkhwVWxmQmUvQ0JhazdpcGdsMFlvSUlFd2pDQ0JMNHdnZ1M2TUlJQ29xQURBZ0VDQWdJSkNUQU5CZ2txaGtpRzl3MEJBUXNGQURCeE1Rc3dDUVlEVlFRR0V3Sk9UekVxTUNnR0ExVUVDZ3doVkVWVFZDQXRJRVpPU0NCdlp5QlRjR0Z5WldKaGJtdG1iM0psYm1sdVoyVnVNUll3RkFZRFZRUUxEQTFVUlZOVUlDMGdRbUZ1YTBsRU1SNHdIQVlEVlFRRERCVlVSVk5VSUMwZ1FtRnVhMGxFSUZKdmIzUWdRMEV3SGhjTk1qTXdNVEV3TVRReU5EVTVXaGNOTWpjd01URXdNVFF5TkRVNVdqQlBNUXN3Q1FZRFZRUUdFd0pPVHpFWE1CVUdBMVVFQ2d3T1ZFVlRWQ0JDWVc1clNVUWdRVk14RFRBTEJnTlZCQXNNQkZSRlUxUXhHREFXQmdOVkJBTU1EMVJsYzNRZ1FtRnVhMGxFSUZaQk16Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9OSEZlL3RRcUN2WGY4enAwYlM5U2NWSXoreGJmU2xYR2FKTEUyVGRITzc5VDUybk9KV3Y2WTQzdkNwdmtobktoRzc2Y0QvNnJiMmtrVXNVRXFqUTA3SlNCSFYzK2pLYWZSNmFZSnNoZmZDc1hkSnhhYWVUU1lxeU1COEdDQTlQKzBSNDA4WkZCUjVBSzhqRHhaeW91ZElyK0dqemt2bG1pZ0l6em5ERFVTVlZGUU9mQmdCMHRCbnVEemR5eEZsN1VXL3B2NFUrTXJONHhhMTZjcTBlWjA5MWhIaWRwQnFmOE5ZWDJ2K1QyS3N6Uzc3QWFrczZNb0dmM3I5M3RSYktXRUkwSlFiczhESTVmTDRabnBqN1IxT3lQOHg4dnpGOGZ2RWZDRTJHdjgxOEtOeUpSS1E5VzJSZEM2VitCNndpUDIyL0xiYlBBQ21oRWhyaDVid3hqTUNBd0VBQWFOK01Id3dGUVlEVlIwZ0JBNHdEREFLQmdoZ2hFSUJFQUVGQVRBT0JnTlZIUThCQWY4RUJBTUNCc0F3RXdZRFZSMGxCQXd3Q2dZSUt3WUJCUVVIQXdrd0h3WURWUjBqQkJnd0ZvQVUwMFpYSzNmU1l1OXJ6RFBISEZ4K001VXFvaDR3SFFZRFZSME9CQllFRkpKUHQ4eHdwZUJQOUZzRXVIa05GWm1VSzJwdk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQlA1Nk1VL1E1c1BDZHA4S3MvalZkbWxseURPWFhWQzNZUEV0b3RBRk1kRWlHb0ZQcG4zMmhqY0lJejVBUE1oZzZUWHdFejdQZHlmY21KazBQV01sbEN2V3Z4RGp5c1lBWVNENTBraXZuK2lGZ0tzN0NyMTAwTzVBbjAybzJpd3d2d1BWck9nR1Z2aVJEUk1YalB5WVN3NDkxczhKM25yaEI5dVhkTXlCbHJvQTNPdEdTeVlOL09KUklBNVRxL2JQdzVIVWN0ZDJTQWF0OVQ0VHJ3d2ZFWXFsTExwckZkSUQ5VEVuMVZpdUFlZmtwZ3lPbWVNMituWDdJUHFwbmVQU0IrL203Z0wwaXhrck10UENoY2NKdC9TS0xJcTlRMmhkMFRiOUNOL2pWNy9hQXZDSlZ1OWxPeHBxN0JJU0FVOXM5Ny90T1I1WUd2Q05ZbmFia0lYOGpWRFZrVmY4VjZsb2t1RG5JUWJjWEVzeXpFcmNmV2dFSkFTdnFHcWw2dElabXA3dFluSlozY0NPQm1IQmhIaVZtOFU5eVVYTnRxelhyTTJBY085aE54VllJM0VRRHR3U3E4eld4b3lwcnI0MTZ5QlA4UTV3NGVsakRtcGhnMmN3QXpxWXl0Vm5lakpKM0ZuQmx5Vk9MUlFiOWw2NVl3dnpPQ2t2Y0FqQkUzalZNL0k4RXh5WGgwSWFVM1llZGFhaTljcVZVdzY0RzROYTlMMVNoaXFZU0VRMWx4RStGZXJVMTlXd0RKMHVOSXNyY2lXQTJzcy9PQ0FjU01ndHA4TzJTS3oyOFhsZUViZy9VS2hoOXlyNktwQlhobDhLbmdaY0VHY0ZyR2RJOWpVdjJTSFhRQjRvcWtDVEo1dVBMeTlFa2FHQWRWR1dRZjZnOWliN1l6L29KQStBPT08L1hBZEVTOkVuY2Fwc3VsYXRlZE9DU1BWYWx1ZT48L1hBZEVTOk9DU1BWYWx1ZXM+PC9SZXZvY2F0aW9uVmFsdWVzPjwvVXNlckNlcnRpZmljYXRlQW5kUmV2b2NhdGlvbkRhdGE+PC9DTVNTaWduYXR1cmVFbGVtZW50PjwvU0RPU2lnbmF0dXJlPjwvU0RPU2VhbD48TWV0YWRhdGE+PFZhbHVlUGFpcj48TmFtZT5NZXJjaGFudERlc2M8L05hbWU+PFZhbHVlPmhhc2hUb1NpZ248L1ZhbHVlPjwvVmFsdWVQYWlyPjwvTWV0YWRhdGE+PC9TRE8+PC9TRE9MaXN0Pg==";
public static void main(String[] args) {
System.out.println("sdo document with added: ");
System.out.println(
addDocumentDataToSDO(
bytesSigned,
sdoWithoutDocumentB64));
}
static String addDocumentDataToSDO(byte[] documentBytesSigned, String sdoWithoutDocumentB64) {
try {
String xml = new String(
Base64.getDecoder().decode(sdoWithoutDocumentB64), StandardCharsets.UTF_8);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
// Use XPath to find the desired node
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expr = xpath.compile("/SDOList/SDO");
Node sdoNode = (Node) expr.evaluate(document, XPathConstants.NODE);
if (sdoNode != null) {
// Add the SignedObject node with SignersDocument content to the SDO node
Element signersDocument = document.createElement("SignersDocument");
// The document data should be added as the Base64 encoded bytes of the signed bytes
signersDocument.setTextContent(Base64.getEncoder().encodeToString(documentBytesSigned));
Element signedObject = document.createElement("SignedObject");
signedObject.appendChild(signersDocument);
sdoNode.appendChild(signedObject);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(document);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
StreamResult result = new StreamResult(outputStream);
transformer.transform(source, result);
// The output will be the updated XML document with the SignedObject node added. Should pass the NETS SDO validation.
return outputStream.toString();
} else {
throw new IllegalArgumentException("SDO node not found in the XML document");
}
} catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException |
TransformerException e) {
throw new RuntimeException("Some XML error", e);
}
}
}
Computing SHA256 hashes¶
When the client is sending hashes of documents instead of the documents themselves, the client must compute the hash in the same way as BankID would have done. BankID extracts the bytes for computing the SHA256 hash for text, PDF, or bidxml based documents from the documents in the following ways:
- For text, the bytes are extracted from the text string using the ISO_8859_1 charset.
- For PDF, the bytes are the bytes read from the PDF file as is.
- For bidxml, the XML and XSL strings are wrapped into a string as shown below, and then the bytes are extracted using the ISO_8859_1 charset.
import java.util.Base64;
/**
* Compute the SHA256 hash of the given byte array and return it as a base64 encoded string
*/
private static String sha256B64(byte[] tbs) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(tbs);
return Base64.getEncoder().encodeToString(hash);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Compute the SHA256 hash by converting the text to bytes using the ISO_8859_1 charset
*/
public static String sha256ForTextB64(String textDocument) {
return sha256B64(textDocument.getBytes(StandardCharsets.ISO_8859_1));
}
/**
* Compute the SHA256 hash by using the PDF bytes as is
*/
public static String sha256ForPdf(byte[] pdfDoc) {
return sha256B64(pdfDoc);
}
/**
* Compute the SHA256 hash by wrapping the XML and XSL parts into a BankIDXML structure and then converting the string to bytes using the ISO_8859_1 charset
*/
public static String sha256ForBidXml(String xmlDocument, String xslDocument) {
return sha256ForTextB64(
"<BankIDXML><BIDXML>" + xmlDocument + "</BIDXML><BIDXSL>" + xslDocument + "</BIDXSL></BankIDXML>");
}
OpenAPI Specification¶
Please refer to the B2B BankID Signer OpenAPI for detailed API endpoints, request/response schemas, and examples.