title: Implementation Plan — Mutual-Certificate Signing for Sambankaskema 2013 audience: developer keywords: Landsbankinn, IOBS 2013, mutual certificate, WS-Security, sign, encrypt, EncryptedXml, AsymmetricSecurityBindingElement, currency feed, NRE, recipient certificate
The Sambankaskema 2013 calls (Landsbankinn.CurrencyRates.Get, .Statement.Get, .Claim.*, .Payment.*) reach the bank and fail with HTTP 500 Object reference not set to an instance of an object. Root cause: the requests are signed but not encrypted. The IOBS 2013 services use a WCF mutual-certificate binding that requires each message to be signed with our certificate AND encrypted with the bank's public certificate. This plan covers making our signer do both.
| Finding | Source |
|---|---|
| Username/password are correct | Landsbankinn.Account.Verify / .Skema.Statement.Get (Landsbankaskema, password-only) work live |
| 2013 calls fail with a server NRE on a signature-accepted request | Live MCP calls; bank returns a signed s:Fault |
| IOBS 2013 binding = mutual cert, SignBeforeEncrypt | SampleClients-3.4.0.0\Common\Security\WcfSecurityHelper.cs → GetSchema20131015MutualCertificateBinding |
Client must hold the bank's public cert (downloaded from the WSDL Identity, or b2bCer.cer / LandsbankiPublicB2Bws2023.p7b) and encrypt to it | Same file → GetServerCertificate; the "SSL downloaded from the bank used in signing with our PFX" |
| Messages must be signed (X.509 + Username token), WS-Security 1.1 | TS 166 §2.3; Sambankaskema standard §Security |
Our Base signer only signs — no EncryptedData/EncryptedKey | Cloud Events repo IOBSWsSecSoapSigner.Codeunit.al |
The reference binding, verbatim:
// GetSchema20131015MutualCertificateBinding (IOBS 20131015)
var initiator = new X509SecurityTokenParameters(Thumbprint, AlwaysToRecipient); // OUR cert (sign)
var recipient = new X509SecurityTokenParameters(Thumbprint, Never); // BANK cert (encrypt to)
var messageSecurity = new AsymmetricSecurityBindingElement(recipient, initiator);
messageSecurity.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt; // sign, then encrypt
messageSecurity.SetKeyDerivation(false);
messageSecurity.IncludeTimestamp = true;
The BC System Application exposes the needed crypto (no .NET add-in required):
| Codeunit | Use |
|---|---|
SignedXml (1464) | Sign the envelope (already used by IOBS WsSec Soap Signer) |
**EncryptedXml (1465)** | Encrypt an element with an X.509 cert — Encrypt(var XmlDocument; ElementToEncrypt: Text; X509CertBase64Value: Text; X509CertPassword: SecretText; [SymmetricAlgorithm]) |
RSA (1475) / AesCryptoServiceProvider (1467) | Key wrap / session key, if hand-building is needed |
X509Certificate2 (1285) | Read cert (thumbprint, public key) for the recipient reference |
EncryptedXml.Encrypt creates a symmetric session key, encrypts the named element, wraps the session key with the recipient (bank) public cert, and emits EncryptedData + EncryptedKey — the exact shape WS-Security needs. The recipient cert is public, so the password argument is empty.
<Identity><KeyInfo><X509Data><X509Certificate> (as GetServerCertificate does), or use the published b2bCer.cer / LandsbankiPublicB2Bws2023.p7b. It expires ~yearly.
Lbi Secret Mgt (IsolatedStorage, Company scope) — e.g. SetBankCertificate / TryGetBankCertificate. Public cert, no password.
cert / password), or a Help.*/Setup message type to load it.
Target the existing SOAP 1.1 WS-Security signer used by Landsbankinn — enum value IOBS Soap Signer::WsSecuritySoap11 (value 3) / codeunit IOBS WsSec Soap11 Signer (Cloud Events repo). This value is used only by Landsbankinn, so Arion (value 0, WsSecurity) is unaffected.
SetRecipientCertificate(Base64: Text) setter on IOBS WsSec Soap Signer (same pattern as the existing SetForceSoap11) so the interface contract does not change.
BuildSignedEnvelope, after producing the signed EnvelopeDoc (SignBeforeEncrypt): EncryptedXml.Encrypt(EnvelopeDoc, '<body element name>', BankCertBase64, '', SymmetricAlgorithm::AES256) to encrypt the SOAP Body content.
IOBS WsSec Soap11 Signer wrapper calls SetForceSoap11(true) today; have it alsopass the recipient cert through (e.g. read it from a context the transport sets, or add a second wrapper method).
back to sign-only when none is configured (preserves current behaviour / other banks).
Lbi Live Transport.SendRequest (the 2013 path): resolve the bank cert from Lbi Secret Mgt, set it on the signer (SetRecipientCertificate) before BuildSignedEnvelope.
| Repo | Object | Change |
|---|---|---|
Cloud Events (Base) | IOBS WsSec Soap Signer (65801) | Add SetRecipientCertificate; encrypt Body via EncryptedXml after signing when set |
Cloud Events (Base) | IOBS WsSec Soap11 Signer (65804) | Pass recipient cert through to the delegate |
| Lbi | Lbi Secret Mgt | Store/retrieve the bank public cert |
| Lbi | Lbi Live Transport | Load + set the bank cert on the signer for the 2013 path |
| Lbi | Setup page | Upload action for the bank cert |
AsymmetricSecurityBindingElement expects specific choices: RSA-OAEP key wrap, AES256-CBC data encryption, the EncryptedKey carried in the wsse:Security header with a SecurityTokenReference to the recipient by thumbprint, and a ReferenceList. BC's EncryptedXml.Encrypt default output may differ (inline EncryptedKey inside EncryptedData, RSA-1.5 vs OAEP). This will need iteration against the live bank, comparing our wire bytes to a known-good sample.
works. Either Arion's bank tolerates sign-only, or Arion's signed feeds were never verified. Confirm before investing (see Step 0 below). If sign-only is accepted, the Landsbankinn NRE is instead a certificate-registration issue (TS 166 errors: "certificate not linked", "kennitala does not match the certificate's kennitala").
(BusinessCentral-Kappi, kennitala 411203-2630) must be registered/linked at the bank to the B2B user, and the kennitalas must line up. Verify with Landsbankinn.
SampleClients-3.4.0.0 (IsIT.B2B.PublicCertDownloaderTest to fetch the bank cert, then IsIT.B2B.AccountServiceTest or IsIT.B2B.ClaimService20131015Test) against b2b.fbl.is with our PFX.
replicate, then proceed to Steps 1–4.
with the bank (no code change).
compare the logged request to the Step 0 sample and iterate on algorithms/structure.
If BC's EncryptedXml cannot be coerced into the exact WS-Security shape WCF accepts, run the proven SampleClients binding inside a small .NET proxy (Azure Function / container) that performs the mutual-cert SOAP call, and have BC call that proxy over HTTPS/JSON — the same isolation pattern the Landsbankaskema path already proves works.
All changes are gated on the WsSecuritySoap11 enum value and a configured recipient cert. Arion uses WsSecurity (value 0) with no recipient cert, so its behaviour is unchanged.
© Origo – Cloud Events Base Extension