Make AGOV IdP with a security extension (support Artifact Binding, Encryption)

This commit is contained in:
haburger 2025-09-04 05:42:06 +00:00
parent 4b630d3dbc
commit 343e92f628
16 changed files with 204 additions and 34 deletions

View File

@ -1,13 +1,13 @@
schemaVersion: "1.0" schemaVersion: "1.0"
bundles: bundles:
- "nevisadmin-plugin-base-generation:8.2411.2.4" - "nevisadmin-plugin-base-generation:8.2505.6.1"
- "nevisadmin-plugin-nevisproxy:8.2411.2.4" - "nevisadmin-plugin-nevisproxy:8.2505.6.1"
- "nevisadmin-plugin-nevisauth:8.2411.2.4" - "nevisadmin-plugin-nevisauth:8.2505.6.1"
- "nevisadmin-plugin-nevisidm:8.2411.2.4" - "nevisadmin-plugin-nevisidm:8.2505.6.1"
- "nevisadmin-plugin-mobile-auth:8.2411.2.4" - "nevisadmin-plugin-mobile-auth:8.2505.6.1"
- "nevisadmin-plugin-fido2:8.2411.2.4" - "nevisadmin-plugin-fido2:8.2505.6.1"
- "nevisadmin-plugin-nevisadapt:8.2411.2.4" - "nevisadmin-plugin-nevisadapt:8.2505.6.1"
- "nevisadmin-plugin-nevisdetect:8.2411.2.4" - "nevisadmin-plugin-nevisdetect:8.2505.6.1"
- "nevisadmin-plugin-oauth:8.2411.2.4" - "nevisadmin-plugin-oauth:8.2505.6.1"
- "nevisadmin-plugin-authcloud:8.2411.2.4" - "nevisadmin-plugin-authcloud:8.2505.6.1"
- "nevisadmin-plugin-nevisdp:8.2411.2.4" - "nevisadmin-plugin-nevisdp:8.2505.6.1"

View File

@ -0,0 +1,9 @@
<WebService class="ch.nevis.esauth.auth.adapter.saml.ArtifactResolutionService" name="IDP_AGOV_SEC_ARS" uri="/nevisauth/services/ars/sec" SSODomain="Auth_Realm_Main_IDP">
<property name="issuer" value="${var.idp_agov_sec-saml-issuer}"/>
<property name="out.keystoreref" value="Store_IDP_AGOV"/>
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
<property name="in.keystoreref" value="Store_IDP_AGOV"/>
<property name="in.verify" value="ArtifactResolve"/>
<property name="in.prospectVerification" value=""/>
</WebService>

View File

@ -3,6 +3,7 @@
<ResultCond name="main" next="${state.exit.1}"/> <ResultCond name="main" next="${state.exit.1}"/>
<ResultCond name="epd" next="${state.exit.2}"/> <ResultCond name="epd" next="${state.exit.2}"/>
<ResultCond name="epd_artifact" next="${state.exit.3}"/> <ResultCond name="epd_artifact" next="${state.exit.3}"/>
<ResultCond name="main_secure" next="${state.exit.4}"/>
<Response value="AUTH_CONTINUE"> <Response value="AUTH_CONTINUE">
<Gui name="saml_dispatcher" label="title.saml.failed"> <Gui name="saml_dispatcher" label="title.saml.failed">

View File

@ -30,47 +30,52 @@ def redirect(String url) {
* @param xml - as parsed by Groovy XmlSlurper * @param xml - as parsed by Groovy XmlSlurper
* @return text content of Issuer element converted or null * @return text content of Issuer element converted or null
*/ */
String getIssuer(GPathResult xml) { String getNodeText(GPathResult xml, String nodeName) {
return xml.depthFirst().find { GPathResult node -> { return xml.depthFirst().find { GPathResult node -> {
node.name().endsWith(":Issuer") || node.name().equalsIgnoreCase("Issuer") node.name().endsWith(":${nodeName}") || node.name().equalsIgnoreCase(nodeName)
} }
}?.text() }?.text()?.trim()
} }
String getIssuer(String value) { String getNodeText(String samlMessage, String nodeName) {
if (value == null) { if (samlMessage == null) {
return return
} }
String text String text
byte[] decoded byte[] decoded
def parser = new XmlSlurper() def parser = new XmlSlurper()
// if value is raw xml then continue otherwise try to parse the base64 encoding // if samlMessage is raw xml then continue otherwise try to parse the base64 encoding
if (value.startsWith("<")) { if (samlMessage.startsWith("<")) {
text = new String(value) text = new String(samlMessage)
} }
else { else {
decoded = value.decodeBase64() decoded = samlMessage.decodeBase64()
text = new String(decoded) text = new String(decoded)
LOG.info("received SAML request $value")
} }
// after decoded, if redirect binding, we need to parse string to xml // after decoded, if redirect binding, we need to parse string to xml
if (text.startsWith("<")) { if (text.startsWith("<")) {
LOG.debug("assuming POST/SOAP binding")
// plain String (POST/SOAP parameter) // plain String (POST/SOAP parameter)
def xml = parser.parseText(text) def xml = parser.parseText(text)
return getIssuer(xml) return getNodeText(xml, nodeName)
} }
else { else {
LOG.debug("assuming redirect binding")
// should be deflate encoded (query parameter) // should be deflate encoded (query parameter)
def is = new InflaterInputStream(new ByteArrayInputStream(decoded), new Inflater(true)) def is = new InflaterInputStream(new ByteArrayInputStream(decoded), new Inflater(true))
def xml = parser.parse(is) def xml = parser.parse(is)
return getIssuer(xml) return getNodeText(xml, nodeName)
} }
} }
def dispatchIssuer(i2s, String issuer) { String getIssuer(String value) {
return getNodeText(value, 'Issuer')
}
String getRequesterID(String value) {
return getNodeText(value, 'RequesterID')
}
def dispatchIssuer(i2s, String issuer, String requester) {
def result = i2s.get(issuer) def result = i2s.get(issuer)
if (result == null) { if (result == null) {
LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.") LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
@ -80,22 +85,30 @@ def dispatchIssuer(i2s, String issuer) {
if(parameters.get('epdMode') == 'artifact' && result == 'epd'){ if(parameters.get('epdMode') == 'artifact' && result == 'epd'){
LOG.debug("EPD: Artifact mode") LOG.debug("EPD: Artifact mode")
result = result + "_artifact" result = result + "_artifact"
}else{ } else if (result == 'main') {
LOG.debug("EPD: POST mode") if ('https://op.agov-w.azure.adnovum.net/SAML2/ACS/' == requester) {
} result = result + "_secure"
}
}
response.setResult(result) response.setResult(result)
session.put("saml.inbound.issuer", issuer) session.put("saml.inbound.issuer", issuer)
session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
} }
def dispatchIssuer(i2s, String issuer) {
dispatchIssuer(i2s, issuer, 'unknown')
}
def dispatchMessage(i2s, String message) { def dispatchMessage(i2s, String message) {
def issuer = getIssuer(message) def issuer = getIssuer(message)
def requester = getRequesterID(message)
if (issuer == null) { if (issuer == null) {
LOG.info("No issuer found in incoming SAML message. Giving up.") LOG.info("No issuer found in incoming SAML message. Giving up.")
} }
session.put("saml.inbound.issuer", issuer) session.put("saml.inbound.issuer", issuer)
dispatchIssuer(i2s, issuer) dispatchIssuer(i2s, issuer, requester)
} }
if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) { if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {

View File

@ -35,7 +35,8 @@
<property name="logoutMode" value="ConcurrentLogout-Redirect"/> <property name="logoutMode" value="ConcurrentLogout-Redirect"/>
<property name="logoutTrigger" value="#{request['currentResource'].contains('logout') || inargs.containsKey('logout') || inargs.containsKey('SAMLLogout')}"/> <property name="logoutTrigger" value="#{request['currentResource'].contains('logout') || inargs.containsKey('logout') || inargs.containsKey('SAMLLogout')}"/>
<property name="in.verify" value="${param.in.verify:Assertion, AuthnRequest, ArtifactResolve, ArtifactResponse}" />
<property name="in.prospectVerification" value="${param.in.prospectVerification:}" />
<property name="out.binding" value="http-post"/> <property name="out.binding" value="http-post"/>
<property name="out.post.relayStateEncoding" value="HTML"/> <property name="out.post.relayStateEncoding" value="HTML"/>
<property name="out.sign" value="Response Assertion"/> <property name="out.sign" value="Response Assertion"/>

View File

@ -7,6 +7,8 @@ pattern:
notes: "modified script taken from what Nevis generated when using a SAM IDP Pattern" notes: "modified script taken from what Nevis generated when using a SAM IDP Pattern"
properties: properties:
authStatesFile: "res://92cb6d5256008a32f12ceb93#authStatesFile" authStatesFile: "res://92cb6d5256008a32f12ceb93#authStatesFile"
parameters: |
in.prospectVerification: ArtifactResolve
onSuccess: onSuccess:
- "pattern://2f81f8b878ef787fc5cc284a" - "pattern://2f81f8b878ef787fc5cc284a"
onFailure: onFailure:

View File

@ -0,0 +1,23 @@
schemaVersion: "1.0"
pattern:
id: "bb9e7806a04578e0ad468829"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC"
label: "IDP CUSTOM"
notes: "modified script taken from what Nevis generated when using a SAM IDP Pattern"
properties:
authStatesFile: "res://bb9e7806a04578e0ad468829#authStatesFile"
parameters: "out.binding: http-post\nout.post.relayStateEncoding: HTML\nout.encrypt:\
\ Assertion\nout.encrypt.keystoreref: EncryptionKeys\nout.encryption_key_from_expression:\
\ \nout.encrypt.keyobjectref: DefaultEncryptionKey\n"
onSuccess:
- "pattern://2f81f8b878ef787fc5cc284a"
onFailure:
- "pattern://5f7e44f4fb2e3f710e4a3e91"
nextSteps:
- "pattern://db4eead0bb25b03205afd79f"
- "pattern://06515d4815de4afde6f8116a"
- "pattern://3f719a1e5c1447ee46c69cb2"
- "pattern://68665057549fd887ea09fb86"
keyObjects:
- "pattern://b09a3092a59797b317c06ae4"

View File

@ -13,4 +13,5 @@ pattern:
- "pattern://92cb6d5256008a32f12ceb93" - "pattern://92cb6d5256008a32f12ceb93"
- "pattern://1d81bd987455a8e1ee044ccf" - "pattern://1d81bd987455a8e1ee044ccf"
- "pattern://5a75ffc73b91b88cfab6168e" - "pattern://5a75ffc73b91b88cfab6168e"
- "pattern://bb9e7806a04578e0ad468829"
resources: "res://73efd00d67082ff1eb927922#resources" resources: "res://73efd00d67082ff1eb927922#resources"

View File

@ -14,6 +14,7 @@ pattern:
- "pattern://4bad2fe3ccc54716cc87138f" - "pattern://4bad2fe3ccc54716cc87138f"
logrend: logrend:
- "pattern://d19fe773e8f9ae00504352da" - "pattern://d19fe773e8f9ae00504352da"
defaultTemplate: "proxy"
initialSessionTimeout: "30s" initialSessionTimeout: "30s"
sessionTimeout: "30s" sessionTimeout: "30s"
maxSessionLifetime: "60m" maxSessionLifetime: "60m"

View File

@ -59,7 +59,7 @@ pattern:
<filter-mapping> <filter-mapping>
<filter-name>DefaultErrorFilter</filter-name> <filter-name>DefaultErrorFilter</filter-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
<exclude-url-regex>^/oidc4vp/.*$|^/resource/utility/.*$</exclude-url-regex> <exclude-url-regex>^/auth/fidouaf$|^/auth/fidouaf/authenticationresponse/.*$|^/nevisfido/devices/credentials/.*$|^/nevisfido/devices/oobOperations/.*$|^/nevisfido/status$|^/nevisfido/token/dispatch/registration$|^/nevisfido/token/dispatch/targets/.*$|^/nevisfido/token/redeem/authentication$|^/nevisfido/token/redeem/registration$|^/nevisfido/uaf/1.1/authentication$|^/nevisfido/uaf/1.1/authentication/.*$|^/nevisfido/uaf/1.1/facets$|^/nevisfido/uaf/1.1/registration/.*$|^/nevisfido/uaf/1.1/request/deregistration/.*$|^/resource/utility/.*$|^/oidc4vp/.*$</exclude-url-regex>
</filter-mapping> </filter-mapping>
<filter-mapping> <filter-mapping>
<filter-name>FallbackErrorFilter</filter-name> <filter-name>FallbackErrorFilter</filter-name>

View File

@ -5,7 +5,9 @@ pattern:
name: "FIDO_UAF_Instance" name: "FIDO_UAF_Instance"
deploymentHosts: "fido-uaf" deploymentHosts: "fido-uaf"
label: "UAF" label: "UAF"
notes: "/!\\ client name needs to be the name and not the ID\n\n" notes: |+
/!\ client name needs to be the name and not the ID
link: link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN" sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "ca92034f995b39fde562293c" sourcePatternId: "ca92034f995b39fde562293c"
@ -22,7 +24,8 @@ pattern:
database: database:
- "pattern://9385d1b33aefe975fb1c5914" - "pattern://9385d1b33aefe975fb1c5914"
facets: "var://fido_uaf_instance-facets" facets: "var://fido_uaf_instance-facets"
basicFullAttestation: "strict" basicFullAttestation: "default"
fullBasicAttestationAndroidPermissiveMode: "enabled"
firebaseServiceAccount: "var://fido_uaf_instance-firebase-configuration" firebaseServiceAccount: "var://fido_uaf_instance-firebase-configuration"
firebaseProxyAddress: "var://fido_uaf_instance-firebase-proxy-url" firebaseProxyAddress: "var://fido_uaf_instance-firebase-proxy-url"
link: "Custom URI" link: "Custom URI"
@ -35,6 +38,7 @@ pattern:
registrationTokenTimeout: "var://fido-uaf-generic-token-timeout" registrationTokenTimeout: "var://fido-uaf-generic-token-timeout"
authenticationTokenTimeout: "var://fido-uaf-generic-token-timeout" authenticationTokenTimeout: "var://fido-uaf-generic-token-timeout"
deviceServiceTimeout: "var://fido-uaf-device-service-timeout" deviceServiceTimeout: "var://fido-uaf-device-service-timeout"
pushMessageTimeout: "var://fido-uaf-generic-token-timeout"
addons: addons:
- "pattern://6c7076da1508f186394a3bd2" - "pattern://6c7076da1508f186394a3bd2"
- "pattern://90af8358cc587f5c5aa79fec" - "pattern://90af8358cc587f5c5aa79fec"

View File

@ -0,0 +1,9 @@
schemaVersion: "1.0"
pattern:
id: "14efdcb489f3f295fcbdf811"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.GenericAuthWebService"
name: "IDP_AGOV_SEC_ARS"
properties:
auth:
- "pattern://7022472ae407577ae604bbb8"
configFile: "res://14efdcb489f3f295fcbdf811#configFile"

View File

@ -0,0 +1,10 @@
schemaVersion: "1.0"
pattern:
id: "b09a3092a59797b317c06ae4"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.KeyObject"
name: "IDP_EncryptionKeys"
properties:
keyObjectId: "DefaultEncryptionKey"
keyStoreName: "EncryptionKeys"
trustStore:
- "pattern://5acc13246699272f5afefe0a"

View File

@ -0,0 +1,7 @@
schemaVersion: "1.0"
pattern:
id: "5acc13246699272f5afefe0a"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.PemTrustStoreProvider"
name: "IDP_PEM_ATB_ENC"
properties:
truststoreFile: "var://idp_pem_atb_enc_certificate"

View File

@ -0,0 +1,45 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="true">
<!-- Auth_Realm_Main_IDP_Concurrent_Logout -->
<ResultCond name="IDP-initiated-ConcurrentLogout" next="${state.exit.1}"/>
<ResultCond name="SP-initiated-ConcurrentLogout" next="${state.exit.1}"/>
<!-- Auth_Realm_Main_IDP_Prepare_Done -->
<ResultCond name="IDP-initiated-SingleLogout" next="${state.done}"/>
<ResultCond name="SP-initiated-SingleLogout" next="${state.done}"/>
<ResultCond name="ok" next="${state.done}"/>
<!-- Auth_Realm_Main_IDP_Logout_Done -->
<ResultCond name="LogoutCompleted" next="${state.exit.2}"/>
<!-- Auth_Realm_Main_IDP_Logout_Fail -->
<ResultCond name="LogoutFailed" next="${state.exit.3}"/>
<!-- Auth_Realm_Main_IDP_RequestedRoleLevel -->
<ResultCond name="authenticate:IDP-initiated-SSO" next="${state.exit.4}"/>
<ResultCond name="authenticate:SP-initiated-SSO" next="${state.exit.4}"/>
<ResultCond name="invalidAssertionConsumerUrl" next="${state.entry}"/>
<!-- Auth_Realm_Main_IDP_Selector -->
<ResultCond name="stepup:IDP-initiated-SSO" next="${state.failed}"/>
<ResultCond name="stepup:SP-initiated-SSO" next="${state.failed}"/>
<Response value="AUTH_ERROR">
<Gui name="saml_idp" label="title.saml.failed">
<GuiElem name="lasterror" type="error" label="error.saml.failed"/>
</Gui>
</Response>
<!-- same as Custom_AGOV_IDP -->
<propertyRef name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP" />
<property name="out.binding" value="${param.out.binding:http-post}" />
<property name="out.post.relayStateEncoding" value="${param.out.post.relayStateEncoding:HTML}" />
<property name="out.encrypt" value="${param.out.encrypt:none}" />
<property name="out.encrypt.keystoreref" value="${param.out.encrypt.keystoreref:DefaultKeyStore}" />
<property name="out.encrypt.keyobjectref" value="${param.out.encrypt.keyobjectref:DefaultSigner}" />
<!-- property name="out.encryption_key_from_expression" value="${param.out.encryption_key_from_expression:DefaultSigner}" / -->
</AuthState>

View File

@ -442,6 +442,14 @@ variables:
format: "^[^\\s,]*$" format: "^[^\\s,]*$"
value: "https://idp.agov-d.azure.adnovum.net/SAML2/" value: "https://idp.agov-d.azure.adnovum.net/SAML2/"
requireOverloading: true requireOverloading: true
idp_agov_sec-saml-issuer:
className: "ch.nevis.admin.v4.plugin.base.generation.property.SimpleTextProperty"
parameters:
minRequired: 1
maxAllowed: 1
format: "^[^\\s,]*$"
value: "agov-sec"
requireOverloading: true
idp_pem_atb-trusted-certificates: idp_pem_atb-trusted-certificates:
className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty" className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty"
parameters: parameters:
@ -449,6 +457,13 @@ variables:
secretPreserving: true secretPreserving: true
value: null value: null
requireOverloading: true requireOverloading: true
idp_pem_atb_enc_certificate:
className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty"
parameters:
minRequired: 0
secretPreserving: true
value: null
requireOverloading: true
idp_pem_signer-key-store-content: idp_pem_signer-key-store-content:
className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty" className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty"
parameters: parameters:
@ -506,6 +521,35 @@ variables:
queryInputMode: "OPTIONAL" queryInputMode: "OPTIONAL"
value: "https://trustbroker-idp.agov-d.azure.adnovum.net/adfs/ls" value: "https://trustbroker-idp.agov-d.azure.adnovum.net/adfs/ls"
requireOverloading: true requireOverloading: true
idp_sp_sec_connector-custom-properties:
className: "ch.nevis.admin.v4.plugin.base.generation.property.AuthStateProperty"
parameters:
separators:
- "->"
- "="
switchedSeparators: []
problematicSeparator: "->"
value:
- out.binding: "http-artifact"
requireOverloading: false
idp_sp_sec_connector-encrypted-content:
className: "ch.nevis.admin.v4.plugin.base.generation.property.SelectionProperty"
parameters:
options:
- "Assertion"
- "Attribute"
- "NameID"
value:
- "Assertion"
requireOverloading: false
idp_sp_sec_connector-sp-issuer:
className: "ch.nevis.admin.v4.plugin.base.generation.property.SimpleTextProperty"
parameters:
minRequired: 1
maxAllowed: 1
format: "^[^\\s,]*$"
value: "atb-sec"
requireOverloading: true
internal-idp-auth-signer-trust-additional-trusted-certificates: internal-idp-auth-signer-trust-additional-trusted-certificates:
className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty" className: "ch.nevis.admin.v4.plugin.base.generation.property.AttachmentProperty"
parameters: parameters: