diff --git a/.gitignore b/.gitignore index 4672598..feb6aa2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ Thumbs.db # backup files # ################ *.bak +target +src diff --git a/input/fsh/codesystem/BeCSPseudonymizationType.fsh b/input/fsh/codesystem/BeCSPseudonymizationType.fsh new file mode 100644 index 0000000..ae40819 --- /dev/null +++ b/input/fsh/codesystem/BeCSPseudonymizationType.fsh @@ -0,0 +1,8 @@ +CodeSystem: BeCSPseudonymizationType +Id: be-cs-pseudonymization-type +Title: "Types of pseudonymization" +Description: "The technique used for pseudonymization" +* ^experimental = false +* ^caseSensitive = true +* #direct "Direct pseudonym, for text shorter than 32 bytes" +* #encrypted "Encrypted pseudonym, for text longer than 32 bytes" \ No newline at end of file diff --git a/input/fsh/codesystem/BeCSPseudonymizationVersion.fsh b/input/fsh/codesystem/BeCSPseudonymizationVersion.fsh new file mode 100644 index 0000000..43395be --- /dev/null +++ b/input/fsh/codesystem/BeCSPseudonymizationVersion.fsh @@ -0,0 +1,9 @@ +CodeSystem: BeCSPseudonymizationVersion +Id: be-cs-pseudonymization-version +Title: "BeCSPseudonymizationVersion" +Description: "List of pseudonymization versions that can be used a.o. in the Capabilities Statement" +* ^experimental = false +* ^caseSensitive = true +* #urn:be:fgov:ehealth:pseudo:v1 +* #urn:be:fgov:ehealth:pseudo:v2 +* #urn:be:fgov:pseudo-encrypted:v1 diff --git a/input/fsh/extensions/BeExtKeyPseudonymization.fsh b/input/fsh/extensions/BeExtKeyPseudonymization.fsh new file mode 100644 index 0000000..44a371b --- /dev/null +++ b/input/fsh/extensions/BeExtKeyPseudonymization.fsh @@ -0,0 +1,15 @@ +Extension: BeExtKeyPseudonymization +Id: be-ext-key-pseudonymization +Title: "Key Pseudonymization Extension" +Description: "This holds a pseudonymized key that can be used for all encrypted fields in the resource (long text pseudonymisation)" +* ^context.type = #element +* ^context.expression = "Meta" +* . ^short = "Pseudonymization key (See Blinded Pseudonymization Cookbook, Annex: Processing of input data exceeding 32 +bytes)" +* id 1..1 MS +* id ^short = "kid (also available in JWE)" +* extension contains key 1..1 MS +* extension[key].value[x] only string +* extension[key].valueString 1..1 MS +* extension[key].valueString ^short = "pseudonymized key" +* extension[key].value[x].extension contains BeExtPseudonymization named pseudonymization 1..1 diff --git a/input/fsh/extensions/BeExtPseudonymization.fsh b/input/fsh/extensions/BeExtPseudonymization.fsh index fb098f5..b8c51e2 100644 --- a/input/fsh/extensions/BeExtPseudonymization.fsh +++ b/input/fsh/extensions/BeExtPseudonymization.fsh @@ -1,10 +1,20 @@ Extension: BeExtPseudonymization Id: be-ext-pseudonymization Title: "Pseudonymization Extension" -Description: "This is a marker interface. If the field is pseudonymized, the string field SHALL have this extension. The original text field SHALL contain the x, y and transitInfo in JWE encoded form. transitInfo is optional depending on the situation." +Description: "This is a marker interface. If the field is pseudonymized, the string field SHALL have this extension. " * ^context.type = #element * ^context.expression = "Element" * . ^short = "Pseudonymization data" -* extension contains marker 1..1 MS +* extension contains marker 1..1 MS and + format 0..1 MS and + version 0..1 MS * extension[marker].value[x] only boolean -* extension[marker].valueBoolean = true \ No newline at end of file +* extension[marker].valueBoolean = true +* extension[format].value[x] only code +* extension[format].valueCode 0..1 MS +* extension[format].valueCode ^short = "provide encrypted only in case of Blinded Pseudonymization Cookbook, Annex: Processing of input data exceeding 32 +bytes" +* extension[format].valueCode from BeVSPseudonymizationType +* extension[version].value[x] only positiveInt +* extension[version].valuePositiveInt 0..1 MS +* extension[version].valuePositiveInt ^short = "version of the pseudonym encoding" \ No newline at end of file diff --git a/input/fsh/instances/bundle1.fsh b/input/fsh/instances/bundle1.fsh new file mode 100644 index 0000000..07874af --- /dev/null +++ b/input/fsh/instances/bundle1.fsh @@ -0,0 +1,7 @@ +Instance: bundle1 +InstanceOf: Bundle +* type = #collection +* entry[+].resource = patient1 +* entry[=].fullUrl = "urn:uuid:be78855a-2ac2-4907-a209-c018ab7bbaa5" +* entry[+].resource = patient2 +* entry[=].fullUrl = "urn:uuid:2c9e9f79-1a05-4a34-ab1b-07328c53a323" \ No newline at end of file diff --git a/input/fsh/instances/capabilitystatement.fsh b/input/fsh/instances/capabilitystatement.fsh new file mode 100644 index 0000000..9c07423 --- /dev/null +++ b/input/fsh/instances/capabilitystatement.fsh @@ -0,0 +1,216 @@ +Instance: capabilitystatement +InstanceOf: CapabilityStatement +Usage: #example +* name = "RestServer" +* status = #active +* date = "2024-10-21T09:29:32.761+00:00" +* publisher = "Not provided" +* kind = #instance +* software.name = "Fictitious FHIR Server" +* implementation.description = "HAPI FHIR" +* implementation.url = "http://localhost:8080/fhir" +* fhirVersion = #4.0.1 +* format[0] = #application/fhir+xml +* format[+] = #xml +* format[+] = #application/fhir+json +* format[+] = #json +* format[+] = #application/x-turtle +* format[+] = #ttl +* rest.mode = #server +* rest.security.service[+] = BeCSPseudonymizationVersion#urn:be:fgov:ehealth:pseudo:v1 +* rest.security.service[+] = BeCSPseudonymizationVersion#urn:be:fgov:ehealth:pseudo:v2 +* rest.security.service[+] = BeCSPseudonymizationVersion#urn:be:fgov:pseudo-encrypted:v1 +* rest.resource[0].type = #CodeSystem +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/CodeSystem" +* rest.resource[=].interaction[0].code = #read +* rest.resource[=].interaction[+].code = #search-type +* rest.resource[=].interaction[+].code = #delete +* rest.resource[=].searchInclude = "*" +* rest.resource[=].searchParam[0].name = "code" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "A code defined in the code system" +* rest.resource[=].searchParam[+].name = "context" +* rest.resource[=].searchParam[=].type = #token +* rest.resource[=].searchParam[=].documentation = "A use context assigned to the code system" +* rest.resource[=].searchParam[+].name = "context-quantity" +* rest.resource[=].searchParam[=].type = #quantity +* rest.resource[=].searchParam[=].documentation = "A quantity- or range-valued use context assigned to the code system" +* rest.resource[=].searchParam[+].name = "context-type" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "A type of use context assigned to the code system" +* rest.resource[=].searchParam[+].name = "date" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The code system publication date" +* rest.resource[=].searchParam[+].name = "description" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The description of the code system" +* rest.resource[=].searchParam[+].name = "id" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[+].name = "identifier" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "External identifier for the code system" +* rest.resource[=].searchParam[+].name = "jurisdiction" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Intended jurisdiction for the code system" +* rest.resource[=].searchParam[+].name = "name" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Computationally friendly name of the code system" +* rest.resource[=].searchParam[+].name = "publisher" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Name of the publisher of the code system" +* rest.resource[=].searchParam[+].name = "reference" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[+].name = "status" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The current status of the code system" +* rest.resource[=].searchParam[+].name = "title" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The human-friendly name of the code system" +* rest.resource[=].searchParam[+].name = "url" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The uri that identifies the code system" +* rest.resource[=].searchParam[+].name = "version" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The business version of the code system" +* rest.resource[=].operation[0].name = "validate-code" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/CodeSystemValueSet-it-validate-code" +* rest.resource[=].operation[+].name = "upload-external-code-system" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/CodeSystem-t-upload-external-code-system" +* rest.resource[=].operation[+].name = "subsumes" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/CodeSystem-it-subsumes" +* rest.resource[=].operation[+].name = "lookup" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/CodeSystem-it-lookup" +* rest.resource[+].type = #ConceptMap +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/ConceptMap" +* rest.resource[=].interaction[0].code = #read +* rest.resource[=].interaction[+].code = #search-type +* rest.resource[=].searchInclude = "*" +* rest.resource[=].searchParam.name = "url" +* rest.resource[=].searchParam.type = #string +* rest.resource[=].searchParam.documentation = "The uri that identifies the concept map" +* rest.resource[=].operation.name = "translate" +* rest.resource[=].operation.definition = "http://localhost:8080/fhir/OperationDefinition/ConceptMap-t-translate" +* rest.resource[+].type = #Medication +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/Medication" +* rest.resource[=].interaction.code = #read +* rest.resource[=].searchInclude = "*" +* rest.resource[+].type = #OperationDefinition +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/OperationDefinition" +* rest.resource[=].interaction.code = #read +* rest.resource[=].searchInclude = "*" +* rest.resource[+].type = #StructureDefinition +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/StructureDefinition" +* rest.resource[=].interaction[0].code = #update +* rest.resource[=].interaction[+].code = #read +* rest.resource[=].interaction[+].code = #search-type +* rest.resource[=].interaction[+].code = #delete +* rest.resource[=].interaction[+].code = #create +* rest.resource[=].searchInclude = "*" +* rest.resource[=].searchParam[0].name = "code" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[+].name = "context" +* rest.resource[=].searchParam[=].type = #token +* rest.resource[=].searchParam[=].documentation = "A use context assigned to the structure definition" +* rest.resource[=].searchParam[+].name = "context-quantity" +* rest.resource[=].searchParam[=].type = #quantity +* rest.resource[=].searchParam[=].documentation = "A quantity- or range-valued use context assigned to the structure definition" +* rest.resource[=].searchParam[+].name = "context-type" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "A type of use context assigned to the structure definition" +* rest.resource[=].searchParam[+].name = "date" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The structure definition publication date" +* rest.resource[=].searchParam[+].name = "description" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The description of the structure definition" +* rest.resource[=].searchParam[+].name = "expansion" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[+].name = "identifier" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "External identifier for the structure definition" +* rest.resource[=].searchParam[+].name = "jurisdiction" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Intended jurisdiction for the structure definition" +* rest.resource[=].searchParam[+].name = "name" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Computationally friendly name of the structure definition" +* rest.resource[=].searchParam[+].name = "publisher" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Name of the publisher of the structure definition" +* rest.resource[=].searchParam[+].name = "reference" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[+].name = "status" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The current status of the structure definition" +* rest.resource[=].searchParam[+].name = "title" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The human-friendly name of the structure definition" +* rest.resource[=].searchParam[+].name = "url" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The uri that identifies the structure definition" +* rest.resource[=].searchParam[+].name = "version" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The business version of the structure definition" +* rest.resource[+].type = #ValueSet +* rest.resource[=].profile = "http://hl7.org/fhir/StructureDefinition/ValueSet" +* rest.resource[=].interaction[0].code = #update +* rest.resource[=].interaction[+].code = #read +* rest.resource[=].interaction[+].code = #search-type +* rest.resource[=].interaction[+].code = #delete +* rest.resource[=].interaction[+].code = #create +* rest.resource[=].searchInclude = "*" +* rest.resource[=].searchParam[0].name = "_id" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The ID of the resource" +* rest.resource[=].searchParam[+].name = "code" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "This special parameter searches for codes in the value set. See additional notes on the ValueSet resource" +* rest.resource[=].searchParam[+].name = "context" +* rest.resource[=].searchParam[=].type = #token +* rest.resource[=].searchParam[=].documentation = "A use context assigned to the value set" +* rest.resource[=].searchParam[+].name = "context-quantity" +* rest.resource[=].searchParam[=].type = #quantity +* rest.resource[=].searchParam[=].documentation = "A quantity- or range-valued use context assigned to the value set" +* rest.resource[=].searchParam[+].name = "context-type" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "A type of use context assigned to the value set" +* rest.resource[=].searchParam[+].name = "date" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The value set publication date" +* rest.resource[=].searchParam[+].name = "description" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The description of the value set" +* rest.resource[=].searchParam[+].name = "expansion" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Identifies the value set expansion (business identifier)" +* rest.resource[=].searchParam[+].name = "identifier" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "External identifier for the value set" +* rest.resource[=].searchParam[+].name = "jurisdiction" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Intended jurisdiction for the value set" +* rest.resource[=].searchParam[+].name = "name" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Computationally friendly name of the value set" +* rest.resource[=].searchParam[+].name = "publisher" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "Name of the publisher of the value set" +* rest.resource[=].searchParam[+].name = "reference" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "A code system included or excluded in the value set or an imported value set" +* rest.resource[=].searchParam[+].name = "status" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The current status of the value set" +* rest.resource[=].searchParam[+].name = "title" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The human-friendly name of the value set" +* rest.resource[=].searchParam[+].name = "url" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The uri that identifies the value set" +* rest.resource[=].searchParam[+].name = "version" +* rest.resource[=].searchParam[=].type = #string +* rest.resource[=].searchParam[=].documentation = "The business version of the value set" +* rest.resource[=].operation[0].name = "validate-code" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/CodeSystemValueSet-it-validate-code" +* rest.resource[=].operation[+].name = "expand" +* rest.resource[=].operation[=].definition = "http://localhost:8080/fhir/OperationDefinition/ValueSet-it-expand" \ No newline at end of file diff --git a/input/fsh/instances/contained1.fsh b/input/fsh/instances/contained1.fsh new file mode 100644 index 0000000..44709d2 --- /dev/null +++ b/input/fsh/instances/contained1.fsh @@ -0,0 +1,5 @@ +Instance: contained1 +InstanceOf: Patient +* contained[+] = patient2 +* link.other = Reference( #patient2 ) +* link.type = #replaced-by \ No newline at end of file diff --git a/input/fsh/instances/parameters1.fsh b/input/fsh/instances/parameters1.fsh new file mode 100644 index 0000000..6237961 --- /dev/null +++ b/input/fsh/instances/parameters1.fsh @@ -0,0 +1,17 @@ +Instance: parameters1 +InstanceOf: Parameters +Usage: #example +* meta.extension[+].url = "https://www.ehealth.fgov.be/standards/fhir/infsec/StructureDefinition/be-ext-key-pseudonymization" +* meta.extension[=].id = "fcc557e7-40fa-4fde-b802-12a461cd176f" +* meta.extension[=].extension[+].url = "key" +* meta.extension[=].extension[=].valueString = "urn:be:fgov:pseudo:v2:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iD:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iDgeip7iSHAW7TNrDBa0XMGeS6G3s/HWLSQ4eirpcox28GghzbtaiUzg=.UPOBi75XsreuYfQwyVvIaHgpzrrdS6joS8JaPlkMPxeU8FmFHRtteJp/FAq91pEllcbH4V4PRSC+QEm0C9thkO4=" +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[marker].valueBoolean = true +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[format].valueCode = #direct +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[version].valuePositiveInt = 2 +* parameter[0].name = "name" +* parameter[=].valueString = "urn:be:fgov:pseudo-encrypted:v1:fcc557e7-40fa-4fde-b802-12a461cd176f:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iD:geip7iSHAW7TNrDBa0XMGeS6G3s/HWLSQ4eirpcox28GghzbtaiUzg=.UPOBi75XsreuYfQwyVvIaHgpzrrdS6joS8JaPlkMPxeU8FmFHRtteJp/FAq91pEllcbH4V4PRSC+QEm0C9thkO4=" +* parameter[=].valueString.extension[BeExtPseudonymization].extension[marker].valueBoolean = true +* parameter[=].valueString.extension[BeExtPseudonymization].extension[format].valueCode = #encrypted +* parameter[=].valueString.extension[BeExtPseudonymization].extension[version].valuePositiveInt = 1 +* parameter[+].name = "version" +* parameter[=].valueString = "1.0.0" \ No newline at end of file diff --git a/input/fsh/instances/patient2.fsh b/input/fsh/instances/patient2.fsh new file mode 100644 index 0000000..dae3d57 --- /dev/null +++ b/input/fsh/instances/patient2.fsh @@ -0,0 +1,98 @@ +Instance: patient2 +InstanceOf: Patient +Usage: #example +* meta.extension[+].url = "https://www.ehealth.fgov.be/standards/fhir/infsec/StructureDefinition/be-ext-key-pseudonymization" +* meta.extension[=].id = "fcc557e7-40fa-4fde-b802-12a461cd176f" +* meta.extension[=].extension[+].url = "key" +* meta.extension[=].extension[=].valueString = "urn:be:fgov:pseudo:v2:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iD:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iDgeip7iSHAW7TNrDBa0XMGeS6G3s/HWLSQ4eirpcox28GghzbtaiUzg=.UPOBi75XsreuYfQwyVvIaHgpzrrdS6joS8JaPlkMPxeU8FmFHRtteJp/FAq91pEllcbH4V4PRSC+QEm0C9thkO4=" +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[marker].valueBoolean = true +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[format].valueCode = #direct +* meta.extension[=].extension[=].valueString.extension[BeExtPseudonymization].extension[version].valuePositiveInt = 2 +* extension[+].url = "http://hl7.org/fhir/StructureDefinition/patient-birthPlace" +* extension[=].valueAddress.extension.url = "http://hl7.org/fhir/StructureDefinition/language" +* extension[=].valueAddress.extension.valueCode = #nl +* extension[=].valueAddress.city = "Namen" +* extension[=].valueAddress.country = "BE" +* extension[+].url = "http://hl7.org/fhir/StructureDefinition/patient-nationality" +* extension[=].extension.url = "code" +* extension[=].extension.valueCodeableConcept = $cd-fed-country#BE "Belgium" +* extension[+].url = "https://www.ehealth.fgov.be/standards/fhir/infsec/StructureDefinition/be-ext-intended-profile" +* extension[=].valueCanonical = "https://www.ehealth.fgov.be/standards/fhir/core/StructureDefinition/be-patient|2.1.0" +* identifier[0].use = #official +* identifier[=].type = $v2-0203#SB "Social Beneficiary Identifier" +* identifier[=].system = "https://www.ehealth.fgov.be/standards/fhir/core/NamingSystem/ssin" +* identifier[=].value = "urn:be:fgov:pseudo:v1:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iDgeip7iSHAW7TNrDBa0XMGeS6G3s/HWLSQ4eirpcox28GghzbtaiUzg=.UPOBi75XsreuYfQwyVvIaHgpzrrdS6joS8JaPlkMPxeU8FmFHRtteJp/FAq91pEllcbH4V4PRSC+QEm0C9thkO4=" +* identifier[=].value.extension[BeExtPseudonymization].extension[marker].valueBoolean = true +* identifier[+].use = #usual +* identifier[=].type = $v2-0203#MR "Medical record number" +* identifier[=].system = "https://www.goodhealthhospital.be/standards/fhir/NamingSystem/patientrecord" +* identifier[=].value = "45XXP0PA-4" +* active = true +* name.use = #official +* name.family = "La Paradisio" +* name.given[0] = "Josephine" +* name.given[+] = "Nessa" +* telecom[0].system = #email +* telecom[=].value = "nessa.laparadisio@belgium.be" +* telecom[+].system = #phone +* telecom[=].value = "+322476792979" +* telecom[=].use = #mobile +* telecom[+].system = #phone +* telecom[=].value = "+3226718655" +* telecom[=].use = #home +* telecom[+].system = #phone +* telecom[=].value = "+322476799" +* telecom[=].use = #work +* gender = #female +* birthDate = "1979-12-11" +* birthDate.extension.url = "http://hl7.org/fhir/StructureDefinition/patient-birthTime" +* birthDate.extension.valueDateTime = "1979-12-11T13:28:17-05:00" +* address[0].extension.url = "http://hl7.org/fhir/StructureDefinition/language" +* address[=].extension.valueCode = #nl +* address[=].use = #home +* address[=].type = #both +* address[=].text = "Sloordelle 42, 1160 Oudergem" +* address[=].line = "Sloordelle 42" +* address[=].line.extension[0].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-streetName" +* address[=].line.extension[=].valueString = "Sloordelle" +* address[=].line.extension[+].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-houseNumber" +* address[=].line.extension[=].valueString = "42" +* address[=].city = "Oudergem" +* address[=].postalCode = "1160" +* address[=].country = "BE" +* address[+].extension.url = "http://hl7.org/fhir/StructureDefinition/language" +* address[=].extension.valueCode = #fr +* address[=].use = #home +* address[=].type = #both +* address[=].text = "42, Allee des Colzas, 1160 Auderghem" +* address[=].line = "42, Allee des Colzas" +* address[=].line.extension[0].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-streetName" +* address[=].line.extension[=].valueString = "Allee des Colzas" +* address[=].line.extension[+].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-houseNumber" +* address[=].line.extension[=].valueString = "42" +* address[=].city = "Auderghem" +* address[=].postalCode = "1160" +* address[=].country = "BE" +* address[+].use = #work +* address[=].type = #both +* address[=].text = "urn:be:fgov:pseudo-encrypted:v1:fcc557e7-40fa-4fde-b802-12a461cd176f:OZADJVppdeQzwgvAUjQNaLvuf94ulY6iDgeip7iSHAW7TNrDBa0XMGeS6G3s/HWLSQ4eirpcox28GghzbtaiUzg=.UPOBi75XsreuYfQwyVvIaHgpzrrdS6joS8JaPlkMPxeU8FmFHRtteJp/FAq91pEllcbH4V4PRSC+QEm0C9thkO4=" +* address[=].text.extension[BeExtPseudonymization].extension[marker].valueBoolean = true +* address[=].text.extension[BeExtPseudonymization].extension[format].valueCode = #encrypted +* address[=].text.extension[BeExtPseudonymization].extension[version].valuePositiveInt = 1 +* address[=].line = "377, Avenue Prince d'Orange" +* address[=].line.extension[0].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-streetName" +* address[=].line.extension[=].valueString = "Avenue Prince d'Orange" +* address[=].line.extension[+].url = "http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-houseNumber" +* address[=].line.extension[=].valueString = "377" +* address[=].city = "Braine-lʼAlleud" +* address[=].postalCode = "1420" +* address[=].country = "BE" +* maritalStatus.coding[0] = $v3-MaritalStatus#D "Divorced" +* maritalStatus.coding[+] = $cd-civilstate#41 "Divorced since 1/10/1994" +* contact.relationship.coding[0] = $v2-0131#N "Next-of-Kin" +* contact.relationship.coding[+] = $cd-contact-person#mother +* contact.name.family = "Vogels" +* contact.name.given = "Leia" +* contact.telecom.system = #phone +* contact.telecom.value = "+31201234567" +* contact.telecom.use = #mobile diff --git a/input/fsh/valueset/BeVSPseudonymizationType.fsh b/input/fsh/valueset/BeVSPseudonymizationType.fsh new file mode 100644 index 0000000..7a4fb96 --- /dev/null +++ b/input/fsh/valueset/BeVSPseudonymizationType.fsh @@ -0,0 +1,6 @@ +ValueSet: BeVSPseudonymizationType +Id: be-vs-pseudonymization-type +Title: "Types of pseudonymization" +Description: "The technique used for pseudonymization" +* ^experimental = false +* include codes from system BeCSPseudonymizationType \ No newline at end of file diff --git a/input/pagecontent/guidance.md b/input/pagecontent/guidance.md index d4e69e3..c47747a 100644 --- a/input/pagecontent/guidance.md +++ b/input/pagecontent/guidance.md @@ -1,4 +1,4 @@ -### The use of pseudonymisation in FHIR for short texts +### The use of pseudonymisation in FHIR Pseudonymisation is the activity of replacing meaningful data with a synonym that hides the original data, but when needed this synonym can be replaced by the original data. The aim is to hide data from readers that do not need it, due to legal (GDPR) or other reasons, but still allow the links between different data elements for those who need it. Additional encryption techniques may be used to restrict the access to the information to those who need it. @@ -16,15 +16,24 @@ This solution only applies for short texts, i.e. text that fall within the lengt * Pseudonymisation should interfere as little as possible with the standard FHIR APIs for searching information, without endangering the essence of pseudonymisation. * Pseudonymisation should be as coherent as possible, so that the developer can (re)use the same techniques whenever he encounters pseudonymisation. -#### The solution: +#### The solution for short texts, less than 32 bytes: * Within the FHIR document, a pseudonymised value will be marked by an extension. This extension is applicable to any text field (string). -* The original value of the string will be replaced by the pseudonym. This pseudonym is a JWE encoded string, containing the transitinfo, x and y value. -* At this moment, the extension does not contain any other fields, but these might be added in the solution for long texts. -* Searching on a pseudonymised field will be done using the normal search parameter. The fact that this search parameter contains a pseudonym will be indicated by a urn-style prefix. The pseudonym will be represented by the same JWE encoded string as described in item 2. +* The original value of the string will be replaced by the pseudonym. This pseudonym can take following forms: + - {base64 json string, containing x, y, and transitInfo} + - urn:be:fgov:pseudo:v1:{base64 json string, containing x, y, and transitInfo} + - urn:be:fgov:pseudo:v2:{SEC1}:{transitInfo} - this type of encoding prevents the double Base64 encoding in v1. See the annexes of the Blinded Pseudonymization Cookbook for more info. + +* The extension will have following fields: + - marker: true (mandatory), indicates that this field is a pseudonym. + - format: direct|encrypted (optional) default is direct + + direct indicates that the field is an immediate result of the pseudonymization service + + encrypted see below for texts larger than 32 bytes. + - version: no version defaults to 1. If the version is different from 1, it is mandatory. +* Searching on a pseudonymised field will be done using the normal search parameter. The fact that this search parameter contains a pseudonym will be indicated by a urn-style prefix. The pseudonym will be represented by the same way as described in item 2. "urn:be:fgov:pseudo-encrypted:" fields cannot be used in a search, if the search parameters are not available as a resource. * Depending on the need of the implementing server, and the length of the query string, the implementing server will be able to use both GET and POST to execute the search, according to the FHIR specifications. The use of POST might be necessary in case of the combination of several pseudonymised search parameters in one query string. -Example of a json containing a pseudonym, before the application of JWE: +Example of a json containing a pseudonym, before the application of the base64 encoding: ``` { @@ -41,12 +50,59 @@ If you want to use the pseudonym for searching, you take the resulting value, an ``` urn:be:fgov:ehealth:pseudo:v1: ``` +or + +``` +urn:be:fgov:ehealth:pseudo:v2: +``` +depending on the type of pseudonymization you are using. Beware that you can only use the long text solution (urn:be:fgov:pseudo-encrypted:) in searches if the body of the query request contains a FHIR resource that can handle the pseudonymized key. + + The resulting value will look like this: ``` urn:be:fgov:ehealth:pseudo:v1:eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiMjAyMi0xMi... ``` +#### The solution for long texts, larger than 32 bytes: + +* Within the FHIR document, a pseudonymised value will be marked by an extension. This extension is applicable to any text field (string). +* The original value of the string will be replaced by the pseudonym. This pseudonym can take following forms: + - urn:be:fgov:pseudo-encrypted:v1:{KID}:{JWE} +* The extension will have following fields: + - marker: true (mandatory), indicates that this field is a pseudonym. + - format: direct or encrypted (optional), default is direct + + direct see above for texts less than 32 bytes + + encrypted indicates that the field is encrypted with a key you can find in the .meta section of the resource, in the extension with url "https://www.ehealth.fgov.be/standards/fhir/infsec/StructureDefinition/be-ext-key-pseudonymization". + - version: no version defaults to 1. If the version is different from 1, it is mandatory. +* In each resource of the document, you will add an extension with url "https://www.ehealth.fgov.be/standards/fhir/infsec/StructureDefinition/be-ext-key-pseudonymization" + - This extension contains one extension containing a string value, with url "key". This is the encryption key that can be used to blockcipher the long text fields. The key is 32 bytes or less, so direct pseudonymization applies. + - This .valueString field is pseudonymized in the direct way, using a pseudonymize extension for short texts. + +#### Content negotiation for pseudonymisation in FHIR + +As clients and servers may have different capabilities with regard to the support of pseudonymisation representations, both clients and servers can express their capabilities and conduct negotiations regarding the pseudonymisation representations to be used. + +The client will be able to express his preferences by using the Accept-Be-Pseudo HTTP header. This header SHALL contain a comma separated list of the prefix values as described above. + +``` +Accept-Be-Pseudo: urn:be:fgov:ehealth:pseudo:v1, urn:be:fgov:ehealth:pseudo:v2, urn:be:fgov:pseudo-encrypted:v1 +``` +This header has been designed according to the guidelines in [RFC 6648](https://www.rfc-editor.org/rfc/rfc6648) + +If the header is not present in the request, the server will default to the lowest version supported as indicated in the capabilities statement (see further). + +The server will indicate the version of pseudonymisation used using the Content-Be-Pseudo header. + +``` +Content-Be-Pseudo: urn:be:fgov:ehealth:pseudo:v2, urn:be:fgov:pseudo-encrypted:v1 +``` +This header is only an suggestion, and the indications in the FHIR resource itself always take precedence over the value in the header. If the header is missing, and there is no conclusive evidence (taking into account the described defaults), the value defaults to the lowest supported version in the capabilities statement. + +The server has the opportunity to indicate its preferred use of pseudonymisation in the capabilities statement, as one or more ``rest.security.service`` codeable concepts from CodeSystem [be-cs-pseudonymization-version](./CodeSystem-be-cs-pseudonymization-version.html). The client is supposed, as in [good fhir practice](https://www.hl7.org/fhir/R4/http.html#capabilities), to request the capabilities statement of the server, to check the type of pseudonymisation that is expected. If no explicit pseudonymisation representation is present, the client can try to use his own preference, but must be prepared to accept a refusal in the form of a 422 Error Code. In general, sending pseudonymised content to a server that is not capable of handling it, will provoke undefined behaviour with regard to the pseudonymisation definition. + +During the pseudonymisation content negotiation, the client and server should choose the highest version that is supported by both client and server. Versions will be ordered by using the ordering rules of positive integers. + ### Ensuring computable integrity clarifying the use of meta.profile and semantic integrity by using the BeExtIntendedProfile extension #### Prerequisites: diff --git a/sushi-config.yaml b/sushi-config.yaml index 3120e1f..9d727af 100644 --- a/sushi-config.yaml +++ b/sushi-config.yaml @@ -163,7 +163,10 @@ menu: Guidance: guidance.html Artifacts: Extensions: artifacts.html#structures-extension-definitions + ValueSets: artifacts.html#terminology-value-sets + CodeSystems: artifacts.html#terminology-code-systems Examples: artifacts.html#example-example-instances + Downloads: downloads.html Changes: changes.html History: https://www.ehealth.fgov.be/standards/fhir/infsec/history.html @@ -196,5 +199,24 @@ parameters: propagate-status: true display-warnings: true +resources: + CapabilityStatement/capabilitystatement: + name: "Capability Statement with Pseudonymisation" + description: This capability statement contains the entries to indicate that you support pseudo v1 and v2 and pseudo-encryption v1. + Bundle/bundle1: + name: "Bundle with pseudonymisation examples" + description: Each resource in the bundle has its own pseudo-encryption key + Parameters/parameters1: + name: "Parameters object with long text encryption and key" + description: Long text encryption for resources which are not domain resources + Patient/patient1: + name: "Patient with v1 Pseudonymisation" + description: Example of v1 Pseudonymisation without prefix + Patient/patient2: + name: "Patient with v2 Pseudonymisation and long text pseudonymisation" + description: Example of a domain resource containing both direct and encrypted pseudonymisation + Patient/contained1: + name: "Contained resource with long text pseudonymisation" + description: Example of a contained resource, which always has its own pseudonymisation key if long text pseudonymisation is applied.