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

locale: en-us

Implementation Plan — Mutual-Certificate Signing for Sambankaskema 2013

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.

Evidence

FindingSource
Username/password are correctLandsbankinn.Account.Verify / .Skema.Statement.Get (Landsbankaskema, password-only) work live
2013 calls fail with a server NRE on a signature-accepted requestLive MCP calls; bank returns a signed s:Fault
IOBS 2013 binding = mutual cert, SignBeforeEncryptSampleClients-3.4.0.0\Common\Security\WcfSecurityHelper.csGetSchema20131015MutualCertificateBinding
Client must hold the bank's public cert (downloaded from the WSDL Identity, or b2bCer.cer / LandsbankiPublicB2Bws2023.p7b) and encrypt to itSame 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.1TS 166 §2.3; Sambankaskema standard §Security
Our Base signer only signs — no EncryptedData/EncryptedKeyCloud 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;

It is feasible in pure AL

The BC System Application exposes the needed crypto (no .NET add-in required):

CodeunitUse
SignedXml (1464)Sign the envelope (already used by IOBS WsSec Soap Signer)
**EncryptedXml (1465)**Encrypt an element with an X.509 certEncrypt(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.

What needs to be done

1. Obtain and store the bank's public (recipient) certificate

<Identity><KeyInfo><X509Data><X509Certificate> (as GetServerCertificate does), or use the published b2bCer.cer / LandsbankiPublicB2Bws2023.p7b. It expires ~yearly.

Company scope) — e.g. SetBankCertificate / TryGetBankCertificate. Public cert, no password.

cert / password), or a Help.*/Setup message type to load it.

2. Extend the Base signer to sign and encrypt

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.

(same pattern as the existing SetForceSoap11) so the interface contract does not change.

  1. Sign as today (Body + Timestamp + addressing).
  2. If a recipient cert is set, call

EncryptedXml.Encrypt(EnvelopeDoc, '<body element name>', BankCertBase64, '', SymmetricAlgorithm::AES256) to encrypt the SOAP Body content.

  1. Emit the resulting document.

pass 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).

3. Wire it in the Lbi transport

Lbi Secret Mgt, set it on the signer (SetRecipientCertificate) before BuildSignedEnvelope.

4. Object changes summary

RepoObjectChange
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
LbiLbi Secret MgtStore/retrieve the bank public cert
LbiLbi Live TransportLoad + set the bank cert on the signer for the 2013 path
LbiSetup pageUpload action for the bank cert

Open questions and risks

  1. Exact xenc shape vs WCF. WCF's 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.

  1. Is encryption actually enforced? Arion uses the same sign-only signer and reportedly

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").

  1. Certificate registration. Independent of encryption, the signing cert

(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.

Recommended sequence

  1. Decisive, cheap — run the .NET reference client first. Build/run

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).

  1. Obtain + store the bank public cert (Step 1).
  2. Implement sign+encrypt in the Base signer (Step 2), encryption conditional on the cert.
  3. Wire the Lbi transport (Step 3); deploy; call the currency feed via the BC Metadata MCP;

compare the logged request to the Step 0 sample and iterate on algorithms/structure.

  1. Roll out to the other 2013 services once currency works.

Fallback

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.

Do not break Arion

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