Revisionshistorik:

 Version

Dato

Ændring

Ansvarlig

0.8

29-11-2012

Oprettet

IO

1.0

04-04-2013

redigeret

IO

3.0.0

03-01-2017

Fjernet .net 3.5 referancer

FMO

4.0.0

26-06-2017

Tilføjet IdCards og OioSamlFactory

KRO og SKS

4.0.1

13-10-2022

Rettet "SOSI Gateway SBO" dokumentation så den passer til ændringerne i '4.2.5'.

KvalitetsIT

5.0.0

09-11-2022

Tilføj migreringsguide for version '5.0.0'. Ret og opdater dokumentation.

KvalitetsIT


Indledning

Den Gode WebService (DGWS) er en profil for webservices, som bygger på flere WebService standarder fra WS* stakken. Det er ikke en triviel opgave at designe en klient eller en webservice implementering, der overholder DGWS profilen. Seal.Net Api'ets formål er, at sænke den tærskel der uvægerligt er forbundet med at udvikle software der overholder Den Gode WebService. Seal.Net indpakker alle DGWS specifikke detaljer og abstraherer alle typer fra XML til objektform. API'et tager sig af validering og signering.
Seal.NET udgives som en NuGet pakke.

Læseren af denne guide forudsættes at have indsigt i .NET, C#, WCF, og XML.

Historik

Den Gode WebService er specificeret i tre versioner 1.0, 1.0.1 og 1.1. Ingen af versionerne er bagud- eller forudkompatible og der er tidligere udviklet individuelle API'er til at understøtte disse versioner.
Dette Api understøtter alle nuværende versioner af DGWS i samme implementering for hhv. klient og service.
Der er dog væsentlige designforskelle mellem dette API og tidligere versioner.

Systemkrav

Seal.NET target'er .NET Standard 2.0, så den er kompatibel med .NET 5+ og .NET Core, samt .NET Framework projekter.

Migrering til version 5.0.0 fra ældre version

5.0.0 skifter target til .NET Standard 2.0, som medførerer API ændringer. Derudover er der lavet forsimplinger og oprydning af API'et.

Generelt

Ændringer til SBO/Sosi2OioSaml

Ændringer til OioSaml2Sosi

Saml2SosiStsClient.ExchangeAssertion er nu en static metode:
I det gamle Seal.NET bliver denne kode:

using (var stsClient = new Saml2SosiStsClient("sts_OIOSaml2Sosi"))
{
    stsClient.ChannelFactory.Credentials.ClientCertificate.Certificate = Global.StatensSerumInstitutFoces;
    var healthContextAssertion = SealUtilities.MakeHealthContextAssertion(
        "Test Sundhed.dk",
        Global.StatensSerumInstitutFoces.SubjectName.Name,
        "Sygdom.dk", userAuthorizationCode);
    return stsClient.ExchangeAssertion(nemidAssertion, healthContextAssertion, "http://sosi.dk");
}

Til

Saml2SosiStsClient.ExchangeAssertion(Global.StatensSerumInstitutFoces, new Uri("<URL der før var i app.config>"), nemidAssertion, userAuthorizationCode);

Hvor nemidAssertion er af typen 'OioSamlAssertion' i stedet for 'Saml2Assertion'.


En 'OioSasmlAssertion' kan laves ud fra et XML DOM objekt på følgende måde:

var exampleXmlDom = new XDocument().Root;
var ast = new dk.nsi.seal.Model.OioSamlAssertion(exampleXmlDom);

ast.XAssertion == exampleXmlDom // true


Seal.NET API

Overordnet

Seal.NET er designet til at blive brugt med WCF service references, som laves ud fra WSDL filer. I en WSDL beskrives en snitflade til Den Gode Webservice (DGWS), samt skemaer der beskriver de specifikke DGWS klasser. Når der genereres en proxy til hhv. klient eller server, dannes disse klasser på typestærk form i den autogenererede proxy.

Eksempler på genererede klasser er Security, Assertion og Header, som vises længere nede.

Seal.NET indeholder en række IEndPointBehavior-klasser som kan benyttes af WCF service references.

Klienter

Saml2SosiStsClient

Konverter en SAML assertion til et ID kort.

Eksempel:

using dk.nsi.seal;

var response = Saml2SosiStsClient.ExchangeAssertion(clientCertificate, new Uri("http://test2.ekstern-test.nspop.dk:8080/sts/services/OIOSaml2Sosi"), nemidAssertion, authorizationCode);

Soisi2SamlStsClient

Konverterer et ID kort til en SAML assertion,  enten via direkte kald til STS eller via SOSI Gateway.

Eksempel:

using dk.nsi.seal;

var response = Sosi2SamlStsClient.ExchangeAssertion(clientCertificate, new Uri("http://test1.ekstern-test.nspop.dk:8080/sosigw/proxy/soap-request"), "/ststest", idCard);
var responseXml = response.XAssertion;

// Der findes også et overload der ikke tager et klientcertifikat
Sosi2SamlStsClient.ExchangeAssertion(uri, "/ststest", idCard);

ID kort


IdCard indpakker svaret fra en STS (assertion) og giver mulighed for at benytte data i fremtidige kald.

IdCard er en abstract klasse, som implementeres af SystemIdCard og UserIdCard, som hhv. repræsenter enten et system eller en bruger.

MessageHeaders

Disse MessageHeaders bruges til at indsætte relevant data i headeren for et request, som er lavet med en klient genereret ud fra en WCF service reference.

For at stemme overens med service references, implementerer MessageHeader-klasserne 'System.ServiceModel.Channels.Message'

fra https://www.nuget.org/packages/System.ServiceModel.Primitives/, som er det service references arbejder med. 


Rent lavpraktisk betyder det at MessageHeaders kan gives til 'OutgoingMessageHeaders.Add', og så bliver den data MessageHeaderen har, sat ind i headeren på det request man laver.

Nedenstående eksempler demonstrerer dette, hvor dk.nsi.seal.dgwstypes.Header og NSTWsProvider.NtsWSProviderClient er genereret ud fra en WSDL, ved at bruge nævnte WCF service references.

IdCardMessageHeader 

Benyttes til at tilføje et ID kort til headeren af et WCF request.

Laves med en static constructor 'IdCardMessageHeader.IdCardHeader'.

Eksempel:

using static dk.nsi.seal.MessageHeaders.IdCardMessageHeader;

var client = new NtsWSProviderClient(new BasicHttpsBinding(), new EndpointAddress("https://test1-cnsp.ekstern-test.nspop.dk:8443/nts/service"));

using (new OperationContextScope(client.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(IdCardHeader(idCard)); // Tilføj ID Kort med IdCardHeader 
    var response = client.invokeAsync("test");
}

XmlMessageHeader

Den Gode Webservice (DGWS) specificerer en ekstra headertype baseret på XML ( dk.nsi.seal.dgwstypes.Header), som kan indsættes ud fra et objekt, der serialiseres til XML med XmlMessageHeader.

XmlMessageHeader indeholder en static constructor-metode, XmlHeader, til at lave instanser af den.

Eksempel på XmlHeader:

using static dk.nsi.seal.MessageHeaders.XmlMessageHeader;

...

reply.Headers.Add(XmlHeader(header));

Mere komplet eksempel hvor dk.nsi.seal.dgwstypes.Header serialiseres til XML og indsættes:

var client = new NSTWsProvider.NtsWSProviderClient(binding, new EndpointAddress("https://test1-cnsp.ekstern-test.nspop.dk:8443/nts/service"));

var dgwsHeader = new dk.nsi.seal.dgwstypes.Header()
{
    SecurityLevel = 4,
    SecurityLevelSpecified = true,
    Linking = new Linking { MessageID = Guid.NewGuid().ToString("D") }
};

using (new OperationContextScope(client.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(XmlHeader(dgwsHeader)); // Indsæt header som XML, ud fra serialiseret header
    var response = client.invokeAsync("test");
}

Endpointbehaviors

Bruges til at tilføje processering af requests og responses af WCF klienter.

Eksempel:

var client = new NSTWsProvider.NtsWSProviderClient(new BasicHttpBinding(), new EndpointAddress("https://test1-cnsp.ekstern-test.nspop.dk:8443/nts/service"));
 
client.Endpoint.EndpointBehaviors.Add(new ViaBehavior(new Uri("http://test1.ekstern-test.nspop.dk:8080/sosigw/proxy/soap-request"))); // Tilføj Via endpointbehavior
 
var dgwsHeader = new Header()
{
    SecurityLevel = 4,
    SecurityLevelSpecified = true,
    Linking = new Linking { MessageID = Guid.NewGuid().ToString("D") }
};
 
using (new OperationContextScope(client.InnerChannel))
{
    var response client.invokeAsync("test");
}


Findes under namespace 'dk.nsi.seal'. Endpointbehaviors implementer System.ServiceModel.Dispatcher.IClientMessageInspector fra NuGet Gallery | System.ServiceModel.Primitives 4.10.0.


SealSigningEndpointBehavior

Tilpasser request XML header med manglende DGWS attributter og underskriver request vha. et associeret certifikat der gives til dens constructor. Yderligere valideres underskriften af response.

SealEndpointBehavior

TODO

ViaBehavior

Portering af ClientViaBehavior Class (System.ServiceModel.Description) | Microsoft Learn til .NET Standard.

Meget simpel klasse, der internt blot sætter en enkelt værdi til den via URL man angiver.

Factories


SosiFactory har static metoder der kan generere nye IdCards.


OioSamlFactory kan generere nye Requests. Normalt laver man en DOM builder som genererer selve XML'en og bagefter en ModelBuilder for at lave selve Requesten. Requesten kan så blive sendt ved hjælp af dk.nsi.seal.SealUtilities.SignIn funktion. Derudover kan man sende requests fra OioSamlFactory afsted uden brug af klienter.

DomBuilders


Dombuilders er et hierarki, som bliver brugt til at generere XML kode. Til venstre er der 3 klasser der håndterer bygning af selve assertions, mens det højre klasse hierarki bliver brugt til at generere selve SOAP beskeden uden Assertion.

Requests


Requests er selve modeller over XML'en. De har diverse convenience methods til at hente informationer direkte ud fra XML'en.

Vaults

Vaults bruges til at holde certifikater, eller læse dem ind fra enhedens trust store. ThumbprintCertStoreCredentialVault læser certifikater fra enhedens trust store. 'InMemoryCredentialVault' er et simpelt dataobjekt, der indeholder et certifikat.

De er forholdsvis simple klasser med én metode, 'GetSystemCredentials', som returner et enkelt certifikat.


'InMemoryCredentialVault' kan bruges når man selv ønsker at bestemme hvordan certifikater læses ind - man læser certifikatet ind som et X509Certificate2, og giver det til 'InMemoryCredentialVault', som så kan gives videre til metoderne i Seal.NET.

På sigt kan dette blive simplificeret, så man direkte kan give et X509Certificate2, i stedet for at man skal forbi det ICredentialVault interface, som vaults arver fra.


Findes under namespace 'dk.nsi.seal.Vault'.

Federation

Federations bliver brugt når man skal validere certfikatet fra den service man snakker med. En Federation gives til 'dk.nsi.seal.Model.SignatureUtil.Validate'.

Klassen 'SosiFederation' vil validere servicens certifikat op mod OCES2/OCES3 root certifikatet for at sikre, at servicen er autoriseret.

Klassen 'SosiTestFederation' vil validere op mod OCES2/OCES3 test root certifikat.

Hvis SignatureUtil.Validate kaldes uden en Federation, skal root certifikatet af den pågældende services certifikat (eller selve certifikatet) ligge i trust store på den enhed programmet eksekveres af.

På windows vil det betyde at importere det givne certifikat som et 'trusted root certificate', med programmet 'Manage Computer Certificates'.


Hjælpefunktioner


Indeholder forskellige funktioner. F.eks. til at signere og validere en signatur.

Brug af Seal.Net Api

I dette afsnit beskrives overordnet hvordan Seal.Net benyttes til at opbygge en klient eller en service applikation.

Konfiguration

Der er to slags valideringer af et certifikat der kan aktiveres/deaktiveres i appsettings.json.

CheckTrust

CheckTrust specificerer om certifikatet skal valideres op mod et root certifikat og om chainen skal valideres. Hvis en federation er specificeret så er det valideringen mod den federation, som bliver aktiveret-deaktiveret.

Skal slås fra ved self signed certifikater.

{
    "CheckTrust": true
}

CheckTrust er per default True.

CheckCrl

CheckCrl specificerer om certifikatet skal checkes op mod en Certificate Revocation List. Skal slås fra ved self-signed certifikater.

{
    "CheckCrl": false
}

CheckCrl er per default False.

Klienteksempler

En klient til en WebService af typen Den Gode Webservice benyttes som enhver anden webservice.
I Visual Studio tilføjes en Service-Reference til den pågældende WSDL. Herefter genereres en proxy indeholdende alle datatyper for webservicen.
Det er også muligt at benytte WCF svcutil tool overview - .NET | Microsoft Learn hvis Visual Studio ikke er tilstrækkelig.
Herefter kan der være flere klient-scenarier:

  1. Direkte kald af Service (ikke føderalt): Her oprettes et kort lokalt og Service Udbyderen kaldes med det oprettede kort. Det er så op til Service Udbyderen at autentificere brugeren.
  2. Føderalt: Et kort oprettes lokalt valideres via et kald til en STS, som returnerer et kort der er digitalt underskrevet. Dette kort benyttes til fremtidige kald af webservices. Service Udbyderen skal nu kun autentificere STS'en.
  3. Via NemId: Hvis en bruger allerede er logget på et system via NemLogin, kan dette login benyttes til at kalde en webservice. Til NemLogin er associeret en SAML token. Denne token skal veksles til et DGWS ID kort. Når ID kortet er modtaget, benyttes det til fremtidige kald.
  4. SOSI Gateway: Kan benyttes til at hente en token til Sikker Browser Opstart (SBO).

Til efterfølgende eksempler benyttes nogle metoder til at oprette instanser af datatyper.
MakeHeader og MakeSecurity.
Disse metoder opretter instanser af de førnævnte klasser; Security og Header.
Der benyttes desuden variablerne callingSystem og user, som kan ses under " eksempler på generering af proxyklasser". TODO
Eksemplerne viser et kald til en FKM webservice. Metodekaldet er GetMedicineCard_2015_06_01

Direkte kald

var client = new MedicineCardPortTypeClient("MedicineCardPort");

GenericCredentialVault vault = new GenericCredentialVault("FMKTestStore");

vault.SetSystemCredentials(user.Certificate);
CredentialVaultSignatureProvider sigProvider= new CredentialVaultSignatureProvider(vault);
SOSIFactory factory = new SOSIFactory(null, sigProvider);

UserInfo userInfo = new UserInfo(user.Cpr, user.GivenName, user.SurName, user.Email, user.Occupation, user.Role, user.AuthCode);

var idCardRequest = factory.CreateNewUserIdCard(callingSystem.SystemName, userInfo, new CareProvider(callingSystem.CareProviderIdFormat, callingSystem.CareProviderId, callingSystem.CareProviderName), AuthenticationLevel.MocesTrustedUser, "", "", user.Certificate, "");

idCardRequest.Sign<Assertion>(factory.SignatureProvider);

var response = client.GetMedicineCard_2015_06_01(

new GetMedicineCardRequest_2015_06_01
{
Security = SecurityHeaderUtil.MakeSecurityUsingDgwsTypes(idCardRequest),
Header = MakeHeader(),
WhitelistingHeader = MakeWhitelistingHeader(),
GetMedicineCardRequest = new GetMedicineCardRequestType
{
PersonIdentifier = new PersonIdentifierType
{
source = "CPR",
Value = "1802602810"
}
}
});

  1. Der oprettes en instans af FMK klientklassen der benytter den navngivne konfiguration.
  2. Der initieres en instans SOSIFactory til at generere et IdCard
  3. Et UserIdCard genereres vha. factory'en.
  4. Id-kortet signeres.
  5. Servicen kaldes.


Følgende konfiguration er benyttes i ovenstående eksempel.
<endpoint
address="https://test2.fmk.netic.dk/fmk12/ws/MedicineCard"
behaviorConfiguration="sealbehavior"
binding="basicHttpBinding"
bindingConfiguration="MedicineCardBinding"
contract="MedicinCard.MedicineCardPortType"
name="MedicineCardPort"
/>
<basicHttpBinding>
<binding name="MedicineCardBinding">
<security mode="Transport"/>
</binding>
</basicHttpBinding>
<behavior name="sealbehavior" >
<sbhe/>
</behavior>
<behaviorExtensions>
<add name="sbhe" type="dk.nsi.seal.SealBehaviorExtentionElement, Seal" />
</behaviorExtensions>
Som det ses er benyttes der en customBinding uden sikkerhed og der benyttes SealBehaviorExtentionElement til at aktivere SealEndpointBehavior.

Føderalt

I et føderalt login skal en bruger først logges på føderationen

  1. var factory = CreateFactory();

factory.GetCredentialVault().SetSystemCredentials(user.Certificate);
UserInfo userInfo = new UserInfo(user.Cpr, user.GivenName, user.SurName, user.Email, user.Occupation, user.Role, user.AuthCode);
var idCardRequest = factory.CreateNewUserIdCard(system.SystemName, userInfo, new CareProvider(system.CareProviderIdFormat, system.CareProviderId, system.CareProviderName), AuthenticationLevel.MocesTrustedUser, "", "", user.Certificate, "");
idCardRequest.Sign<Assertion>(factory.SignatureProvider);

  1. var idc = SealUtilities.SignIn(idCardRequest, system.SystemName, "http://test2.ekstern-test.nspop.dk:8080/sts/services/NewSecurityTokenService");
  2. var client = new MedicineCardPortTypeClient("MedicineCardPort");
  3. var response = client.GetMedicineCard_2015_06_01(

new GetMedicineCardRequest_2015_06_01
{
Security = SecurityHeaderUtil.MakeSecurityUsingDgwsTypes(idc),
Header = requestHeader,
WhitelistingHeader = makeWhitelistingHeader,
GetMedicineCardRequest = new GetMedicineCardRequestType
{
PersonIdentifier = new PersonIdentifierType
{
source = "CPR",
Value = requestCpr
}
}
});

  1. Der initieres et signeret IdCard vha. en SOSIFactory ud fra user og callingSystem
  2. STS kaldes med kortet. Der returneres et nyt IdCard underskrevet af STS
  3. Der oprettes en instans af Service proxy klientklassen, i dette tilfælde FMK.
  4. Der oprettes et Security element der indeholder den Assertion der er underskrevet af STS, samt et MedcomHeader element. Herefter kaldes Servicen.

Konfigurationen er det samme som ved Direkte kald i forrige afsnit.
<endpoint
address="https://test2.fmk.netic.dk/fmk12/ws/MedicineCard"
behaviorConfiguration="sealbehavior"
binding="basicHttpBinding"
bindingConfiguration="MedicineCardBinding"
contract="MedicinCard.MedicineCardPortType"
name="MedicineCardPort"
/>
<basicHttpBinding>
<binding name="MedicineCardBinding">
<security mode="Transport"/>
</binding>
</basicHttpBinding>
<behavior name="sealbehavior" >
<sbhe/>
</behavior>
<behaviorExtensions>
<add name="sbhe" type="dk.nsi.seal.SealBehaviorExtentionElement, Seal" />
</behaviorExtensions>

SOSI Gateway

Nå SOSI Gateway skal benyttes skal der ført oprettes en service reference SOSI Gateway. Hvis ikke det allerede er oprettet, oprettes også en service reference til servicen, i nedenstående eksempel FMK.
Følgende kode opretter et login på SOSI Gateway.


using GW = SealTest.SosiGWReference;
private static Assertion LoginToGateway(IdCard idc, X509Certificate2 certificate)
{
// Convert the dgwsType Assertion into a sosi gateway Assertion.
var assertion = idc.GetAssertion<GW.AssertionType>();
var security = new GW.Security
{
Timestamp = new GW.Timestamp { Created = DateTimeEx.UtcNowRound - TimeSpan.FromMinutes(5) },
Assertion = assertion
};
using (var gwClient = new GW.SosiGWFacadeClient())
{
// Get an digest form the sosi gateway that should be signed
var dig = gwClient.requestIdCardDigestForSigning(security, "whatever");
// Create SHA1 hash of digest
var sha1Managed = new SHA1Managed();
var computeHash = sha1Managed.ComputeHash(dig.DigestValue);
// Get the private key
var privateKey = (RSACryptoServiceProvider) certificate.PrivateKey;
// Calculate the needed signature
var signatureValue = privateKey.SignHash(computeHash, CryptoConfig.MapNameToOID("SHA1"));
var cardRequestBody = new GW.signIdCardRequestBody
{
SignatureValue = signatureValue,
KeyInfo = new GW.KeyInfo
{
Item = new GW.X509Data {Item = certificate.Export(X509ContentType.Cert)}
}
};
// The unsigned id-card in the cache is combined with the signature and the certificate and is sent to STS.
// The STS signed card is saved in the sosiGw cache and is used for future calls through the proxy
var res = gwClient.signIdCard(security, cardRequestBody);
if (res != GW.signIdCardResponse.ok)
{
throw new Exception("Gateway logon error");
}
// Convert the GW Assertion to a dgwsType Assertion for later use.
idc.Xassertion = SerializerUtil.Serialize(security.Assertion).Root;
return idc.GetAssertion<Assertion>(typeof(GW.AssertionType).Name);
}

  1. var factory = CreateFactory();


var idCardRequest = factory.CreateNewUserIdCard(system.SystemName, userInfo, new CareProvider(system.CareProviderIdFormat, system.CareProviderId, system.CareProviderName), AuthenticationLevel.MocesTrustedUser, "", "", user.Certificate, "");

  1. LoginToGateway(idCardRequest, global.NsiLge1);


  1. var client = new MedicineCardPortTypeClient("SosiGWFMK");
  2. var response = client.GetMedicineCard_2015_06_01(

new GetMedicineCardRequest_2015_06_01
{
Security = SecurityHeaderUtil.MakeSecurityUsingDgwsTypes(idc),
Header = requestHeader,
WhitelistingHeader = makeWhitelistingHeader,
GetMedicineCardRequest = new GetMedicineCardRequestType
{
PersonIdentifier = new PersonIdentifierType
{
source = "CPR",
Value = requestCpr
}
}
});


1. Opretter en SosiFactory og IdCardRequest
2. Kalder LoginToGateway som har inline kommentar
3. FMK klient oprettes.
4. Service kaldes
Konfiguration:
SOSIGW
<endpoint address="http://test2.ekstern-test.nspop.dk:8080/sosigw/service/sosigw"
binding="basicHttpBinding"
contract="SosiGwService.SosiGWFacade"
name="SosiGWSoapBinding"/>
FMK
<endpoint address="https://test2.fmk.netic.dk/fmk12/ws/MedicineCard"
behaviorConfiguration="AddressingBehavior"
binding="customBinding"
bindingConfiguration="Soap11Http"
contract="MedicinCard.MedicineCardPortType"
name="SosiGWFMK"/>
<customBinding>
<binding name="Soap11Http">
<textMessageEncoding messageVersion="Soap11WSAddressing10" writeEncoding="utf-8" />
<httpTransport />
</binding>
</customBinding>

<behavior name="AddressingBehavior">
<clientVia viaUri="http://test2.ekstern-test.nspop.dk:8080/sosigw/proxy/soap-request"/>
</behavior>

SOSI Gateway SBO

SOSI Gateway kan bruges til SBO (Sikker Browser Opstart).

Det første der skal gøres er at logge ind i SOSI GW. Dette gøres ved at sende det ID kort man har til SOSI Gateway, som vil gemme kortet i dens cache.

Ved efterfølgende kald til SOSI GW vil den kun kigge på Name ID (også kaldet Alternative Identifier i andre sammenhænge) i ID kortet der sendes, 

og kigge i dens cache om den har et ID kort med samme Name ID liggende. Hvis den har, vil den erstatte ID kortet i det request den har modtaget, med det der ligger i dens cache.


Først logges der ind i gateway'en:

public static async Task LoginToGateway(IdCard idCard, X509Certificate2 userCert)
{
    var sosiGwAssertion = idCard.GetAssertion<AssertionType>();
    var security = new GW.Security
    {
        Timestamp = new GW.Timestamp { Created = DateTimeEx.UtcNowRound - TimeSpan.FromMinutes(5) },
        Assertion = sosiGwAssertion
    };
    const string endpointAddress = "http://test1.ekstern-test.nspop.dk:8080/sosigw/service/sosigw";
    using var gwClient = new SosiGWFacadeClient(SosiGWSoapBinding, endpointAddress);
    var dig = (await gwClient.requestIdCardDigestForSigningAsync(security, "whatever")).requestIdCardDigestForSigningResponse;
    var digestHash = SHA1.HashData(dig.DigestValue);
    var signature = userCert.GetRSAPrivateKey().SignHash(digestHash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
    var cardRequestBody = new signIdCardRequestBody
    {
        SignatureValue = signature,
        KeyInfo = new GW.KeyInfo
        {
            Item = new GW.X509Data { Item = userCert.Export(X509ContentType.Cert) }
        }
    };
    // The STS signed card is saved in the sosiGw cache and is used for future calls through the proxy
    var res = (await gwClient.signIdCardAsync(security, cardRequestBody)).signIdCardResponse;
    if (res != signIdCardResponse.ok) { throw new Exception("Gateway logon error"); }
    // Convert the GW Assertion to a dgwsType Assertion
    var sosiGwAssertionDocument = SerializerUtil.Serialize(sosiGwAssertion).Root;
    SerializerUtil.Deserialize<Assertion>(sosiGwAssertionDocument, typeof(AssertionType).Name);
}

Her bruges en WCF service reference, SosiGWFacadeClient, der er lavet ud fra en WSDL fil.


Herefter kan ID kort ombyttes til SAML tokens med 'Sosi2SamlStsClient':

var uri = new Uri("http://test1.ekstern-test.nspop.dk:8080/sosigw/proxy/soap-request");
var response = Sosi2SamlStsClient.ExchangeAssertion(uri, "Invalid audience", IdCard).XAssertion;

SOSI Gateway SBO

En token til SBO kan hentes fra SOSI Gateway på nedenstående måde. Det forudsættes at der er logget på SOSI Gateway og dermed er assertion initieret.
Endpoint refererer til STS og ClientVia refererer til SOSI Gateway.
using (var stsClient = new Seal2SamlStsClient("GWFetchCard"))
using (var scope = new OperationContextScope((IContextChannel)stsClient.Channel.Channel))
{
var factory = CreateFactory();
OperationContext.Current.OutgoingMessageHeaders.Add(new IdCardMessageHeader( factory.DeserializeIdCard(assertion)));
var d = stsClient.ExchangeAssertionViaGW( "http://sundhed.dk/") as GenericXmlSecurityToken;
var elm = d.TokenXml;
}

Minimal eksempelkonfiguration:

<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="Soap11Http">
                    <textMessageEncoding messageVersion="Soap11" writeEncoding="utf-8" />
                    <httpTransport />  <!-- If client certificate is required, use '<httpsTransport requireClientCertificate="true" />' instead, -->
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint
            address="http://test1.ekstern-test.nspop.dk:8080/sts/services/Sosi2OIOSam"
            behaviorConfiguration="AddressingBehavior"
            binding="customBinding"
            bindingConfiguration="Soap11Http"
            contract="System.ServiceModel.Security.IWSTrustChannelContract"
            name="GWFetchCard"
            />
        </client>
        <behaviors>
            <endpointBehaviors>
                <behavior name="AddressingBehavior">
                    <clientVia viaUri="http://test2.ekstern-test.nspop.dk:8080/sosigw/proxy/soap-request" />
                    <clientCredentials>
                        <clientCertificate findValue="Insert certificate thumprint here" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
                    </clientCredentials>
                    <DccSealBehaviorBE />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="DccSealBehaviorBE" type="dk.nsi.seal.DccSealBehaviorExtentionElement, Seal" />
            </behaviorExtensions>
        </extensions>
    </system.serviceModel>
</configuration>

Eksempler ved brug af OioSamlFactory

Brug af NemId:

Efterfølgende kode viser hvordan en NemidLogin Saml2Assertion konverteres til en DGWSAssertion. For herefter at oprette en Security instans der indeholder denne Assertion og kalde en FMK service.

  1. var callingSystem = new CallingSystem(

systemName: "Seal.Net.Fmk.Demo",
careProviderId: "30808460",
careProviderName: "TRIFORK SERVICES A/S",
careProviderIdFormat:
dk.nsi.seal.dgwstypes.SubjectIdentifierType.medcomcvrnumber,
certificate: new X509Certificate2("Resources/certificates/Statens_Serum_Institut_FOCES.p12", "Test1234"));
var user = new User(
cpr: "1802602810",
givenName: "Stine",
surName: "Svendsen",
email: "stineSvendsen@example.com",
role: "læge",
authCode: "ZXCVB",
occupation: "Overlæge",
certificate:
new X509Certificate2("Resources/certificates/MOCES_cpr_gyldig.p12", "Test1234"));

  1. var userVault = new GenericCredentialVault("UserVault");

userVault.SetSystemCredentials(user.Certificate);
// This nemidAssertion is expected to have been generated at an earlier login.

  1. var nemidAssertion = NemIdAssertionBuilder.MakeNemIdAssertion(user.Certificate, system.Certificate, system, user);
  2. var assertion = new OioSamlAssertion(samlAssertion);
  3. var factory = new OIOSAMLFactory();
  4. var domBuilder = factory.CreateOiosamlAssertionToIdCardRequestDomBuilder();

domBuilder.SigningVault = userVault;
domBuilder.OioSamlAssertion = assertion;
domBuilder.ItSystemName = system.SystemName;
domBuilder.UserAuthorizationCode = user.AuthCode;
domBuilder.UserEducationCode = null;
domBuilder.UserGivenName = user.GivenName;
domBuilder.UserSurName = user.SurName;

  1. var requestDoc = domBuilder.Build();


  1. var request = factory.CreateOioSamlAssertionToIdCardRequestModelBuilder() .Build(requestDoc);
  2. var idCard = SealUtilities.SignIn(request, "http://test2.ekstern-test.nspop.dk:8080/sts/services/OIOSaml2Sosi");


  1. var client = new MedicineCardPortTypeClient(_fmkEndpoint);
  2. using (new OperationContextScope(client.InnerChannel))

{
var response = client.GetMedicineCard_2015_06_01(
new GetMedicineCardRequest_2015_06_01
{
Security = SecurityHeaderUtil.MakeSecurityUsingDgwsTypes(idCard),
Header = MakeHeader(),
WhitelistingHeader = MakeWhitelistingHeader(),
GetMedicineCardRequest = new GetMedicineCardRequestType
{
PersonIdentifier = new PersonIdentifierType
{
source = "CPR",
Value = "2603558084"
}
}
});
Her kan man se brugen af OioSamlFactory til at udveksle en Nemid assertion med FMK.

  1. Generer bruger og system information for nem tilgængelighed.
  2. Opret ny CredentialVault med bruger certifikat.
  3. Opret en ny Saml2Assertion ud fra informationerne.
  4. Opret OioSamlAssertion ud fra NemidAssertion
  5. Opret ny OioSamlFactory
  6. Opret ny OiosamlAssertionToIdCardRequestDomBuilder og initialiser med korrekte informationer.
  7. Byg dokumentet
  8. Opret OioSamlAssertionToIdCardRequestModelBuilder og byg requesten.
  9. Brug Sealutilities.Signin til at sende requesten til serveren. Returnerer nyt IdCard signed fra STS
  10. Opret ny klient til FMK kommunikation
  11. Kald FMK.

2.3.2. BST2SOSI

Oprettelsen af et bst2sosi request er meget i stil med et bst2idws request:




2.3.3 OIO2BST_CITIZEN

Det er muligt at genere, parse og validere OIO2BST_CITIZEN bootstrap tokens. Dette sker via klasserne OIO2BSTCitizenSAMLAssertionBuilder og OIO2BSTCitizenSAMLAssertion klasserne. OIOSAMLFactory anvendes til at lave en instans af OIO2BSTCitizenSAMLAssertionBuilder.

For kryptering og dekryptering af disse tokens kan der hentes inspiration i testen TestEncryptDecrypt i klassen OIO2BSTCitizenTest.

2.4. OIOBSTSAMLAssertion-factory

OIOBSTSAMLAssertionFactory benyttes til at oprette OIO bootstraptoken objekter givet et xml element.

Sådanne bootstraptokens kan enten være for medarbejdere (hertil benyttes createOIOBSTSAMLAssertion(...)) eller borgere (hertil benyttes createOIOBSTCitizenSAMLAssertion(...)), og identificeres ud fra deres spec version attribut.

Eksempler på generering af objekter fra proxy

Nedenstående eksempler viser initieringer at klasser der er autogenereret ud fra en DGWS WSDL.

CallingSystem og User


var callingSystem = new CallingSystem(
systemName: "Seal.Net.Fmk.Demo",
careProviderId: "30808460",
careProviderName: "TRIFORK SERVICES A/S",
careProviderIdFormat: dk.nsi.seal.dgwstypes.SubjectIdentifierType.medcomcvrnumber,
certificate: new X509Certificate2("Resources/certificates/Statens_Serum_Institut_FOCES.p12", "Test1234"));
var user = new User(
cpr: "1802602810",
givenName: "Stine",
surName: "Svendsen",
email: "stineSvendsen@example.com",
role: "læge",
authCode: "ZXCVB",
occupation: "Overlæge",
certificate: new X509Certificate2("Resources/certificates/MOCES_cpr_gyldig.p12", "Test1234"));

Security

Nedenstående eksempel opretter en Security element Timestamp sættes til at være 5 minutter gammel.
static Security MakeSecurity( Assertion assertion)
{
return new Security
{
id = Guid.NewGuid().ToString("D"),
Timestamp = new Timestamp { Created = DateTime.Now - TimeSpan.FromMinutes(5) },
Assertion = assertion
};
}

Header


static Header MakeHeader()
{
return new Header
{
SecurityLevel = 3,
TimeOut = TimeOut.Item1440,
TimeOutSpecified = true,
Linking = new Linking
{
FlowID = Guid.NewGuid().ToString("D"),
MessageID = Guid.NewGuid().ToString("D")
},
FlowStatus = FlowStatus.flow_running,
FlowStatusSpecified = true,
Priority = Priority.RUTINE,
RequireNonRepudiationReceipt = RequireNonRepudiationReceipt.yes
};
}


Make NemId Assertion

public static Saml2Assertion MakeNemIdAssertion(
X509Certificate2 userCertificate,
X509Certificate2 signingCertificate,
CallingSystem system,
User user
)
{
var ass = new Saml2Assertion(new Saml2NameIdentifier("https://saml.test-nemlog-in.dk/"))
{
Conditions = new Saml2Conditions
{
NotOnOrAfter = DateTime.Now + TimeSpan.FromHours(8),
NotBefore = DateTime.Now
},
Subject = new Saml2Subject(new Saml2NameIdentifier(userCertificate.SubjectName.Name))
};
ass.Subject.SubjectConfirmations.Add(
new Saml2SubjectConfirmation(new Uri("urn:oasis:names:tc:SAML:2.0:cm:bearer"))
{
SubjectConfirmationData = new Saml2SubjectConfirmationData
{
NotOnOrAfter = DateTime.Now + TimeSpan.FromHours(8),
Recipient = new Uri("https://staging.fmk-online.dk/fmk/saml/SAMLAssertionConsumer")
}
});
IList<Saml2Attribute> q = new List<Saml2Attribute>();
// Spec
q.Add(new Saml2Attribute(OioSamlAttributes.SpecVersion, SpecVersion) {NameFormat = BasicNameFormat});
// User
q.Add(new Saml2Attribute(OioSamlAttributes.CommonName, user.GivenName) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.Surname, user.SurName) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.Email, user.Email) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.CprNumber, user.Cpr) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.AssuranceLevel, "4") {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.UserCertificate, Convert.ToBase64String(userCertificate.RawData)) {NameFormat = BasicNameFormat});
// Organization
q.Add(new Saml2Attribute(OioSamlAttributes.CvrNumber, system.CareProviderId) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.OrganizationName, system.CareProviderName) {NameFormat = BasicNameFormat});
// Certificate
var subjectSerialNumber = userCertificate.SubjectName.Name;
q.Add(new Saml2Attribute(OioSamlAttributes.CertificateSerial, userCertificate.GetSerialNumberString()) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.CertificateIssuer, userCertificate.IssuerName.Name) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.Uid, ExtractUidNumber(subjectSerialNumber)) {NameFormat = BasicNameFormat});
q.Add(new Saml2Attribute(OioSamlAttributes.RidNumber, ExtractRidNumber(subjectSerialNumber)) {NameFormat = BasicNameFormat});
ass.Statements.Add(new Saml2AttributeStatement(q));
ass.Statements.Add(
new Saml2AuthenticationStatement(
new Saml2AuthenticationContext(new Uri("element:urn:oasis:names:tc:SAML:2.0:ac:classes:X509")),
DateTime.Now));
ass.SigningCredentials = new X509SigningCredentials(signingCertificate, SignedXml.XmlDsigRSASHA1Url, SignedXml.XmlDsigSHA1Url);
return ass;
}

Eksempler

Seal.Net indeholder en række integrationstests. Disse har tilformål at verificere at de genererede requests rent faktisk kan forstås af STS'en og som inspiration til anvendere.

Bootstrap token tests

I klassen OIOBSTTests findes der en række tests der veksler et boot strap token til et SOSI ID kort. Der er tests af OIOH2BST, OIOH3BST og OIO3BST.

SOSI Gatweay

I klassen SosiGwTest er der tests af kald til SOSI Gateway. Her er der både test af kald mod createIdCardFromBST og requestIdCardDigestForSigning.

Referencer

Forkortelse i teksten

Henvisning

<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="a906cdd8-a856-48fa-b3af-9e69d149de6f"><ac:plain-text-body><![CDATA[

[SAML2.0]

https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=security

]]></ac:plain-text-body></ac:structured-macro>

<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="7e8fddac-a1c4-4c1e-893d-d02f891e609d"><ac:plain-text-body><![CDATA[

[OIOSAML]

http://digitaliser.dk/resource/2377872

]]></ac:plain-text-body></ac:structured-macro>

<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="923053d9-c34e-46e7-879d-75fa83f9df93"><ac:plain-text-body><![CDATA[

[OIOIDWS]

http://digitaliser.dk/resource/526486

]]></ac:plain-text-body></ac:structured-macro>