mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-20 05:50:08 -06:00
Allow configure encryption details for SAML clients
Closes #40933 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
@@ -78,6 +78,8 @@ This option applies to REDIRECT bindings where the signature is transferred in q
|
|||||||
+
|
+
|
||||||
This option is used when {project_name} server and adapter provide the IDP and SP. This option is only relevant when *Sign Documents* is set to ON.
|
This option is used when {project_name} server and adapter provide the IDP and SP. This option is only relevant when *Sign Documents* is set to ON.
|
||||||
|
|
||||||
|
*Allow ECP Flow*:: If true, this application is allowed to use SAML ECP profile for authentication.
|
||||||
|
|
||||||
=== Signature and Encryption
|
=== Signature and Encryption
|
||||||
|
|
||||||
*Sign Documents*:: When set to ON, {project_name} signs the document using the realms private key.
|
*Sign Documents*:: When set to ON, {project_name} signs the document using the realms private key.
|
||||||
@@ -98,6 +100,16 @@ do not work if the SAML client runs on Java 17 or higher.
|
|||||||
+
|
+
|
||||||
*Canonicalization Method*:: The canonicalization method for XML signatures.
|
*Canonicalization Method*:: The canonicalization method for XML signatures.
|
||||||
|
|
||||||
|
*Encryption algorithm*:: Encryption algorithm used for the client. Default value is `AES_256_GCM` when not defined.
|
||||||
|
|
||||||
|
*Key transport algorithm*:: Key transport algorithm used for the client to encrypt the secret key used for encryption. Default value is `RSA-OAEP-11` when not defined.
|
||||||
|
|
||||||
|
*Digest method for RSA-OAEP*:: Digest method to use when RSA-OAEP is selected as the key transport algorithm. Only available if *Key transport algorithm* is set to any RSA-OAEP algorithm. Default value is `SHA-256` when not defined.
|
||||||
|
|
||||||
|
*Mask generation function*:: Mask generation function to use when `RSA-OAEP-11` is selected as the key transport algorithm. Only available if *Key transport algorithm* is set to `RSA-OAEP-11` algorithm. Default value is `mgf1sha256` when no defined.
|
||||||
|
|
||||||
|
NOTE: The encryption options are only available if the *Encrypt Assertions* option is enabled in the *Keys* tab. For more information about SAML/XML encryption, see the link:https://www.w3.org/TR/xmlenc-core1/[XML Encryption Syntax and Processing] specification.
|
||||||
|
|
||||||
=== Login settings
|
=== Login settings
|
||||||
|
|
||||||
*Login theme*:: A theme to use for login, OTP, grant registration, and forgotten password pages.
|
*Login theme*:: A theme to use for login, OTP, grant registration, and forgotten password pages.
|
||||||
@@ -123,11 +135,9 @@ There will be also one item on the consent screen about this client itself.
|
|||||||
|
|
||||||
== Keys tab
|
== Keys tab
|
||||||
|
|
||||||
*Encrypt Assertions*:: Encrypts the assertions in SAML documents with the realms private key. The AES algorithm uses a key size of 128 bits.
|
|
||||||
|
|
||||||
*Client Signature Required*:: If *Client Signature Required* is enabled, documents coming from a client are expected to be signed. {project_name} will validate this signature using the client public key or cert set up in the `Keys` tab.
|
*Client Signature Required*:: If *Client Signature Required* is enabled, documents coming from a client are expected to be signed. {project_name} will validate this signature using the client public key or cert set up in the `Keys` tab.
|
||||||
|
|
||||||
*Allow ECP Flow*:: If true, this application is allowed to use SAML ECP profile for authentication.
|
*Encrypt Assertions*:: Encrypts the assertions in SAML documents with the specified client public key. Default algorithms used for encryption are configured with security in mind. If you need a different configuration, the encryption details can be modified in the *Settings* tab, section *Signature and Encryption*. The encryption options are only visible when this *Encrypt Assertions* option is enabled.
|
||||||
|
|
||||||
== Advanced tab
|
== Advanced tab
|
||||||
|
|
||||||
@@ -157,3 +167,6 @@ This tab has many fields for specific situations. Some fields are covered in ot
|
|||||||
|
|
||||||
*Artifact Resolution Service*:: URL of the client SOAP endpoint where to send the `ArtifactResolve` messages to.
|
*Artifact Resolution Service*:: URL of the client SOAP endpoint where to send the `ArtifactResolve` messages to.
|
||||||
|
|
||||||
|
=== Advanced settings
|
||||||
|
|
||||||
|
*Assertion Lifespan*:: Specific client lifespan set in the SAML assertion conditions. After that time the assertion will be invalid. If not specified the realm *Access Token Lifespan* is used. The `SessionNotOnOrAfter` attribute is not modified and continue using the *SSO Session Max* time defined at realm level.
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ The feature is enabled for a realm, if `Update Email` required action is enabled
|
|||||||
The feature slightly changes behaviour from previous versions when updating the profile during the authentication flow (e.g. when running the `UPDATE_PROFILE` required action).
|
The feature slightly changes behaviour from previous versions when updating the profile during the authentication flow (e.g. when running the `UPDATE_PROFILE` required action).
|
||||||
If an existing user does have an email set when updating the profile during the authentication flow, the email attribute will not be available.
|
If an existing user does have an email set when updating the profile during the authentication flow, the email attribute will not be available.
|
||||||
|
|
||||||
|
=== Encryption algorithms for SAML updated
|
||||||
|
|
||||||
|
When a SAML client was enabled to *Encrypt Assertions*, the assertion included in the SAML response was encrypted following the link:https://www.w3.org/TR/xmlenc-core1/[XML Encryption Syntax and Processing] specification. The algorithms used for encryption were fixed and outdated. Since this release, default encryption options are up to date and better suited in terms of security. Besides, the encryption details are also configurable, just in case a specific client needs a different set of algorithms to work properly. New attributes can be defined in the client to specify the exact algorithms used for encryption. The Admin console displays them in the client tab *Settings*, section *Signature and Encryption*, when the *Encrypt Assertions* option is enabled in the *Keys* tab.
|
||||||
|
|
||||||
|
In order to maintain backwards compatibility, {project_name}'s upgrade will modify the existing SAML clients to set the encryption attributes to work as before. This way old clients will receive the same encrypted assertion using the same previous algorithms. If the client supports the new default configuration, removing the attributes is recommended.
|
||||||
|
|
||||||
|
For more information about client configuration, please see link:{adminguide_link}#_client-saml-configuration[Creating a SAML client] chapter in the {adminguide_name}.
|
||||||
|
|
||||||
// ------------------------ Deprecated features ------------------------ //
|
// ------------------------ Deprecated features ------------------------ //
|
||||||
== Deprecated features
|
== Deprecated features
|
||||||
|
|
||||||
|
|||||||
@@ -1592,7 +1592,7 @@ synchronizationSettings=Synchronization settings
|
|||||||
certificateHelp=Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.
|
certificateHelp=Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.
|
||||||
resetPasswordError=Error resetting password\: {{error}}
|
resetPasswordError=Error resetting password\: {{error}}
|
||||||
associatedPermissions=Associated permission
|
associatedPermissions=Associated permission
|
||||||
encryptionKeysConfigExplain=If you enable the "Encryption assertions" below, you must configure the encryption keys by generating or importing keys, and the SAML assertions will be encrypted with the client's public key using AES.
|
encryptionKeysConfigExplain=If you enable the "Encryption assertions" below, you must configure the encryption keys by generating or importing keys, and the SAML assertions will be encrypted with the client's public key. When enabled, the encryption details can be modified in the "Settings" tab, section "Signature and Encryption".
|
||||||
preserveGroupInheritanceHelp=Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups.
|
preserveGroupInheritanceHelp=Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups.
|
||||||
createScopeBasedPermission=Create scope-based permission
|
createScopeBasedPermission=Create scope-based permission
|
||||||
showMore=Show more
|
showMore=Show more
|
||||||
@@ -1917,7 +1917,7 @@ eventTypes.IDENTITY_PROVIDER_LOGIN_ERROR.name=Identity provider login error
|
|||||||
scopePermissions.groups.view-description=Policies that decide if an administrator can view this group
|
scopePermissions.groups.view-description=Policies that decide if an administrator can view this group
|
||||||
tokens=Tokens
|
tokens=Tokens
|
||||||
createFlow=Create flow
|
createFlow=Create flow
|
||||||
encryptAssertionsHelp=Should SAML assertions be encrypted with client's public key using AES?
|
encryptAssertionsHelp=Should SAML assertions be encrypted with client's public key?
|
||||||
oAuthDPoPHelp=This enables support for Demonstrating Proof-of-Possession (DPoP) bound tokens. The access and refresh tokens are bound to the key stored on the user agent. In order to prove the possession of the key, the user agent must send a signed proof alongside the token.
|
oAuthDPoPHelp=This enables support for Demonstrating Proof-of-Possession (DPoP) bound tokens. The access and refresh tokens are bound to the key stored on the user agent. In order to prove the possession of the key, the user agent must send a signed proof alongside the token.
|
||||||
unsavedChangesConfirm=You have unsaved changes. Do you really want to leave the page?
|
unsavedChangesConfirm=You have unsaved changes. Do you really want to leave the page?
|
||||||
disabledOff=Disabled off
|
disabledOff=Disabled off
|
||||||
@@ -3501,6 +3501,14 @@ givenNameClaim=Given name Claim
|
|||||||
givenNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's given name. If not provided, defaults to `given_name`.
|
givenNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's given name. If not provided, defaults to `given_name`.
|
||||||
familyNameClaim=Family name Claim
|
familyNameClaim=Family name Claim
|
||||||
familyNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's family name. If not provided, defaults to `family_name`.
|
familyNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's family name. If not provided, defaults to `family_name`.
|
||||||
|
samlClientEncryptionAlgorithm=Encryption algorithm
|
||||||
|
samlClientEncryptionAlgorithmHelp=Encryption algorithm used for the client. Default AES_256_GCM.
|
||||||
|
samlClientKeyEncryptionAlgorithm=Key transport algorithm
|
||||||
|
samlClientKeyEncryptionAlgorithmHelp=Key transport algorithm used for the client to encrypt the secret key used for encryption. Default value RSA-OAEP-11.
|
||||||
|
samlClientEncryptionDigestMethod=Digest method for RSA-OAEP
|
||||||
|
samlClientEncryptionDigestMethodHelp=Digest method to use when any RSA-OAEP algorithm is selected as the key transport algorithm. Default value SHA-256.
|
||||||
|
samlClientEncryptionMaskGenerationFunction=Mask generation function
|
||||||
|
samlClientEncryptionMaskGenerationFunctionHelp=Mask generation function to use when RSA-OAEP-11 is selected as the key transport algorithm. Default value mgf1sha256.
|
||||||
openIdVerifiableCredentials=OpenID for Verifiable Credentials
|
openIdVerifiableCredentials=OpenID for Verifiable Credentials
|
||||||
openIdVerifiableCredentialsHelp=This section is used to configure settings related to OpenID for Verifiable Credential Issuance (OID4VCI).
|
openIdVerifiableCredentialsHelp=This section is used to configure settings related to OpenID for Verifiable Credential Issuance (OID4VCI).
|
||||||
oid4vciEnabled=Enable OID4VCI
|
oid4vciEnabled=Enable OID4VCI
|
||||||
|
|||||||
167
js/apps/admin-ui/src/clients/add/SamlEncryption.tsx
Normal file
167
js/apps/admin-ui/src/clients/add/SamlEncryption.tsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { SelectControl } from "@keycloak/keycloak-ui-shared";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { convertAttributeNameToForm } from "../../util";
|
||||||
|
import { FormFields } from "../ClientDetails";
|
||||||
|
|
||||||
|
export const SamlEncryption = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { watch, setValue } = useFormContext();
|
||||||
|
const ALGORITHM_RSA_OAEP = "http://www.w3.org/2009/xmlenc11#rsa-oaep";
|
||||||
|
const ALGORITHM_RSA_OAEP_MGF1P =
|
||||||
|
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
|
||||||
|
const keyEncryptionAlgorithm = watch(
|
||||||
|
convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.keyAlgorithm",
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove optional fields if not displayed
|
||||||
|
useEffect(() => {
|
||||||
|
if (keyEncryptionAlgorithm !== ALGORITHM_RSA_OAEP) {
|
||||||
|
setValue(
|
||||||
|
convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.maskGenerationFunction",
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
if (keyEncryptionAlgorithm !== ALGORITHM_RSA_OAEP_MGF1P) {
|
||||||
|
setValue(
|
||||||
|
convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.digestMethod",
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [keyEncryptionAlgorithm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SelectControl
|
||||||
|
name={convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.algorithm",
|
||||||
|
)}
|
||||||
|
label={t("samlClientEncryptionAlgorithm")}
|
||||||
|
labelIcon={t("samlClientEncryptionAlgorithmHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: "",
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ key: "", value: t("choose") },
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#aes256-gcm",
|
||||||
|
value: "AES_256_GCM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#aes192-gcm",
|
||||||
|
value: "AES_192_GCM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#aes128-gcm",
|
||||||
|
value: "AES_128_GCM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
|
||||||
|
value: "AES_256_CBC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
|
||||||
|
value: "AES_192_CBC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
|
||||||
|
value: "AES_128_CBC",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectControl
|
||||||
|
name={convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.keyAlgorithm",
|
||||||
|
)}
|
||||||
|
label={t("samlClientKeyEncryptionAlgorithm")}
|
||||||
|
labelIcon={t("samlClientKeyEncryptionAlgorithmHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: "",
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ key: "", value: t("choose") },
|
||||||
|
{
|
||||||
|
key: ALGORITHM_RSA_OAEP,
|
||||||
|
value: "RSA-OAEP-11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ALGORITHM_RSA_OAEP_MGF1P,
|
||||||
|
value: "RSA-OAEP-MGF1P",
|
||||||
|
},
|
||||||
|
{ key: "http://www.w3.org/2001/04/xmlenc#rsa-1_5", value: "RSA1_5" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{(keyEncryptionAlgorithm === ALGORITHM_RSA_OAEP ||
|
||||||
|
keyEncryptionAlgorithm === ALGORITHM_RSA_OAEP_MGF1P) && (
|
||||||
|
<SelectControl
|
||||||
|
name={convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.digestMethod",
|
||||||
|
)}
|
||||||
|
label={t("samlClientEncryptionDigestMethod")}
|
||||||
|
labelIcon={t("samlClientEncryptionDigestMethodHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: "",
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ key: "", value: t("choose") },
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2001/04/xmlenc#sha512",
|
||||||
|
value: "SHA-512",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2001/04/xmlenc#sha256",
|
||||||
|
value: "SHA-256",
|
||||||
|
},
|
||||||
|
{ key: "http://www.w3.org/2000/09/xmldsig#sha1", value: "SHA-1" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{keyEncryptionAlgorithm === ALGORITHM_RSA_OAEP && (
|
||||||
|
<SelectControl
|
||||||
|
name={convertAttributeNameToForm<FormFields>(
|
||||||
|
"attributes.saml.encryption.maskGenerationFunction",
|
||||||
|
)}
|
||||||
|
label={t("samlClientEncryptionMaskGenerationFunction")}
|
||||||
|
labelIcon={t("samlClientEncryptionMaskGenerationFunctionHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: "",
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ key: "", value: t("choose") },
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#mgf1sha512",
|
||||||
|
value: "mgf1sha512",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#mgf1sha384",
|
||||||
|
value: "mgf1sha384",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#mgf1sha256",
|
||||||
|
value: "mgf1sha256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#mgf1sha224",
|
||||||
|
value: "mgf1sha224",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "http://www.w3.org/2009/xmlenc11#mgf1sha1",
|
||||||
|
value: "mgf1sha1",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { FormAccess } from "../../components/form/FormAccess";
|
|||||||
import { convertAttributeNameToForm } from "../../util";
|
import { convertAttributeNameToForm } from "../../util";
|
||||||
import { FormFields } from "../ClientDetails";
|
import { FormFields } from "../ClientDetails";
|
||||||
import { Toggle } from "./SamlConfig";
|
import { Toggle } from "./SamlConfig";
|
||||||
|
import { SamlEncryption } from "./SamlEncryption";
|
||||||
|
|
||||||
export const SIGNATURE_ALGORITHMS = [
|
export const SIGNATURE_ALGORITHMS = [
|
||||||
"RSA_SHA1",
|
"RSA_SHA1",
|
||||||
@@ -45,6 +46,10 @@ export const SamlSignature = () => {
|
|||||||
"attributes.saml.assertion.signature",
|
"attributes.saml.assertion.signature",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const samlEncryption = watch(
|
||||||
|
convertAttributeNameToForm<FormFields>("attributes.saml.encrypt"),
|
||||||
|
"false",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
@@ -96,6 +101,7 @@ export const SamlSignature = () => {
|
|||||||
value: name,
|
value: name,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
{samlEncryption === "true" && <SamlEncryption />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
|
|||||||
@@ -38,19 +38,35 @@ type SamlKeysProps = {
|
|||||||
save: () => void;
|
save: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type KeyMapping = {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
key: string;
|
||||||
|
relatedKeys: string[];
|
||||||
|
};
|
||||||
|
|
||||||
const KEYS = ["saml.signing", "saml.encryption"] as const;
|
const KEYS = ["saml.signing", "saml.encryption"] as const;
|
||||||
export type KeyTypes = (typeof KEYS)[number];
|
export type KeyTypes = (typeof KEYS)[number];
|
||||||
|
|
||||||
const KEYS_MAPPING: { [key in KeyTypes]: { [index: string]: string } } = {
|
const KEYS_MAPPING: { [key in KeyTypes]: KeyMapping } = {
|
||||||
"saml.signing": {
|
"saml.signing": {
|
||||||
name: convertAttributeNameToForm("attributes.saml.client.signature"),
|
name: convertAttributeNameToForm("attributes.saml.client.signature"),
|
||||||
title: "signingKeysConfig",
|
title: "signingKeysConfig",
|
||||||
key: "clientSignature",
|
key: "clientSignature",
|
||||||
|
relatedKeys: [],
|
||||||
},
|
},
|
||||||
"saml.encryption": {
|
"saml.encryption": {
|
||||||
name: convertAttributeNameToForm("attributes.saml.encrypt"),
|
name: convertAttributeNameToForm("attributes.saml.encrypt"),
|
||||||
title: "encryptionKeysConfig",
|
title: "encryptionKeysConfig",
|
||||||
key: "encryptAssertions",
|
key: "encryptAssertions",
|
||||||
|
relatedKeys: [
|
||||||
|
convertAttributeNameToForm("attributes.saml.encryption.algorithm"),
|
||||||
|
convertAttributeNameToForm("attributes.saml.encryption.keyAlgorithm"),
|
||||||
|
convertAttributeNameToForm("attributes.saml.encryption.digestMethod"),
|
||||||
|
convertAttributeNameToForm(
|
||||||
|
"attributes.saml.encryption.maskGenerationFunction",
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -215,6 +231,9 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => {
|
|||||||
cancelButtonLabel: "no",
|
cancelButtonLabel: "no",
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
setValue(KEYS_MAPPING[selectedType!].name, "false");
|
setValue(KEYS_MAPPING[selectedType!].name, "false");
|
||||||
|
for (const key of KEYS_MAPPING[selectedType!].relatedKeys) {
|
||||||
|
setValue(key, ""); // remove related attributes when disabled
|
||||||
|
}
|
||||||
save();
|
save();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -237,6 +256,9 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => {
|
|||||||
attr={isChanged}
|
attr={isChanged}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsChanged(undefined);
|
setIsChanged(undefined);
|
||||||
|
for (const key of KEYS_MAPPING[selectedType!].relatedKeys) {
|
||||||
|
setValue(key, ""); // take defaults when enabled
|
||||||
|
}
|
||||||
save();
|
save();
|
||||||
setRefresh(refresh + 1);
|
setRefresh(refresh + 1);
|
||||||
}}
|
}}
|
||||||
@@ -262,7 +284,10 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => {
|
|||||||
clientId={clientId}
|
clientId={clientId}
|
||||||
keyInfo={keyInfo?.[index]}
|
keyInfo={keyInfo?.[index]}
|
||||||
attr={attr}
|
attr={attr}
|
||||||
onChanged={setIsChanged}
|
onChanged={(type) => {
|
||||||
|
setIsChanged(type);
|
||||||
|
setSelectedType(type);
|
||||||
|
}}
|
||||||
onGenerate={(type, isNew) => {
|
onGenerate={(type, isNew) => {
|
||||||
setSelectedType(type);
|
setSelectedType(type);
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
|
|||||||
@@ -10,15 +10,29 @@ import { clickTableRowItem } from "../utils/table";
|
|||||||
import { goToAdvancedTab, revertFineGrain, saveFineGrain } from "./advanced";
|
import { goToAdvancedTab, revertFineGrain, saveFineGrain } from "./advanced";
|
||||||
import {
|
import {
|
||||||
assertCertificate,
|
assertCertificate,
|
||||||
|
assertEncryptionAlgorithm,
|
||||||
|
assertEncryptionKeyAlgorithm,
|
||||||
|
assertEncryptionDigestMethod,
|
||||||
|
assertEncryptionMaskGenerationFunction,
|
||||||
|
assertEncryptionAlgorithmInputVisible,
|
||||||
|
assertEncryptionKeyAlgorithmInputVisible,
|
||||||
|
assertEncryptionDigestMethodInputVisible,
|
||||||
|
assertEncryptionMaskGenerationFunctionInputVisible,
|
||||||
assertNameIdFormatDropdown,
|
assertNameIdFormatDropdown,
|
||||||
assertSamlClientDetails,
|
assertSamlClientDetails,
|
||||||
assertTermsOfServiceUrl,
|
assertTermsOfServiceUrl,
|
||||||
clickClientSignature,
|
clickClientSignature,
|
||||||
clickEncryptionAssertions,
|
clickEncryptionAssertions,
|
||||||
|
clickOffEncryptionAssertions,
|
||||||
clickGenerate,
|
clickGenerate,
|
||||||
clickPostBinding,
|
clickPostBinding,
|
||||||
|
goToClientSettingsTab,
|
||||||
goToKeysTab,
|
goToKeysTab,
|
||||||
saveSamlSettings,
|
saveSamlSettings,
|
||||||
|
selectEncryptionAlgorithmInput,
|
||||||
|
selectEncryptionKeyAlgorithmInput,
|
||||||
|
selectEncryptionDigestMethodInput,
|
||||||
|
selectEncryptionMaskGenerationFunctionInput,
|
||||||
setTermsOfServiceUrl,
|
setTermsOfServiceUrl,
|
||||||
} from "./saml";
|
} from "./saml";
|
||||||
|
|
||||||
@@ -120,6 +134,7 @@ test.describe("Clients SAML tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should enable Encryption keys config", async ({ page }) => {
|
test("should enable Encryption keys config", async ({ page }) => {
|
||||||
|
// enable encryption on keys tab
|
||||||
await goToKeysTab(page);
|
await goToKeysTab(page);
|
||||||
await clickEncryptionAssertions(page);
|
await clickEncryptionAssertions(page);
|
||||||
await clickGenerate(page);
|
await clickGenerate(page);
|
||||||
@@ -129,12 +144,50 @@ test.describe("Clients SAML tests", () => {
|
|||||||
);
|
);
|
||||||
await confirmModal(page);
|
await confirmModal(page);
|
||||||
await assertCertificate(page, false);
|
await assertCertificate(page, false);
|
||||||
|
|
||||||
|
// assert encryption algorithms can be modified
|
||||||
|
await goToClientSettingsTab(page);
|
||||||
|
await assertEncryptionAlgorithm(page, "Choose...");
|
||||||
|
await assertEncryptionKeyAlgorithm(page, "Choose...");
|
||||||
|
await assertEncryptionDigestMethodInputVisible(page, false);
|
||||||
|
await assertEncryptionMaskGenerationFunctionInputVisible(page, false);
|
||||||
|
await selectEncryptionAlgorithmInput(page, "AES_256_GCM");
|
||||||
|
await selectEncryptionKeyAlgorithmInput(page, "RSA-OAEP-11");
|
||||||
|
await assertEncryptionDigestMethod(page, "Choose...");
|
||||||
|
await assertEncryptionMaskGenerationFunction(page, "Choose...");
|
||||||
|
await selectEncryptionDigestMethodInput(page, "SHA-256");
|
||||||
|
await selectEncryptionMaskGenerationFunctionInput(page, "mgf1sha256");
|
||||||
|
await selectEncryptionKeyAlgorithmInput(page, "RSA1_5");
|
||||||
|
await assertEncryptionDigestMethodInputVisible(page, false);
|
||||||
|
await assertEncryptionMaskGenerationFunctionInputVisible(page, false);
|
||||||
|
await selectEncryptionKeyAlgorithmInput(page, "RSA-OAEP-11");
|
||||||
|
await assertEncryptionDigestMethod(page, "Choose...");
|
||||||
|
await assertEncryptionMaskGenerationFunction(page, "Choose...");
|
||||||
|
await selectEncryptionDigestMethodInput(page, "SHA-256");
|
||||||
|
await selectEncryptionMaskGenerationFunctionInput(page, "mgf1sha256");
|
||||||
|
|
||||||
|
// disable encryption and check encryption algorithms are hidden
|
||||||
|
await goToKeysTab(page);
|
||||||
|
await clickOffEncryptionAssertions(page);
|
||||||
|
await confirmModal(page);
|
||||||
|
await goToClientSettingsTab(page);
|
||||||
|
await assertEncryptionAlgorithmInputVisible(page, false);
|
||||||
|
await assertEncryptionKeyAlgorithmInputVisible(page, false);
|
||||||
|
await assertEncryptionDigestMethodInputVisible(page, false);
|
||||||
|
await assertEncryptionMaskGenerationFunctionInputVisible(page, false);
|
||||||
|
|
||||||
|
// enable encryption again and check algorithms are empty/default
|
||||||
|
await goToKeysTab(page);
|
||||||
|
await clickEncryptionAssertions(page);
|
||||||
|
await confirmModal(page);
|
||||||
|
await goToClientSettingsTab(page);
|
||||||
|
await assertEncryptionAlgorithm(page, "Choose...");
|
||||||
|
await assertEncryptionKeyAlgorithm(page, "Choose...");
|
||||||
|
await assertEncryptionDigestMethodInputVisible(page, false);
|
||||||
|
await assertEncryptionMaskGenerationFunctionInputVisible(page, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should check SAML capabilities", async ({ page }) => {
|
test("should check SAML capabilities", async ({ page }) => {
|
||||||
// Assert Name ID Format dropdown exists
|
|
||||||
await assertNameIdFormatDropdown(page);
|
|
||||||
|
|
||||||
// Assert SAML Capabilities switches exist
|
// Assert SAML Capabilities switches exist
|
||||||
const switches = [
|
const switches = [
|
||||||
['[data-testid="attributes.saml_force_name_id_format"]', "on"],
|
['[data-testid="attributes.saml_force_name_id_format"]', "on"],
|
||||||
@@ -151,6 +204,9 @@ test.describe("Clients SAML tests", () => {
|
|||||||
await switchOn(page, name);
|
await switchOn(page, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert Name ID Format dropdown exists
|
||||||
|
await assertNameIdFormatDropdown(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should check access settings", async ({ page }) => {
|
test("should check access settings", async ({ page }) => {
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
import { Page, expect } from "@playwright/test";
|
import { Locator, Page, expect } from "@playwright/test";
|
||||||
import { selectItem, switchOff, switchOn } from "../utils/form";
|
import {
|
||||||
|
assertSelectValue,
|
||||||
|
selectItem,
|
||||||
|
switchOff,
|
||||||
|
switchOn,
|
||||||
|
} from "../utils/form";
|
||||||
|
|
||||||
function getTermsOfServiceUrl(page: Page) {
|
function getTermsOfServiceUrl(page: Page) {
|
||||||
return page.getByTestId("attributes.tosUri");
|
return page.getByTestId("attributes.tosUri");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getKeyForEncryptionAlgorithmInput(page: Page) {
|
||||||
|
return page.locator("#attributes\\.saml🍺encryption🍺algorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyForEncryptionKeyAlgorithmInput(page: Page) {
|
||||||
|
return page.locator("#attributes\\.saml🍺encryption🍺keyAlgorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyForEncryptionDigestMethodInput(page: Page) {
|
||||||
|
return page.locator("#attributes\\.saml🍺encryption🍺digestMethod");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyForEncryptionMaskGenerationFunctionInput(page: Page) {
|
||||||
|
return page.locator("#attributes\\.saml🍺encryption🍺maskGenerationFunction");
|
||||||
|
}
|
||||||
|
|
||||||
export async function setTermsOfServiceUrl(page: Page, url: string) {
|
export async function setTermsOfServiceUrl(page: Page, url: string) {
|
||||||
await getTermsOfServiceUrl(page).fill(url);
|
await getTermsOfServiceUrl(page).fill(url);
|
||||||
}
|
}
|
||||||
@@ -37,6 +58,10 @@ export async function goToKeysTab(page: Page) {
|
|||||||
await page.getByTestId("keysTab").click();
|
await page.getByTestId("keysTab").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function goToClientSettingsTab(page: Page) {
|
||||||
|
await page.getByTestId("clientSettingsTab").click();
|
||||||
|
}
|
||||||
|
|
||||||
export async function clickClientSignature(page: Page) {
|
export async function clickClientSignature(page: Page) {
|
||||||
await switchOff(page, "#clientSignature");
|
await switchOff(page, "#clientSignature");
|
||||||
}
|
}
|
||||||
@@ -49,6 +74,10 @@ export async function clickEncryptionAssertions(page: Page) {
|
|||||||
await switchOn(page, "#encryptAssertions");
|
await switchOn(page, "#encryptAssertions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function clickOffEncryptionAssertions(page: Page) {
|
||||||
|
await switchOff(page, "#encryptAssertions");
|
||||||
|
}
|
||||||
|
|
||||||
export async function clickGenerate(page: Page) {
|
export async function clickGenerate(page: Page) {
|
||||||
await page.getByTestId("generate").click();
|
await page.getByTestId("generate").click();
|
||||||
}
|
}
|
||||||
@@ -66,3 +95,96 @@ export async function assertNameIdFormatDropdown(page: Page) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function selectEncryptionAlgorithmInput(
|
||||||
|
page: Page,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
await selectItem(page, getKeyForEncryptionAlgorithmInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectEncryptionKeyAlgorithmInput(
|
||||||
|
page: Page,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
await selectItem(page, getKeyForEncryptionKeyAlgorithmInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectEncryptionDigestMethodInput(
|
||||||
|
page: Page,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
await selectItem(page, getKeyForEncryptionDigestMethodInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectEncryptionMaskGenerationFunctionInput(
|
||||||
|
page: Page,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
await selectItem(
|
||||||
|
page,
|
||||||
|
getKeyForEncryptionMaskGenerationFunctionInput(page),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionAlgorithm(page: Page, value: string) {
|
||||||
|
await assertSelectValue(getKeyForEncryptionAlgorithmInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionKeyAlgorithm(page: Page, value: string) {
|
||||||
|
await assertSelectValue(getKeyForEncryptionKeyAlgorithmInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionDigestMethod(page: Page, value: string) {
|
||||||
|
await assertSelectValue(getKeyForEncryptionDigestMethodInput(page), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionMaskGenerationFunction(
|
||||||
|
page: Page,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
await assertSelectValue(
|
||||||
|
getKeyForEncryptionMaskGenerationFunctionInput(page),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertInputVisible(locator: Locator, visible: boolean) {
|
||||||
|
if (visible) {
|
||||||
|
await expect(locator).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(locator).toBeHidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionAlgorithmInputVisible(
|
||||||
|
page: Page,
|
||||||
|
visible: boolean,
|
||||||
|
) {
|
||||||
|
await assertInputVisible(getKeyForEncryptionAlgorithmInput(page), visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionKeyAlgorithmInputVisible(
|
||||||
|
page: Page,
|
||||||
|
visible: boolean,
|
||||||
|
) {
|
||||||
|
await assertInputVisible(getKeyForEncryptionKeyAlgorithmInput(page), visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionDigestMethodInputVisible(
|
||||||
|
page: Page,
|
||||||
|
visible: boolean,
|
||||||
|
) {
|
||||||
|
await assertInputVisible(getKeyForEncryptionDigestMethodInput(page), visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assertEncryptionMaskGenerationFunctionInputVisible(
|
||||||
|
page: Page,
|
||||||
|
visible: boolean,
|
||||||
|
) {
|
||||||
|
await assertInputVisible(
|
||||||
|
getKeyForEncryptionMaskGenerationFunctionInput(page),
|
||||||
|
visible,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||||
|
|
||||||
|
import liquibase.exception.CustomChangeException;
|
||||||
|
import liquibase.statement.SqlStatement;
|
||||||
|
import liquibase.statement.core.RawParameterizedSqlStatement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
public class JpaUpdate26_4_0_SamlEncryptionAttributes extends CustomKeycloakTask {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTaskId() {
|
||||||
|
return "Insert legacy encryption attributes in SAML clients";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generateStatementsImpl() throws CustomChangeException {
|
||||||
|
statements.add(createInsertQueryForAttribute("saml.encryption.algorithm", "http://www.w3.org/2001/04/xmlenc#aes128-cbc"));
|
||||||
|
statements.add(createInsertQueryForAttribute("saml.encryption.keyAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"));
|
||||||
|
statements.add(createInsertQueryForAttribute("saml.encryption.digestMethod", "http://www.w3.org/2000/09/xmldsig#sha1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlStatement createInsertQueryForAttribute(String attribute, String value) {
|
||||||
|
final String clientTable = getTableName("CLIENT");
|
||||||
|
final String clientAttributesTable = getTableName("CLIENT_ATTRIBUTES");
|
||||||
|
return new RawParameterizedSqlStatement(
|
||||||
|
"INSERT INTO " + clientAttributesTable + " (CLIENT_ID,NAME,VALUE) " +
|
||||||
|
"SELECT ID, ?, ? FROM " + clientTable + " WHERE PROTOCOL = ? AND ID NOT IN " +
|
||||||
|
"(SELECT CLIENT_ID FROM " + clientAttributesTable + " WHERE NAME = ?)",
|
||||||
|
attribute, value, "saml", attribute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
~ * Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ * and other contributors as indicated by the @author tags.
|
||||||
|
~ *
|
||||||
|
~ * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ * you may not use this file except in compliance with the License.
|
||||||
|
~ * You may obtain a copy of the License at
|
||||||
|
~ *
|
||||||
|
~ * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~ *
|
||||||
|
~ * Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ * See the License for the specific language governing permissions and
|
||||||
|
~ * limitations under the License.
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||||
|
|
||||||
|
<changeSet author="keycloak" id="26.4.0-40933-saml-encryption-attributes">
|
||||||
|
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate26_4_0_SamlEncryptionAttributes"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -88,5 +88,6 @@
|
|||||||
<include file="META-INF/jpa-changelog-26.2.0.xml"/>
|
<include file="META-INF/jpa-changelog-26.2.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-26.2.6.xml"/>
|
<include file="META-INF/jpa-changelog-26.2.6.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-26.3.0.xml"/>
|
<include file="META-INF/jpa-changelog-26.3.0.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-26.4.0.xml"/>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.keycloak.exportimport.ExportOptions;
|
|||||||
import org.keycloak.exportimport.util.ExportUtils;
|
import org.keycloak.exportimport.util.ExportUtils;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.migration.MigrationProvider;
|
import org.keycloak.migration.MigrationProvider;
|
||||||
|
import org.keycloak.migration.ModelVersion;
|
||||||
import org.keycloak.migration.migrators.MigrateTo8_0_0;
|
import org.keycloak.migration.migrators.MigrateTo8_0_0;
|
||||||
import org.keycloak.migration.migrators.MigrationUtils;
|
import org.keycloak.migration.migrators.MigrationUtils;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
@@ -193,6 +194,8 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importRealm(RealmRepresentation rep, RealmModel newRealm, Runnable userImport) {
|
public void importRealm(RealmRepresentation rep, RealmModel newRealm, Runnable userImport) {
|
||||||
|
ModelVersion version = DefaultMigrationManager.getModelVersionFromRep(rep);
|
||||||
|
|
||||||
convertDeprecatedSocialProviders(rep);
|
convertDeprecatedSocialProviders(rep);
|
||||||
convertDeprecatedApplications(session, rep);
|
convertDeprecatedApplications(session, rep);
|
||||||
convertDeprecatedClientTemplates(rep);
|
convertDeprecatedClientTemplates(rep);
|
||||||
@@ -393,7 +396,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||||||
|
|
||||||
Map<String, ClientModel> createdClients = new HashMap<>();
|
Map<String, ClientModel> createdClients = new HashMap<>();
|
||||||
if (rep.getClients() != null) {
|
if (rep.getClients() != null) {
|
||||||
createdClients = createClients(session, rep, newRealm, mappedFlows);
|
createdClients = createClients(session, version, rep, newRealm, mappedFlows);
|
||||||
}
|
}
|
||||||
|
|
||||||
importRoles(rep.getRoles(), newRealm);
|
importRoles(rep.getRoles(), newRealm);
|
||||||
@@ -561,8 +564,17 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, ClientModel> createClients(KeycloakSession session, RealmRepresentation rep, RealmModel realm, Map<String, String> mappedFlows) {
|
private static void addSamlEncryptionAttributes(ClientModel app) {
|
||||||
|
if ("saml".equals(app.getProtocol()) && "true".equals(app.getAttribute("saml.encrypt"))) {
|
||||||
|
app.setAttribute("saml.encryption.algorithm", "http://www.w3.org/2001/04/xmlenc#aes128-cbc");
|
||||||
|
app.setAttribute("saml.encryption.keyAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
|
||||||
|
app.setAttribute("saml.encryption.digestMethod", "http://www.w3.org/2000/09/xmldsig#sha1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, ClientModel> createClients(KeycloakSession session, ModelVersion version, RealmRepresentation rep, RealmModel realm, Map<String, String> mappedFlows) {
|
||||||
Map<String, ClientModel> appMap = new HashMap<>();
|
Map<String, ClientModel> appMap = new HashMap<>();
|
||||||
|
final boolean samlEncryptionAttributes = version != null && version.lessThan(new ModelVersion(26, 4, 0));
|
||||||
for (ClientRepresentation resourceRep : rep.getClients()) {
|
for (ClientRepresentation resourceRep : rep.getClients()) {
|
||||||
if (Profile.isFeatureEnabled(Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
|
if (Profile.isFeatureEnabled(Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
|
||||||
if (realm.getAdminPermissionsClient() != null && realm.getAdminPermissionsClient().getClientId().equals(resourceRep.getClientId())) {
|
if (realm.getAdminPermissionsClient() != null && realm.getAdminPermissionsClient().getClientId().equals(resourceRep.getClientId())) {
|
||||||
@@ -574,6 +586,9 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||||||
if (postLogoutRedirectUris == null) {
|
if (postLogoutRedirectUris == null) {
|
||||||
app.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
|
app.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
|
||||||
}
|
}
|
||||||
|
if (samlEncryptionAttributes) {
|
||||||
|
addSamlEncryptionAttributes(app);
|
||||||
|
}
|
||||||
appMap.put(app.getClientId(), app);
|
appMap.put(app.getClientId(), app);
|
||||||
|
|
||||||
ValidationUtil.validateClient(session, app, false, r -> {
|
ValidationUtil.validateClient(session, app, false, r -> {
|
||||||
|
|||||||
@@ -196,13 +196,7 @@ public class DefaultMigrationManager implements MigrationManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void migrate(RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
|
public void migrate(RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
|
||||||
ModelVersion stored = null;
|
ModelVersion stored = getModelVersionFromRep(rep);
|
||||||
if (rep.getKeycloakVersion() != null) {
|
|
||||||
stored = convertRHSSOVersionToKeycloakVersion(rep.getKeycloakVersion());
|
|
||||||
if (stored == null) {
|
|
||||||
stored = new ModelVersion(rep.getKeycloakVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stored == null) {
|
if (stored == null) {
|
||||||
stored = migrations[0].getVersion();
|
stored = migrations[0].getVersion();
|
||||||
} else {
|
} else {
|
||||||
@@ -241,4 +235,15 @@ public class DefaultMigrationManager implements MigrationManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ModelVersion getModelVersionFromRep(RealmRepresentation rep) {
|
||||||
|
ModelVersion version = null;
|
||||||
|
if (rep.getKeycloakVersion() != null) {
|
||||||
|
version = convertRHSSOVersionToKeycloakVersion(rep.getKeycloakVersion());
|
||||||
|
if (version == null) {
|
||||||
|
version = new ModelVersion(rep.getKeycloakVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ public enum JBossSAMLURIConstants {
|
|||||||
XMLSCHEMA_NSURI("http://www.w3.org/2001/XMLSchema"),
|
XMLSCHEMA_NSURI("http://www.w3.org/2001/XMLSchema"),
|
||||||
XMLDSIG_NSURI("http://www.w3.org/2000/09/xmldsig#"),
|
XMLDSIG_NSURI("http://www.w3.org/2000/09/xmldsig#"),
|
||||||
XMLENC_NSURI("http://www.w3.org/2001/04/xmlenc#"),
|
XMLENC_NSURI("http://www.w3.org/2001/04/xmlenc#"),
|
||||||
|
XMLENC11_NSURI("http://www.w3.org/2009/xmlenc11#"),
|
||||||
XSI_NSURI("http://www.w3.org/2001/XMLSchema-instance"),
|
XSI_NSURI("http://www.w3.org/2001/XMLSchema-instance"),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.keycloak.saml;
|
package org.keycloak.saml;
|
||||||
|
|
||||||
|
import org.apache.xml.security.encryption.XMLCipher;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
@@ -68,9 +70,9 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||||||
protected boolean signAssertions;
|
protected boolean signAssertions;
|
||||||
protected SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSA_SHA1;
|
protected SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSA_SHA1;
|
||||||
protected String relayState;
|
protected String relayState;
|
||||||
protected int encryptionKeySize = 128;
|
protected int encryptionKeySize = 256;
|
||||||
protected PublicKey encryptionPublicKey;
|
protected PublicKey encryptionPublicKey;
|
||||||
protected String encryptionAlgorithm = "AES";
|
protected String encryptionAlgorithm = XMLCipher.AES_256_GCM;
|
||||||
protected boolean encrypt;
|
protected boolean encrypt;
|
||||||
protected String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
|
protected String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
|
||||||
protected String keyEncryptionAlgorithm;
|
protected String keyEncryptionAlgorithm;
|
||||||
@@ -129,12 +131,42 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String updateAesWithSize(int size) {
|
||||||
|
switch (size) {
|
||||||
|
case 128: return XMLCipher.AES_128;
|
||||||
|
case 192: return XMLCipher.AES_192;
|
||||||
|
case 256: return XMLCipher.AES_256;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Invalid size for AES: " + size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encryption algorithm to use as expected by XMLCipher (XMLCipher.AES_256_GCM for example)
|
||||||
|
* @param alg The algorithm in XMLCipher format.
|
||||||
|
* @return This same builder
|
||||||
|
*/
|
||||||
public T encryptionAlgorithm(String alg) {
|
public T encryptionAlgorithm(String alg) {
|
||||||
|
if ("AES".equals(alg)) {
|
||||||
|
// deprecated way, remove later
|
||||||
|
this.encryptionAlgorithm = updateAesWithSize(this.encryptionKeySize);
|
||||||
|
} else {
|
||||||
this.encryptionAlgorithm = alg;
|
this.encryptionAlgorithm = alg;
|
||||||
|
}
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size for the AES method to use.
|
||||||
|
* @param size
|
||||||
|
* @return This same builder
|
||||||
|
* @deprecated Use directly the XMLCipher algorithm in the
|
||||||
|
* {@link #encryptionAlgorithm(java.lang.String)} method
|
||||||
|
* (for example XMLCipher.AES_256_GCM).
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "26.4.0", forRemoval = true)
|
||||||
public T encryptionKeySize(int size) {
|
public T encryptionKeySize(int size) {
|
||||||
|
this.encryptionAlgorithm = updateAesWithSize(size);
|
||||||
this.encryptionKeySize = size;
|
this.encryptionKeySize = size;
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
@@ -284,13 +316,15 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||||||
QName encryptedAssertionElementQName = new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
QName encryptedAssertionElementQName = new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
||||||
JBossSAMLConstants.ENCRYPTED_ASSERTION.get(), samlNSPrefix);
|
JBossSAMLConstants.ENCRYPTED_ASSERTION.get(), samlNSPrefix);
|
||||||
|
|
||||||
byte[] secret = RandomSecret.createRandomSecret(encryptionKeySize / 8);
|
final String keyAlgorithm = XMLEncryptionUtil.getJCEKeyAlgorithmFromURI(encryptionAlgorithm);
|
||||||
SecretKey secretKey = new SecretKeySpec(secret, encryptionAlgorithm);
|
final int keySize = XMLEncryptionUtil.getKeyLengthFromURI(encryptionAlgorithm);
|
||||||
|
byte[] secret = RandomSecret.createRandomSecret(keySize / 8);
|
||||||
|
SecretKey secretKey = new SecretKeySpec(secret, keyAlgorithm);
|
||||||
|
|
||||||
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
|
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
|
||||||
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
||||||
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, secretKey, encryptionKeySize,
|
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, secretKey, keySize,
|
||||||
encryptedAssertionElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
encryptedAssertionElementQName, true, encryptionAlgorithm, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ProcessingException("failed to encrypt", e);
|
throw new ProcessingException("failed to encrypt", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.saml.processing.core.util;
|
package org.keycloak.saml.processing.core.util;
|
||||||
|
|
||||||
|
import org.apache.xml.security.algorithms.JCEMapper;
|
||||||
import org.apache.xml.security.encryption.EncryptedData;
|
import org.apache.xml.security.encryption.EncryptedData;
|
||||||
import org.apache.xml.security.encryption.EncryptedKey;
|
import org.apache.xml.security.encryption.EncryptedKey;
|
||||||
import org.apache.xml.security.encryption.XMLCipher;
|
import org.apache.xml.security.encryption.XMLCipher;
|
||||||
@@ -75,9 +76,7 @@ public class XMLEncryptionUtil {
|
|||||||
|
|
||||||
public static final String DS_KEY_INFO = "ds:KeyInfo";
|
public static final String DS_KEY_INFO = "ds:KeyInfo";
|
||||||
|
|
||||||
private static final String RSA_ENCRYPTION_SCHEME = Objects.equals(System.getProperty("keycloak.saml.key_trans.rsa_v1.5"), "true")
|
private static final String RSA_ENCRYPTION_SCHEME = XMLCipher.RSA_OAEP_11;
|
||||||
? XMLCipher.RSA_v1dot5
|
|
||||||
: XMLCipher.RSA_OAEP;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -93,14 +92,13 @@ public class XMLEncryptionUtil {
|
|||||||
* @param document
|
* @param document
|
||||||
* @param keyToBeEncrypted Symmetric Key (SecretKey)
|
* @param keyToBeEncrypted Symmetric Key (SecretKey)
|
||||||
* @param keyUsedToEncryptSecretKey Asymmetric Key (Public Key)
|
* @param keyUsedToEncryptSecretKey Asymmetric Key (Public Key)
|
||||||
* @param keySize Length of the key
|
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws org.keycloak.saml.common.exceptions.ProcessingException
|
* @throws org.keycloak.saml.common.exceptions.ProcessingException
|
||||||
*/
|
*/
|
||||||
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
|
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
|
||||||
int keySize, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod,
|
String keyEncryptionAlgorithm, String keyEncryptionDigestMethod,
|
||||||
String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
||||||
XMLCipher keyCipher;
|
XMLCipher keyCipher;
|
||||||
|
|
||||||
@@ -114,17 +112,32 @@ public class XMLEncryptionUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getJCEKeyAlgorithmFromURI(String algorithm) {
|
||||||
|
return JCEMapper.getJCEKeyAlgorithmFromURI(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getKeyLengthFromURI(String algorithm) {
|
||||||
|
return JCEMapper.getKeyLengthFromURI(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException {
|
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException {
|
||||||
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName, addEncryptedKeyInKeyInfo,
|
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName, addEncryptedKeyInKeyInfo,
|
||||||
null, null, null);
|
null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo,
|
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo,
|
||||||
String keyEncryptionAlgorithm) throws ProcessingException {
|
String keyEncryptionAlgorithm) throws ProcessingException {
|
||||||
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName,
|
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName,
|
||||||
addEncryptedKeyInKeyInfo, keyEncryptionAlgorithm, null, null);
|
addEncryptedKeyInKeyInfo, null, keyEncryptionAlgorithm, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||||
|
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo, String keyEncryptionAlgorithm,
|
||||||
|
String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
||||||
|
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName, addEncryptedKeyInKeyInfo,
|
||||||
|
null, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +151,7 @@ public class XMLEncryptionUtil {
|
|||||||
* @param keySize The size of the public key
|
* @param keySize The size of the public key
|
||||||
* @param wrappingElementQName A QName of an element that will wrap the encrypted element
|
* @param wrappingElementQName A QName of an element that will wrap the encrypted element
|
||||||
* @param addEncryptedKeyInKeyInfo Need for the EncryptedKey to be placed in ds:KeyInfo
|
* @param addEncryptedKeyInKeyInfo Need for the EncryptedKey to be placed in ds:KeyInfo
|
||||||
|
* @param encryptionAlgorithm The encryption algorithm
|
||||||
* @param keyEncryptionAlgorithm The wrap algorithm for the secret key (can be null, default is used depending the publicKey type)
|
* @param keyEncryptionAlgorithm The wrap algorithm for the secret key (can be null, default is used depending the publicKey type)
|
||||||
* @param keyEncryptionDigestMethod An optional digestMethod to use (can be null)
|
* @param keyEncryptionDigestMethod An optional digestMethod to use (can be null)
|
||||||
* @param keyEncryptionMgfAlgorithm The xenc11 MGF Algorithm to use (can be null)
|
* @param keyEncryptionMgfAlgorithm The xenc11 MGF Algorithm to use (can be null)
|
||||||
@@ -145,8 +159,8 @@ public class XMLEncryptionUtil {
|
|||||||
* @throws ProcessingException
|
* @throws ProcessingException
|
||||||
*/
|
*/
|
||||||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo, String keyEncryptionAlgorithm,
|
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo, String encryptionAlgorithm,
|
||||||
String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
String keyEncryptionAlgorithm, String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
||||||
if (elementQName == null)
|
if (elementQName == null)
|
||||||
throw logger.nullArgumentError("elementQName");
|
throw logger.nullArgumentError("elementQName");
|
||||||
if (document == null)
|
if (document == null)
|
||||||
@@ -160,14 +174,40 @@ public class XMLEncryptionUtil {
|
|||||||
if (documentElement == null)
|
if (documentElement == null)
|
||||||
throw logger.domMissingDocElementError(elementQName.toString());
|
throw logger.domMissingDocElementError(elementQName.toString());
|
||||||
|
|
||||||
XMLCipher cipher = null;
|
// set default algorithms
|
||||||
|
if (encryptionAlgorithm == null) {
|
||||||
|
// set default encryption based on the secret key passed
|
||||||
|
encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
|
||||||
|
}
|
||||||
|
|
||||||
if (keyEncryptionAlgorithm == null) {
|
if (keyEncryptionAlgorithm == null) {
|
||||||
// get default one for the public key
|
// get default one for the public key
|
||||||
keyEncryptionAlgorithm = getXMLEncryptionURLForKeyUnwrap(publicKey.getAlgorithm(), keySize);
|
keyEncryptionAlgorithm = getXMLEncryptionURLForKeyUnwrap(publicKey.getAlgorithm(), keySize);
|
||||||
}
|
}
|
||||||
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
|
||||||
|
|
||||||
String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
|
if ((XMLCipher.RSA_OAEP.equals(keyEncryptionAlgorithm) || XMLCipher.RSA_OAEP_11.equals(keyEncryptionAlgorithm))) {
|
||||||
|
if (keyEncryptionDigestMethod == null) {
|
||||||
|
keyEncryptionDigestMethod = XMLCipher.SHA256; // default digest method to SHA256
|
||||||
|
} else if (XMLCipher.SHA1.equals(keyEncryptionDigestMethod)){
|
||||||
|
keyEncryptionDigestMethod = null; // default by spec
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keyEncryptionDigestMethod = null; // not used for RSA_v1dot5
|
||||||
|
}
|
||||||
|
|
||||||
|
if (XMLCipher.RSA_OAEP_11.equals(keyEncryptionAlgorithm)) {
|
||||||
|
if (keyEncryptionMgfAlgorithm == null) {
|
||||||
|
keyEncryptionMgfAlgorithm = EncryptionConstants.MGF1_SHA256; // default mgf to mgf1sha256
|
||||||
|
} else if (EncryptionConstants.MGF1_SHA1.equals(keyEncryptionMgfAlgorithm)) {
|
||||||
|
keyEncryptionMgfAlgorithm = null; // default by spec
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keyEncryptionMgfAlgorithm = null; // only available for RSA_OAEP_11
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||||
|
|
||||||
|
XMLCipher cipher = null;
|
||||||
// Encrypt the Document
|
// Encrypt the Document
|
||||||
try {
|
try {
|
||||||
cipher = XMLCipher.getInstance(encryptionAlgorithm);
|
cipher = XMLCipher.getInstance(encryptionAlgorithm);
|
||||||
|
|||||||
@@ -213,7 +213,6 @@ public class SamlClient extends ClientConfigResolver {
|
|||||||
|
|
||||||
public void setClientEncryptingCertificate(String val) {
|
public void setClientEncryptingCertificate(String val) {
|
||||||
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, val);
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClientEncryptingPrivateKey() {
|
public String getClientEncryptingPrivateKey() {
|
||||||
@@ -222,7 +221,38 @@ public class SamlClient extends ClientConfigResolver {
|
|||||||
|
|
||||||
public void setClientEncryptingPrivateKey(String val) {
|
public void setClientEncryptingPrivateKey(String val) {
|
||||||
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE, val);
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientEncryptingAlgorithm() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingAlgorithm(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_ALGORITHM, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientEncryptingKeyAlgorithm() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_KEY_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingKeyAlgorithm(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_KEY_ALGORITHM, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientEncryptingDigestMethod() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_DIGEST_METHOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingDigestMethod(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_DIGEST_METHOD, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientEncryptingMaskGenerationFunction() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_MASK_GENERATION_FUNTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingMaskGenerationFunction(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_MASK_GENERATION_FUNTION, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ public interface SamlConfigAttributes {
|
|||||||
String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + CertificateInfoHelper.X509CERTIFICATE;
|
String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + CertificateInfoHelper.X509CERTIFICATE;
|
||||||
String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.X509CERTIFICATE;
|
String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.X509CERTIFICATE;
|
||||||
String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.PRIVATE_KEY;
|
String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.PRIVATE_KEY;
|
||||||
|
String SAML_ENCRYPTION_ALGORITHM = "saml.encryption.algorithm";
|
||||||
|
String SAML_ENCRYPTION_KEY_ALGORITHM = "saml.encryption.keyAlgorithm";
|
||||||
|
String SAML_ENCRYPTION_DIGEST_METHOD = "saml.encryption.digestMethod";
|
||||||
|
String SAML_ENCRYPTION_MASK_GENERATION_FUNTION = "saml.encryption.maskGenerationFunction";
|
||||||
String SAML_ASSERTION_LIFESPAN = "saml.assertion.lifespan";
|
String SAML_ASSERTION_LIFESPAN = "saml.assertion.lifespan";
|
||||||
String SAML_ARTIFACT_BINDING_IDENTIFIER = "saml.artifact.binding.identifier";
|
String SAML_ARTIFACT_BINDING_IDENTIFIER = "saml.artifact.binding.identifier";
|
||||||
String SAML_ALLOW_ECP_FLOW = "saml.allow.ecp.flow";
|
String SAML_ALLOW_ECP_FLOW = "saml.allow.ecp.flow";
|
||||||
|
|||||||
@@ -597,14 +597,12 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (samlClient.requiresEncryption()) {
|
if (samlClient.requiresEncryption()) {
|
||||||
PublicKey publicKey = null;
|
|
||||||
try {
|
try {
|
||||||
publicKey = SamlProtocolUtils.getEncryptionKey(client);
|
SamlProtocolUtils.setupEncryption(samlClient, bindingBuilder);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("failed", e);
|
logger.error("failed", e);
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
|
||||||
}
|
}
|
||||||
bindingBuilder.encrypt(publicKey);
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
samlDocument = builder.buildDocument(samlModel);
|
samlDocument = builder.buildDocument(samlModel);
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||||||
import org.keycloak.saml.SignatureAlgorithm;
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
|
|
||||||
import org.keycloak.saml.validators.DestinationValidator;
|
import org.keycloak.saml.validators.DestinationValidator;
|
||||||
|
|
||||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.security.cert.CertificateException;
|
|||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
import org.apache.xml.security.encryption.XMLCipher;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
@@ -40,6 +41,7 @@ import org.keycloak.dom.saml.v2.protocol.StatusType;
|
|||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.rotation.KeyLocator;
|
import org.keycloak.rotation.KeyLocator;
|
||||||
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
@@ -121,6 +123,28 @@ public class SamlProtocolUtils {
|
|||||||
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
|
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setupEncryption(SamlClient samlClient, BaseSAML2BindingBuilder<?> bindingBuilder) throws VerificationException {
|
||||||
|
PublicKey publicKey = getPublicKey(samlClient.getClientEncryptingCertificate());
|
||||||
|
bindingBuilder.encrypt(publicKey);
|
||||||
|
if (samlClient.getClientEncryptingAlgorithm() != null) {
|
||||||
|
bindingBuilder.encryptionAlgorithm(samlClient.getClientEncryptingAlgorithm());
|
||||||
|
}
|
||||||
|
if (samlClient.getClientEncryptingKeyAlgorithm() != null) {
|
||||||
|
bindingBuilder.keyEncryptionAlgorithm(samlClient.getClientEncryptingKeyAlgorithm());
|
||||||
|
}
|
||||||
|
if (samlClient.getClientEncryptingDigestMethod() != null &&
|
||||||
|
(XMLCipher.RSA_OAEP.equals(samlClient.getClientEncryptingKeyAlgorithm()) ||
|
||||||
|
XMLCipher.RSA_OAEP_11.equals(samlClient.getClientEncryptingKeyAlgorithm()))) {
|
||||||
|
// digest method is only available to rsa oaep
|
||||||
|
bindingBuilder.keyEncryptionDigestMethod(samlClient.getClientEncryptingDigestMethod());
|
||||||
|
}
|
||||||
|
if (samlClient.getClientEncryptingMaskGenerationFunction() != null &&
|
||||||
|
XMLCipher.RSA_OAEP_11.equals(samlClient.getClientEncryptingKeyAlgorithm())) {
|
||||||
|
// the mgf is only available for rsa oaep 11
|
||||||
|
bindingBuilder.keyEncryptionMgfAlgorithm(samlClient.getClientEncryptingMaskGenerationFunction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
|
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
|
||||||
String certPem = client.getAttribute(attribute);
|
String certPem = client.getAttribute(attribute);
|
||||||
return getPublicKey(certPem);
|
return getPublicKey(certPem);
|
||||||
|
|||||||
@@ -1305,14 +1305,12 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||||||
|
|
||||||
// Encrypt assertion if client requires it
|
// Encrypt assertion if client requires it
|
||||||
if (samlClient.requiresEncryption()) {
|
if (samlClient.requiresEncryption()) {
|
||||||
PublicKey publicKey = null;
|
|
||||||
try {
|
try {
|
||||||
publicKey = SamlProtocolUtils.getEncryptionKey(clientModel);
|
SamlProtocolUtils.setupEncryption(samlClient, bindingBuilder);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to obtain encryption key for client", e);
|
logger.error("Failed to obtain encryption key for client", e);
|
||||||
return emptyArtifactResponseMessage(artifactResolveMessage, null);
|
return emptyArtifactResponseMessage(artifactResolveMessage, null);
|
||||||
}
|
}
|
||||||
bindingBuilder.encrypt(publicKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,12 @@ public class SamlEncryptionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAES256() throws Exception {
|
public void testAES256() throws Exception {
|
||||||
testEncryption(rsaKeyPair, "AES", 256, null, null, null);
|
testEncryption(rsaKeyPair, XMLCipher.AES_256, -1, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAES256GCM() throws Exception {
|
||||||
|
testEncryption(rsaKeyPair, XMLCipher.AES_256_GCM, -1, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.keycloak.testsuite.migration;
|
|||||||
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.xml.security.encryption.XMLCipher;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
@@ -48,6 +49,7 @@ import org.keycloak.models.utils.TimeBasedOTP;
|
|||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.protocol.saml.SamlProtocolFactory;
|
import org.keycloak.protocol.saml.SamlProtocolFactory;
|
||||||
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
@@ -149,7 +151,8 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||||||
|
|
||||||
protected void testMigratedMigrationData(boolean supportsAuthzService) {
|
protected void testMigratedMigrationData(boolean supportsAuthzService) {
|
||||||
assertNames(migrationRealm.roles().list(), "offline_access", "uma_authorization", "default-roles-migration", "migration-test-realm-role");
|
assertNames(migrationRealm.roles().list(), "offline_access", "uma_authorization", "default-roles-migration", "migration-test-realm-role");
|
||||||
List<String> expectedClientIds = new ArrayList<>(Arrays.asList("account", "account-console", "admin-cli", "broker", "migration-test-client", "migration-saml-client", "realm-management", "security-admin-console"));
|
List<String> expectedClientIds = new ArrayList<>(Arrays.asList("account", "account-console", "admin-cli", "broker", "migration-test-client", "migration-saml-client",
|
||||||
|
"realm-management", "security-admin-console", "http://localhost:8280/sales-post-enc/"));
|
||||||
|
|
||||||
if (supportsAuthzService) {
|
if (supportsAuthzService) {
|
||||||
expectedClientIds.add("authz-servlet");
|
expectedClientIds.add("authz-servlet");
|
||||||
@@ -449,6 +452,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||||||
testIdpLinkActionAvailable(migrationRealm);
|
testIdpLinkActionAvailable(migrationRealm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void testMigrationTo26_4_0() {
|
||||||
|
testSamlEncryptionAttributes(migrationRealm);
|
||||||
|
}
|
||||||
|
|
||||||
private void testClientContainsExpectedClientScopes() {
|
private void testClientContainsExpectedClientScopes() {
|
||||||
// Test OIDC client contains expected client scopes
|
// Test OIDC client contains expected client scopes
|
||||||
ClientResource migrationTestOIDCClient = ApiUtil.findClientByClientId(migrationRealm, "migration-test-client");
|
ClientResource migrationTestOIDCClient = ApiUtil.findClientByClientId(migrationRealm, "migration-test-client");
|
||||||
@@ -1381,4 +1388,19 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||||||
assertTrue(clientRepresentation.isFullScopeAllowed());
|
assertTrue(clientRepresentation.isFullScopeAllowed());
|
||||||
assertTrue(Boolean.parseBoolean(clientRepresentation.getAttributes().get(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED)));
|
assertTrue(Boolean.parseBoolean(clientRepresentation.getAttributes().get(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void testSamlEncryptionAttributes(RealmResource realm) {
|
||||||
|
// check all the saml clients have the encryption attributes
|
||||||
|
List<ClientRepresentation> samlClients = realm.clients().findAll().stream()
|
||||||
|
.filter(client -> SamlProtocol.LOGIN_PROTOCOL.equals(client.getProtocol()))
|
||||||
|
.filter(client -> "true".equals(client.getAttributes().get(SamlConfigAttributes.SAML_ENCRYPT)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(samlClients.size(), is(1));
|
||||||
|
for (ClientRepresentation client : samlClients) {
|
||||||
|
assertThat(client.getAttributes().get(SamlConfigAttributes.SAML_ENCRYPTION_ALGORITHM), is(XMLCipher.AES_128));
|
||||||
|
assertThat(client.getAttributes().get(SamlConfigAttributes.SAML_ENCRYPTION_KEY_ALGORITHM), is(XMLCipher.RSA_OAEP));
|
||||||
|
assertThat(client.getAttributes().get(SamlConfigAttributes.SAML_ENCRYPTION_DIGEST_METHOD), is(XMLCipher.SHA1));
|
||||||
|
assertThat(client.getAttributes().get(SamlConfigAttributes.SAML_ENCRYPTION_MASK_GENERATION_FUNTION), nullValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
|
|||||||
testMigrationTo25_0_0();
|
testMigrationTo25_0_0();
|
||||||
testMigrationTo26_0_0(false);
|
testMigrationTo26_0_0(false);
|
||||||
testMigrationTo26_3_0();
|
testMigrationTo26_3_0();
|
||||||
|
testMigrationTo26_4_0();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class MigrationTest extends AbstractMigrationTest {
|
|||||||
testMigrationTo26_0_0(true);
|
testMigrationTo26_0_0(true);
|
||||||
testMigrationTo26_1_0(true);
|
testMigrationTo26_1_0(true);
|
||||||
testMigrationTo26_3_0();
|
testMigrationTo26_3_0();
|
||||||
|
testMigrationTo26_4_0();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -89,5 +90,6 @@ public class MigrationTest extends AbstractMigrationTest {
|
|||||||
testMigrationTo26_0_0(true);
|
testMigrationTo26_0_0(true);
|
||||||
testMigrationTo26_1_0(true);
|
testMigrationTo26_1_0(true);
|
||||||
testMigrationTo26_3_0();
|
testMigrationTo26_3_0();
|
||||||
|
testMigrationTo26_4_0();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package org.keycloak.testsuite.saml;
|
package org.keycloak.testsuite.saml;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.adapters.saml.SamlDeployment;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
@@ -13,6 +18,7 @@ import org.keycloak.saml.common.exceptions.ProcessingException;
|
|||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||||
import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
|
import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
@@ -23,6 +29,8 @@ import org.keycloak.testsuite.util.SamlClient.Binding;
|
|||||||
import org.keycloak.testsuite.util.SamlClient.RedirectStrategyWithSwitchableFollowRedirect;
|
import org.keycloak.testsuite.util.SamlClient.RedirectStrategyWithSwitchableFollowRedirect;
|
||||||
import org.keycloak.testsuite.util.SamlClient.Step;
|
import org.keycloak.testsuite.util.SamlClient.Step;
|
||||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
import org.keycloak.testsuite.util.SamlUtils;
|
||||||
|
import org.keycloak.utils.StringUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -40,11 +48,15 @@ import org.apache.http.client.methods.HttpUriRequest;
|
|||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.apache.xml.security.encryption.EncryptedData;
|
||||||
|
import org.apache.xml.security.encryption.XMLCipher;
|
||||||
|
import org.apache.xml.security.utils.EncryptionConstants;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.jboss.resteasy.util.Encode;
|
import org.jboss.resteasy.util.Encode;
|
||||||
import org.w3c.dom.Attr;
|
import org.w3c.dom.Attr;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.not;
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
@@ -54,6 +66,7 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.matchesRegex;
|
import static org.hamcrest.Matchers.matchesRegex;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT;
|
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT;
|
||||||
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
|
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
|
||||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_PORT;
|
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_PORT;
|
||||||
@@ -371,4 +384,106 @@ public class BasicSamlTest extends AbstractSamlTest {
|
|||||||
assertThat(action, endsWith("javascript:alert('xss');"));
|
assertThat(action, endsWith("javascript:alert('xss');"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void testEncryption(String algorithm, String keyAlgorithm, String digestMethod, String maskGenerationFunction) throws Exception {
|
||||||
|
testEncryption(algorithm, keyAlgorithm, digestMethod, maskGenerationFunction,
|
||||||
|
algorithm, keyAlgorithm, digestMethod, maskGenerationFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEncryption(String algorithm, String keyAlgorithm, String digestMethod, String maskGenerationFunction,
|
||||||
|
String expectedAlgorithm, String expectedKeyAlgorithm, String expectedDigestMethod, String expectedMaxskGenerationFuntion) throws Exception {
|
||||||
|
try (var c = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST_ENC)
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.TRUE.toString())
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_ENCRYPT, Boolean.TRUE.toString())
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_ALGORITHM, algorithm)
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_KEY_ALGORITHM, keyAlgorithm)
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_DIGEST_METHOD, digestMethod)
|
||||||
|
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_MASK_GENERATION_FUNTION, maskGenerationFunction)
|
||||||
|
.update()) {
|
||||||
|
SAMLDocumentHolder holder = new SamlClientBuilder()
|
||||||
|
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_ENC, SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC, Binding.POST)
|
||||||
|
.signWith(SAML_CLIENT_SALES_POST_ENC_PRIVATE_KEY, SAML_CLIENT_SALES_POST_ENC_PUBLIC_KEY)
|
||||||
|
.build()
|
||||||
|
.login().user(bburkeUser).build()
|
||||||
|
.getSamlResponse(Binding.POST);
|
||||||
|
|
||||||
|
// check it is signed
|
||||||
|
SamlProtocolUtils.verifyDocumentSignature(holder.getSamlDocument(), new HardcodedKeyLocator(PemUtils.decodePublicKey(REALM_PUBLIC_KEY)));
|
||||||
|
|
||||||
|
// check document is encrypted
|
||||||
|
ResponseType responseType = (ResponseType) holder.getSamlObject();
|
||||||
|
assertTrue("Assertion is not encrypted", AssertionUtil.isAssertionEncrypted(responseType));
|
||||||
|
|
||||||
|
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post-enc");
|
||||||
|
AssertionUtil.decryptAssertion(responseType, (EncryptedData encryptedData) -> Collections.singletonList(deployment.getDecryptionKey()));
|
||||||
|
|
||||||
|
// check algorithm is the expected one
|
||||||
|
NodeList list = holder.getSamlDocument().getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC_NSURI.get(), "EncryptedData");
|
||||||
|
assertThat("EncryptedData missing", list.getLength(), is(1));
|
||||||
|
Element encryptedData = (Element) list.item(0);
|
||||||
|
list = encryptedData.getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC_NSURI.get(), "EncryptionMethod");
|
||||||
|
assertThat("EncryptionMethod missing", list.getLength(), is(2));
|
||||||
|
Element encryptionMethod = (Element) list.item(0);
|
||||||
|
assertThat("Unexpected encryption method", encryptionMethod.getAttribute("Algorithm"), is(expectedAlgorithm));
|
||||||
|
|
||||||
|
// check keyAlgorithm is the expected one
|
||||||
|
list = encryptedData.getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC_NSURI.get(), "EncryptedKey");
|
||||||
|
assertThat("EncryptedKey missing", list.getLength(), is(1));
|
||||||
|
Element encryptedKey = (Element) list.item(0);
|
||||||
|
list = encryptedKey.getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC_NSURI.get(), "EncryptionMethod");
|
||||||
|
assertThat("EncryptionMethod missing", list.getLength(), is(1));
|
||||||
|
encryptionMethod = (Element) list.item(0);
|
||||||
|
assertThat("Unexpected key encryption method", encryptionMethod.getAttribute("Algorithm"), is(expectedKeyAlgorithm));
|
||||||
|
|
||||||
|
// check digestMethod is the expected one or missing
|
||||||
|
if (StringUtil.isNotBlank(expectedDigestMethod)) {
|
||||||
|
list = encryptionMethod.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), "DigestMethod");
|
||||||
|
assertThat("EncryptionMethod missing", list.getLength(), is(1));
|
||||||
|
Element ds = (Element) list.item(0);
|
||||||
|
assertThat("Unexpected digest method", ds.getAttribute("Algorithm"), is(expectedDigestMethod));
|
||||||
|
} else {
|
||||||
|
assertThat(encryptionMethod.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), "DigestMethod").getLength(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check mgf is the expected one or missing
|
||||||
|
if (StringUtil.isNotBlank(expectedMaxskGenerationFuntion)) {
|
||||||
|
list = encryptionMethod.getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC11_NSURI.get(), "MGF");
|
||||||
|
assertThat("EncryptionMethod missing", list.getLength(), is(1));
|
||||||
|
Element mgf = (Element) list.item(0);
|
||||||
|
assertThat("Unexpected mgf method", mgf.getAttribute("Algorithm"), is(expectedMaxskGenerationFuntion));
|
||||||
|
} else {
|
||||||
|
assertThat(encryptionMethod.getElementsByTagNameNS(JBossSAMLURIConstants.XMLENC11_NSURI.get(), "MGF").getLength(), is(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionDefault() throws Exception {
|
||||||
|
testEncryption("", "", "", "", XMLCipher.AES_256_GCM, XMLCipher.RSA_OAEP_11, XMLCipher.SHA256, EncryptionConstants.MGF1_SHA256);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionRsaOaep11() throws Exception {
|
||||||
|
testEncryption(XMLCipher.AES_256_GCM, XMLCipher.RSA_OAEP_11, XMLCipher.SHA512, EncryptionConstants.MGF1_SHA512);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionRsaOaep11Default() throws Exception {
|
||||||
|
testEncryption(XMLCipher.AES_256_GCM, XMLCipher.RSA_OAEP_11, XMLCipher.SHA1, EncryptionConstants.MGF1_SHA1, XMLCipher.AES_256_GCM, XMLCipher.RSA_OAEP_11, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionRsaOaep() throws Exception {
|
||||||
|
testEncryption(XMLCipher.AES_256_GCM, XMLCipher.RSA_OAEP, XMLCipher.SHA256, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionRsaOaepLegacy() throws Exception {
|
||||||
|
testEncryption(XMLCipher.AES_128, XMLCipher.RSA_OAEP, XMLCipher.SHA1, "", XMLCipher.AES_128, XMLCipher.RSA_OAEP, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionRsa15() throws Exception {
|
||||||
|
testEncryption(XMLCipher.AES_256_GCM, XMLCipher.RSA_v1dot5, "", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2240,6 +2240,24 @@
|
|||||||
"saml.authnstatement": "true",
|
"saml.authnstatement": "true",
|
||||||
"saml_idp_initiated_sso_url_name": "sales-post"
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
"clientId": "http://localhost:8280/sales-post-enc/",
|
||||||
|
"enabled": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post-enc",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post-enc/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml.server.signature": "true",
|
||||||
|
"saml.signature.algorithm": "RSA_SHA512",
|
||||||
|
"saml.client.signature": "true",
|
||||||
|
"saml.encrypt": "true",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml.signing.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg==",
|
||||||
|
"saml.encryption.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg=="
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"id" : "e6856a02-8f24-48d3-bb06-fae5dddae83e",
|
"id" : "e6856a02-8f24-48d3-bb06-fae5dddae83e",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
|
|||||||
@@ -716,6 +716,25 @@
|
|||||||
} ],
|
} ],
|
||||||
"defaultClientScopes" : [ "web-origins", "roles", "profile", "email" ],
|
"defaultClientScopes" : [ "web-origins", "roles", "profile", "email" ],
|
||||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clientId": "http://localhost:8280/sales-post-enc/",
|
||||||
|
"enabled": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post-enc",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post-enc/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml.server.signature": "true",
|
||||||
|
"saml.signature.algorithm": "RSA_SHA512",
|
||||||
|
"saml.client.signature": "true",
|
||||||
|
"saml.encrypt": "true",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml.signing.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg==",
|
||||||
|
"saml.encryption.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg=="
|
||||||
|
}
|
||||||
} ],
|
} ],
|
||||||
"clientScopes" : [ {
|
"clientScopes" : [ {
|
||||||
"id" : "adef1610-70ec-4282-88ef-bcb26b1f5edf",
|
"id" : "adef1610-70ec-4282-88ef-bcb26b1f5edf",
|
||||||
|
|||||||
@@ -2951,6 +2951,25 @@
|
|||||||
} ],
|
} ],
|
||||||
"defaultClientScopes" : [ "web-origins", "roles", "profile", "email" ],
|
"defaultClientScopes" : [ "web-origins", "roles", "profile", "email" ],
|
||||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clientId": "http://localhost:8280/sales-post-enc/",
|
||||||
|
"enabled": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post-enc",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post-enc/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml.server.signature": "true",
|
||||||
|
"saml.signature.algorithm": "RSA_SHA512",
|
||||||
|
"saml.client.signature": "true",
|
||||||
|
"saml.encrypt": "true",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml.signing.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg==",
|
||||||
|
"saml.encryption.certificate": "MIIDBjCCAe6gAwIBAgIJANPu/mvxOREdMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wIBcNMjQwNjIxMTkzMTE3WhgPMjEyNDA1MjgxOTMxMTdaMDAxLjAsBgNVBAMTJWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9zYWxlcy1wb3N0LWVuYy8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE5iKDNNW5XxHAF0ITErZcHDYZI68z7u68n7o4dsiywkfOWf7jVnw7PJVnMeDEtLWtTO6f0tRTqJ4OV5HYdJ9+mhPJtn+2UuvrepyYa2IsC1eFPH98ZEtYapsE6ObvhKBQMcu5G/tQrxkCFY2ssDa99unwBH5STLyX78UvqKiYnkPCvIhkiPIHy8ab7DQowc+EE9XhlE3b63A65rp4G9R87rwgJX5VTM3h81WcDuWLPOg7YRYLZoorWz2p38/qL9gXY5NxIRK16EHGfw2W1dPrX3GyMOJbXVyrBNZ6m5IL9Wn7lBEJ/Dl7ZFMFB5W36QkJ+3aaNLT/Tu/Gz+7f24inAgMBAAGjITAfMB0GA1UdDgQWBBSk7RegFbEBruVbt/VFl2gZhZ2IpDANBgkqhkiG9w0BAQsFAAOCAQEAGyH1sXVU3HDMhCzP2k5fsJBGA+1iKLMsyyiGcaD/22anQ1uVU7iWPZH8mSJGWqkvo/4oFb7RjB2KzO/50wP0q/P/tymGsYoznt+MEJKKxYEqAYmIns7SKRIgv3xEfF8yQy2jOuULC9FTq/Pb3gd9Om40jmeJtYccDSICjEC+A2fcGe56ScuRRLt+3WFyIZUFH7Y9FYZQ3EYQ88UZg//5F1ddAzGtdMSeTanMxLKow7rUIm/+Sx6cd+Vkwo/SYdk4hsD8xZCYx8Ln4i3NKh+SzyvbYykyWVI2fwjplqvM5Md/M+SNvPtU9tkOCUxQqVfz/bwtTiqfjdSaUJlasgGByg=="
|
||||||
|
}
|
||||||
} ],
|
} ],
|
||||||
"clientScopes" : [ {
|
"clientScopes" : [ {
|
||||||
"id" : "adef1610-70ec-4282-88ef-bcb26b1f5edf",
|
"id" : "adef1610-70ec-4282-88ef-bcb26b1f5edf",
|
||||||
|
|||||||
Reference in New Issue
Block a user