diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-browse.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-browse.json index e7385fb1746c4..e1b579a11e562 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-browse.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-browse.json @@ -49,8 +49,11 @@ "requestTimeout": { "index": 22, "kind": "property", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 23, "kind": "property", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 24, "kind": "property", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 25, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 26, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 25, "kind": "property", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 26, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 27, "kind": "property", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 28, "kind": "property", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 29, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } }, "headers": { "CamelMiloNodeIds": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The node ids.", "constantName": "org.apache.camel.component.milo.MiloConstants#HEADER_NODE_IDS" } @@ -87,7 +90,10 @@ "requestTimeout": { "index": 28, "kind": "parameter", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 29, "kind": "parameter", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 30, "kind": "parameter", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 31, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 32, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 31, "kind": "parameter", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 33, "kind": "parameter", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 34, "kind": "parameter", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-client.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-client.json index 8b3ac0766cbbf..e43d805aceefc 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-client.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/milo-client.json @@ -50,8 +50,11 @@ "requestTimeout": { "index": 23, "kind": "property", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 24, "kind": "property", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 25, "kind": "property", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 26, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 27, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 26, "kind": "property", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 27, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 28, "kind": "property", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 29, "kind": "property", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 30, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } }, "headers": { "CamelMiloNodeIds": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The node ids.", "constantName": "org.apache.camel.component.milo.MiloConstants#HEADER_NODE_IDS" }, @@ -96,7 +99,10 @@ "requestTimeout": { "index": 34, "kind": "parameter", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 35, "kind": "parameter", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 36, "kind": "parameter", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 37, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 38, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 37, "kind": "parameter", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 38, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 39, "kind": "parameter", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 40, "kind": "parameter", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 41, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-browse-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-browse-component.adoc index 3356e17c1e68a..a6d6a530733d5 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-browse-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-browse-component.adoc @@ -54,7 +54,7 @@ include::partial$component-endpoint-headers.adoc[] === Client The browse component shares the same base options like the Camel Milo Client component, e.g. concerning topics like discovery, -security policies, the construction of node ids, etc. +security policies, the validation of the server certificate, the construction of node ids, etc. Please refer to the documentation of the Camel Milo Client component for further details. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-client-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-client-component.adoc index e04f72f92737f..6edf23d0da402 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-client-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/milo-client-component.adoc @@ -229,10 +229,60 @@ The known security policy URIs and enum literals are can be seen here: https://g NOTE: In any case security policies are considered case sensitive. +When `allowedSecurityPolicies` is not set, the client accepts every security policy the server offers and uses the +endpoint with the highest security level reported by the server, which can be `None`. To make sure the connection is +secured, set `allowedSecurityPolicies` to the secured policies you expect the server to offer. + +=== Validating the server certificate + +When the selected endpoint uses a security policy other than `None`, the client validates the certificate of the +server when it opens the secure channel and the session. Configure the trusted certificates with one of these options: + +* `trustListDirectory`: a directory holding the trust list, in the layout used by Eclipse Milo. Put the certificate of +the server, or the certificate of the CA that issued it, in `trusted/certs`. Certificates of intermediate CAs that are +only used to build the chain go in `issuer/certs`, and certificate revocation lists in `trusted/crl` and `issuer/crl`. +The directories are created if they do not exist. +* `certificateValidator`: a custom `org.eclipse.milo.opcua.stack.core.security.CertificateValidator`. It is used +instead of `trustListDirectory` when both are set. Endpoints share a connection only when they use the same validator +instance. + +With `trustListDirectory`, the client refuses a server certificate that: + +* does not chain up to a certificate in `trusted/certs`, possibly through the certificates in `issuer/certs`, +* is outside its validity period, +* is revoked by a certificate revocation list in `trusted/crl` or `issuer/crl`. A certificate issued by a CA is also +refused, with `Bad_CertificateRevocationUnknown`, when the trust list holds no revocation list of that CA. An empty +CRL signed by the CA is enough. A self-signed certificate in `trusted/certs` is not checked for revocation, +* or holds an application URI that differs from the one the server announces in its endpoint description. + +A failed host name, key usage or extended key usage check is only logged. A certificate that does not chain up to a +trusted certificate is written to `rejected/certs`, from where it can be moved to `trusted/certs` once it has been +checked. A certificate refused for another reason is not written there. To use other checks, configure a +`certificateValidator`, for example a `DefaultClientCertificateValidator` created with the `ValidationCheck` set you +need. + +If neither option is configured, the client does not connect to an endpoint that uses a security policy other than +`None`. Setting `trustAllCertificates` to `true` makes the client accept any server certificate instead. This is +insecure: the connection is encrypted, but the client does not authenticate the server it talks to. + +When the server certificate cannot be validated, the client logs a `WARN` message that starts with `Failed to +connect`, and tries again every 10 seconds, so a certificate moved to `trusted/certs` is used at the next attempt. A +producer fails with `IllegalStateException: No connected`, whose cause gives the reason. + +The same validator checks the server certificate that the user name and password are encrypted with. With the +security policy `None`, the server is not authenticated, whatever the configuration. + +A secured policy also requires a client certificate and key, configured with `keyStoreUrl` and the related options. +For example: + +---- +milo-client:opc.tcp://localhost:4840?node=RAW(ns=2;s=Temperature)&allowedSecurityPolicies=Basic256Sha256&keyStoreUrl=file:/etc/opcua/client.p12&keyStorePassword=RAW(changeit)&keyAlias=client&keyPassword=RAW(changeit)&trustListDirectory=/etc/opcua/pki +---- + ==== Milo 1.0.5 Migration Notes With Milo 1.0.5, several security-related API changes were introduced: * The `ServerCertificateValidator` class has been replaced with `CertificateValidator` in the core security package * Certificate validation now uses `org.eclipse.milo.opcua.stack.core.security.CertificateValidator` -* When configuring custom certificate validators, ensure you're using the updated API +* A validator configured with the `certificateValidator` option must implement this interface diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseComponentConfigurer.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseComponentConfigurer.java index b7046b96293b1..af20ae1082166 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseComponentConfigurer.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseComponentConfigurer.java @@ -38,6 +38,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "applicationUri": getOrCreateConfiguration(target).setApplicationUri(property(camelContext, java.lang.String.class, value)); return true; case "autowiredenabled": case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; + case "certificatevalidator": + case "certificateValidator": getOrCreateConfiguration(target).setCertificateValidator(property(camelContext, org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class, value)); return true; case "channellifetime": case "channelLifetime": getOrCreateConfiguration(target).setChannelLifetime(property(camelContext, java.lang.Long.class, value)); return true; case "clientid": @@ -80,6 +82,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "sessionName": getOrCreateConfiguration(target).setSessionName(property(camelContext, java.lang.String.class, value)); return true; case "sessiontimeout": case "sessionTimeout": getOrCreateConfiguration(target).setSessionTimeout(property(camelContext, java.lang.Long.class, value)); return true; + case "trustallcertificates": + case "trustAllCertificates": getOrCreateConfiguration(target).setTrustAllCertificates(property(camelContext, boolean.class, value)); return true; + case "trustlistdirectory": + case "trustListDirectory": getOrCreateConfiguration(target).setTrustListDirectory(property(camelContext, java.lang.String.class, value)); return true; case "username": getOrCreateConfiguration(target).setUsername(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -101,6 +107,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "applicationUri": return java.lang.String.class; case "autowiredenabled": case "autowiredEnabled": return boolean.class; + case "certificatevalidator": + case "certificateValidator": return org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class; case "channellifetime": case "channelLifetime": return java.lang.Long.class; case "clientid": @@ -143,6 +151,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "sessionName": return java.lang.String.class; case "sessiontimeout": case "sessionTimeout": return java.lang.Long.class; + case "trustallcertificates": + case "trustAllCertificates": return boolean.class; + case "trustlistdirectory": + case "trustListDirectory": return java.lang.String.class; case "username": return java.lang.String.class; default: return null; } @@ -160,6 +172,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "applicationUri": return getOrCreateConfiguration(target).getApplicationUri(); case "autowiredenabled": case "autowiredEnabled": return target.isAutowiredEnabled(); + case "certificatevalidator": + case "certificateValidator": return getOrCreateConfiguration(target).getCertificateValidator(); case "channellifetime": case "channelLifetime": return getOrCreateConfiguration(target).getChannelLifetime(); case "clientid": @@ -202,6 +216,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "sessionName": return getOrCreateConfiguration(target).getSessionName(); case "sessiontimeout": case "sessionTimeout": return getOrCreateConfiguration(target).getSessionTimeout(); + case "trustallcertificates": + case "trustAllCertificates": return getOrCreateConfiguration(target).isTrustAllCertificates(); + case "trustlistdirectory": + case "trustListDirectory": return getOrCreateConfiguration(target).getTrustListDirectory(); case "username": return getOrCreateConfiguration(target).getUsername(); default: return null; } diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointConfigurer.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointConfigurer.java index aa088f47c678e..b67504f356a51 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointConfigurer.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointConfigurer.java @@ -29,6 +29,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "applicationName": target.getConfiguration().setApplicationName(property(camelContext, java.lang.String.class, value)); return true; case "applicationuri": case "applicationUri": target.getConfiguration().setApplicationUri(property(camelContext, java.lang.String.class, value)); return true; + case "certificatevalidator": + case "certificateValidator": target.getConfiguration().setCertificateValidator(property(camelContext, org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class, value)); return true; case "channellifetime": case "channelLifetime": target.getConfiguration().setChannelLifetime(property(camelContext, java.lang.Long.class, value)); return true; case "clientid": @@ -79,6 +81,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "sessionName": target.getConfiguration().setSessionName(property(camelContext, java.lang.String.class, value)); return true; case "sessiontimeout": case "sessionTimeout": target.getConfiguration().setSessionTimeout(property(camelContext, java.lang.Long.class, value)); return true; + case "trustallcertificates": + case "trustAllCertificates": target.getConfiguration().setTrustAllCertificates(property(camelContext, boolean.class, value)); return true; + case "trustlistdirectory": + case "trustListDirectory": target.getConfiguration().setTrustListDirectory(property(camelContext, java.lang.String.class, value)); return true; case "username": target.getConfiguration().setUsername(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -93,6 +99,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "applicationName": return java.lang.String.class; case "applicationuri": case "applicationUri": return java.lang.String.class; + case "certificatevalidator": + case "certificateValidator": return org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class; case "channellifetime": case "channelLifetime": return java.lang.Long.class; case "clientid": @@ -143,6 +151,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "sessionName": return java.lang.String.class; case "sessiontimeout": case "sessionTimeout": return java.lang.Long.class; + case "trustallcertificates": + case "trustAllCertificates": return boolean.class; + case "trustlistdirectory": + case "trustListDirectory": return java.lang.String.class; case "username": return java.lang.String.class; default: return null; } @@ -158,6 +170,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "applicationName": return target.getConfiguration().getApplicationName(); case "applicationuri": case "applicationUri": return target.getConfiguration().getApplicationUri(); + case "certificatevalidator": + case "certificateValidator": return target.getConfiguration().getCertificateValidator(); case "channellifetime": case "channelLifetime": return target.getConfiguration().getChannelLifetime(); case "clientid": @@ -208,6 +222,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "sessionName": return target.getConfiguration().getSessionName(); case "sessiontimeout": case "sessionTimeout": return target.getConfiguration().getSessionTimeout(); + case "trustallcertificates": + case "trustAllCertificates": return target.getConfiguration().isTrustAllCertificates(); + case "trustlistdirectory": + case "trustListDirectory": return target.getConfiguration().getTrustListDirectory(); case "username": return target.getConfiguration().getUsername(); default: return null; } diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointUriFactory.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointUriFactory.java index 3835e731ff908..28d4029a86350 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointUriFactory.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/browse/MiloBrowseEndpointUriFactory.java @@ -24,10 +24,11 @@ public class MiloBrowseEndpointUriFactory extends org.apache.camel.support.compo private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(33); + Set props = new HashSet<>(36); props.add("allowedSecurityPolicies"); props.add("applicationName"); props.add("applicationUri"); + props.add("certificateValidator"); props.add("channelLifetime"); props.add("clientId"); props.add("depth"); @@ -57,6 +58,8 @@ public class MiloBrowseEndpointUriFactory extends org.apache.camel.support.compo props.add("requestedPublishingInterval"); props.add("sessionName"); props.add("sessionTimeout"); + props.add("trustAllCertificates"); + props.add("trustListDirectory"); props.add("username"); PROPERTY_NAMES = Collections.unmodifiableSet(props); Set secretProps = new HashSet<>(3); diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientComponentConfigurer.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientComponentConfigurer.java index 055e23d8baf8c..a360bc71a2c0d 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientComponentConfigurer.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientComponentConfigurer.java @@ -40,6 +40,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; + case "certificatevalidator": + case "certificateValidator": getOrCreateConfiguration(target).setCertificateValidator(property(camelContext, org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class, value)); return true; case "channellifetime": case "channelLifetime": getOrCreateConfiguration(target).setChannelLifetime(property(camelContext, java.lang.Long.class, value)); return true; case "clientid": @@ -82,6 +84,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "sessionName": getOrCreateConfiguration(target).setSessionName(property(camelContext, java.lang.String.class, value)); return true; case "sessiontimeout": case "sessionTimeout": getOrCreateConfiguration(target).setSessionTimeout(property(camelContext, java.lang.Long.class, value)); return true; + case "trustallcertificates": + case "trustAllCertificates": getOrCreateConfiguration(target).setTrustAllCertificates(property(camelContext, boolean.class, value)); return true; + case "trustlistdirectory": + case "trustListDirectory": getOrCreateConfiguration(target).setTrustListDirectory(property(camelContext, java.lang.String.class, value)); return true; case "username": getOrCreateConfiguration(target).setUsername(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -105,6 +111,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "autowiredEnabled": return boolean.class; case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; + case "certificatevalidator": + case "certificateValidator": return org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class; case "channellifetime": case "channelLifetime": return java.lang.Long.class; case "clientid": @@ -147,6 +155,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "sessionName": return java.lang.String.class; case "sessiontimeout": case "sessionTimeout": return java.lang.Long.class; + case "trustallcertificates": + case "trustAllCertificates": return boolean.class; + case "trustlistdirectory": + case "trustListDirectory": return java.lang.String.class; case "username": return java.lang.String.class; default: return null; } @@ -166,6 +178,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "autowiredEnabled": return target.isAutowiredEnabled(); case "bridgeerrorhandler": case "bridgeErrorHandler": return target.isBridgeErrorHandler(); + case "certificatevalidator": + case "certificateValidator": return getOrCreateConfiguration(target).getCertificateValidator(); case "channellifetime": case "channelLifetime": return getOrCreateConfiguration(target).getChannelLifetime(); case "clientid": @@ -208,6 +222,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "sessionName": return getOrCreateConfiguration(target).getSessionName(); case "sessiontimeout": case "sessionTimeout": return getOrCreateConfiguration(target).getSessionTimeout(); + case "trustallcertificates": + case "trustAllCertificates": return getOrCreateConfiguration(target).isTrustAllCertificates(); + case "trustlistdirectory": + case "trustListDirectory": return getOrCreateConfiguration(target).getTrustListDirectory(); case "username": return getOrCreateConfiguration(target).getUsername(); default: return null; } diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointConfigurer.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointConfigurer.java index 921685c5fb791..a31809fd82e2d 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointConfigurer.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointConfigurer.java @@ -31,6 +31,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "applicationUri": target.getConfiguration().setApplicationUri(property(camelContext, java.lang.String.class, value)); return true; case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; + case "certificatevalidator": + case "certificateValidator": target.getConfiguration().setCertificateValidator(property(camelContext, org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class, value)); return true; case "channellifetime": case "channelLifetime": target.getConfiguration().setChannelLifetime(property(camelContext, java.lang.Long.class, value)); return true; case "clientid": @@ -93,6 +95,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "sessionName": target.getConfiguration().setSessionName(property(camelContext, java.lang.String.class, value)); return true; case "sessiontimeout": case "sessionTimeout": target.getConfiguration().setSessionTimeout(property(camelContext, java.lang.Long.class, value)); return true; + case "trustallcertificates": + case "trustAllCertificates": target.getConfiguration().setTrustAllCertificates(property(camelContext, boolean.class, value)); return true; + case "trustlistdirectory": + case "trustListDirectory": target.getConfiguration().setTrustListDirectory(property(camelContext, java.lang.String.class, value)); return true; case "username": target.getConfiguration().setUsername(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -109,6 +115,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "applicationUri": return java.lang.String.class; case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; + case "certificatevalidator": + case "certificateValidator": return org.eclipse.milo.opcua.stack.core.security.CertificateValidator.class; case "channellifetime": case "channelLifetime": return java.lang.Long.class; case "clientid": @@ -171,6 +179,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "sessionName": return java.lang.String.class; case "sessiontimeout": case "sessionTimeout": return java.lang.Long.class; + case "trustallcertificates": + case "trustAllCertificates": return boolean.class; + case "trustlistdirectory": + case "trustListDirectory": return java.lang.String.class; case "username": return java.lang.String.class; default: return null; } @@ -188,6 +200,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "applicationUri": return target.getConfiguration().getApplicationUri(); case "bridgeerrorhandler": case "bridgeErrorHandler": return target.isBridgeErrorHandler(); + case "certificatevalidator": + case "certificateValidator": return target.getConfiguration().getCertificateValidator(); case "channellifetime": case "channelLifetime": return target.getConfiguration().getChannelLifetime(); case "clientid": @@ -250,6 +264,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "sessionName": return target.getConfiguration().getSessionName(); case "sessiontimeout": case "sessionTimeout": return target.getConfiguration().getSessionTimeout(); + case "trustallcertificates": + case "trustAllCertificates": return target.getConfiguration().isTrustAllCertificates(); + case "trustlistdirectory": + case "trustListDirectory": return target.getConfiguration().getTrustListDirectory(); case "username": return target.getConfiguration().getUsername(); default: return null; } diff --git a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointUriFactory.java b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointUriFactory.java index 678e4ed402d75..fe6f76a6cfee6 100644 --- a/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointUriFactory.java +++ b/components/camel-milo/src/generated/java/org/apache/camel/component/milo/client/MiloClientEndpointUriFactory.java @@ -24,11 +24,12 @@ public class MiloClientEndpointUriFactory extends org.apache.camel.support.compo private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(39); + Set props = new HashSet<>(42); props.add("allowedSecurityPolicies"); props.add("applicationName"); props.add("applicationUri"); props.add("bridgeErrorHandler"); + props.add("certificateValidator"); props.add("channelLifetime"); props.add("clientId"); props.add("dataChangeFilterDeadbandType"); @@ -63,6 +64,8 @@ public class MiloClientEndpointUriFactory extends org.apache.camel.support.compo props.add("samplingInterval"); props.add("sessionName"); props.add("sessionTimeout"); + props.add("trustAllCertificates"); + props.add("trustListDirectory"); props.add("username"); PROPERTY_NAMES = Collections.unmodifiableSet(props); Set secretProps = new HashSet<>(3); diff --git a/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/browse/milo-browse.json b/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/browse/milo-browse.json index e7385fb1746c4..e1b579a11e562 100644 --- a/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/browse/milo-browse.json +++ b/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/browse/milo-browse.json @@ -49,8 +49,11 @@ "requestTimeout": { "index": 22, "kind": "property", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 23, "kind": "property", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 24, "kind": "property", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 25, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 26, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 25, "kind": "property", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 26, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 27, "kind": "property", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 28, "kind": "property", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 29, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } }, "headers": { "CamelMiloNodeIds": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The node ids.", "constantName": "org.apache.camel.component.milo.MiloConstants#HEADER_NODE_IDS" } @@ -87,7 +90,10 @@ "requestTimeout": { "index": 28, "kind": "parameter", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 29, "kind": "parameter", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 30, "kind": "parameter", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 31, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 32, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 31, "kind": "parameter", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 33, "kind": "parameter", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 34, "kind": "parameter", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } } } diff --git a/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/client/milo-client.json b/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/client/milo-client.json index 8b3ac0766cbbf..e43d805aceefc 100644 --- a/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/client/milo-client.json +++ b/components/camel-milo/src/generated/resources/META-INF/org/apache/camel/component/milo/client/milo-client.json @@ -50,8 +50,11 @@ "requestTimeout": { "index": 23, "kind": "property", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 24, "kind": "property", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 25, "kind": "property", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 26, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 27, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 26, "kind": "property", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 27, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 28, "kind": "property", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 29, "kind": "property", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 30, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } }, "headers": { "CamelMiloNodeIds": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The node ids.", "constantName": "org.apache.camel.component.milo.MiloConstants#HEADER_NODE_IDS" }, @@ -96,7 +99,10 @@ "requestTimeout": { "index": 34, "kind": "parameter", "displayName": "Request Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds" }, "sessionName": { "index": 35, "kind": "parameter", "displayName": "Session Name", "group": "client", "label": "client", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session name" }, "sessionTimeout": { "index": 36, "kind": "parameter", "displayName": "Session Timeout", "group": "client", "label": "client", "required": false, "type": "integer", "javaType": "java.lang.Long", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Session timeout in milliseconds" }, - "password": { "index": 37, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, - "username": { "index": 38, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } + "certificateValidator": { "index": 37, "kind": "parameter", "displayName": "Certificate Validator", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.eclipse.milo.opcua.stack.core.security.CertificateValidator", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance." }, + "password": { "index": 38, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The password for authentication. Use this instead of embedding credentials in the endpoint URI when the password contains special characters (such as {code }, {code \/}, {code }, {code &})." }, + "trustAllCertificates": { "index": 39, "kind": "parameter", "displayName": "Trust All Certificates", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:ssl", "defaultValue": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, but the client does not authenticate the server it talks to. It only applies when neither certificateValidator nor trustListDirectory is configured." }, + "trustListDirectory": { "index": 40, "kind": "parameter", "displayName": "Trust List Directory", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by Eclipse Milo: trusted certificates in trusted\/certs, CRLs of trusted certificates in trusted\/crl, certificates of issuing CAs in issuer\/certs and their CRLs in issuer\/crl. The directories are created if they do not exist. Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is written to rejected\/certs, from where it can be moved to trusted\/certs once it has been checked." }, + "username": { "index": 41, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.milo.client.MiloClientConfiguration", "configurationField": "configuration", "description": "The username for authentication. Use this instead of embedding credentials in the endpoint URI when the username contains special characters (such as {code }, {code \/}, {code }, {code &})." } } } diff --git a/components/camel-milo/src/main/docs/milo-browse-component.adoc b/components/camel-milo/src/main/docs/milo-browse-component.adoc index 3356e17c1e68a..a6d6a530733d5 100644 --- a/components/camel-milo/src/main/docs/milo-browse-component.adoc +++ b/components/camel-milo/src/main/docs/milo-browse-component.adoc @@ -54,7 +54,7 @@ include::partial$component-endpoint-headers.adoc[] === Client The browse component shares the same base options like the Camel Milo Client component, e.g. concerning topics like discovery, -security policies, the construction of node ids, etc. +security policies, the validation of the server certificate, the construction of node ids, etc. Please refer to the documentation of the Camel Milo Client component for further details. diff --git a/components/camel-milo/src/main/docs/milo-client-component.adoc b/components/camel-milo/src/main/docs/milo-client-component.adoc index e04f72f92737f..6edf23d0da402 100644 --- a/components/camel-milo/src/main/docs/milo-client-component.adoc +++ b/components/camel-milo/src/main/docs/milo-client-component.adoc @@ -229,10 +229,60 @@ The known security policy URIs and enum literals are can be seen here: https://g NOTE: In any case security policies are considered case sensitive. +When `allowedSecurityPolicies` is not set, the client accepts every security policy the server offers and uses the +endpoint with the highest security level reported by the server, which can be `None`. To make sure the connection is +secured, set `allowedSecurityPolicies` to the secured policies you expect the server to offer. + +=== Validating the server certificate + +When the selected endpoint uses a security policy other than `None`, the client validates the certificate of the +server when it opens the secure channel and the session. Configure the trusted certificates with one of these options: + +* `trustListDirectory`: a directory holding the trust list, in the layout used by Eclipse Milo. Put the certificate of +the server, or the certificate of the CA that issued it, in `trusted/certs`. Certificates of intermediate CAs that are +only used to build the chain go in `issuer/certs`, and certificate revocation lists in `trusted/crl` and `issuer/crl`. +The directories are created if they do not exist. +* `certificateValidator`: a custom `org.eclipse.milo.opcua.stack.core.security.CertificateValidator`. It is used +instead of `trustListDirectory` when both are set. Endpoints share a connection only when they use the same validator +instance. + +With `trustListDirectory`, the client refuses a server certificate that: + +* does not chain up to a certificate in `trusted/certs`, possibly through the certificates in `issuer/certs`, +* is outside its validity period, +* is revoked by a certificate revocation list in `trusted/crl` or `issuer/crl`. A certificate issued by a CA is also +refused, with `Bad_CertificateRevocationUnknown`, when the trust list holds no revocation list of that CA. An empty +CRL signed by the CA is enough. A self-signed certificate in `trusted/certs` is not checked for revocation, +* or holds an application URI that differs from the one the server announces in its endpoint description. + +A failed host name, key usage or extended key usage check is only logged. A certificate that does not chain up to a +trusted certificate is written to `rejected/certs`, from where it can be moved to `trusted/certs` once it has been +checked. A certificate refused for another reason is not written there. To use other checks, configure a +`certificateValidator`, for example a `DefaultClientCertificateValidator` created with the `ValidationCheck` set you +need. + +If neither option is configured, the client does not connect to an endpoint that uses a security policy other than +`None`. Setting `trustAllCertificates` to `true` makes the client accept any server certificate instead. This is +insecure: the connection is encrypted, but the client does not authenticate the server it talks to. + +When the server certificate cannot be validated, the client logs a `WARN` message that starts with `Failed to +connect`, and tries again every 10 seconds, so a certificate moved to `trusted/certs` is used at the next attempt. A +producer fails with `IllegalStateException: No connected`, whose cause gives the reason. + +The same validator checks the server certificate that the user name and password are encrypted with. With the +security policy `None`, the server is not authenticated, whatever the configuration. + +A secured policy also requires a client certificate and key, configured with `keyStoreUrl` and the related options. +For example: + +---- +milo-client:opc.tcp://localhost:4840?node=RAW(ns=2;s=Temperature)&allowedSecurityPolicies=Basic256Sha256&keyStoreUrl=file:/etc/opcua/client.p12&keyStorePassword=RAW(changeit)&keyAlias=client&keyPassword=RAW(changeit)&trustListDirectory=/etc/opcua/pki +---- + ==== Milo 1.0.5 Migration Notes With Milo 1.0.5, several security-related API changes were introduced: * The `ServerCertificateValidator` class has been replaced with `CertificateValidator` in the core security package * Certificate validation now uses `org.eclipse.milo.opcua.stack.core.security.CertificateValidator` -* When configuring custom certificate validators, ensure you're using the updated API +* A validator configured with the `certificateValidator` option must implement this interface diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java index 93f292aaef79a..3916cf652d48c 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java @@ -30,6 +30,7 @@ import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriParams; import org.eclipse.milo.opcua.sdk.client.OpcUaClientConfigBuilder; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; @@ -119,6 +120,15 @@ public class MiloClientConfiguration implements Cloneable { @UriParam(label = "security", security = "secret") private String password; + @UriParam(label = "security") + private String trustListDirectory; + + @UriParam(label = "security") + private CertificateValidator certificateValidator; + + @UriParam(label = "security", security = "insecure:ssl") + private boolean trustAllCertificates; + public MiloClientConfiguration() { } @@ -146,6 +156,9 @@ public MiloClientConfiguration(final MiloClientConfiguration other) { this.requestedPublishingInterval = other.requestedPublishingInterval; this.username = other.username; this.password = other.password; + this.trustListDirectory = other.trustListDirectory; + this.certificateValidator = other.certificateValidator; + this.trustAllCertificates = other.trustAllCertificates; } /** @@ -447,6 +460,48 @@ public String getPassword() { return this.password; } + /** + * The directory holding the trust list used to validate the certificate of the OPC UA server, in the layout used by + * Eclipse Milo: trusted certificates in trusted/certs, CRLs of trusted certificates in trusted/crl, certificates of + * issuing CAs in issuer/certs and their CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a CRL of the trust list and the application + * URI of the server certificate are checked. A certificate issued by a CA is refused when the trust list holds no + * CRL of that CA; an empty CRL is enough. A server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to trusted/certs once it has been checked. + */ + public void setTrustListDirectory(final String trustListDirectory) { + this.trustListDirectory = trustListDirectory; + } + + public String getTrustListDirectory() { + return this.trustListDirectory; + } + + /** + * A custom validator for the certificate of the OPC UA server. When set, it is used instead of the trust list + * configured with trustListDirectory. Endpoints share a connection only when they use the same validator instance. + */ + public void setCertificateValidator(final CertificateValidator certificateValidator) { + this.certificateValidator = certificateValidator; + } + + public CertificateValidator getCertificateValidator() { + return this.certificateValidator; + } + + /** + * Whether to accept any certificate presented by the OPC UA server. This is insecure: the connection is encrypted, + * but the client does not authenticate the server it talks to. It only applies when neither certificateValidator + * nor trustListDirectory is configured. + */ + public void setTrustAllCertificates(final boolean trustAllCertificates) { + this.trustAllCertificates = trustAllCertificates; + } + + public boolean isTrustAllCertificates() { + return this.trustAllCertificates; + } + public Double getRequestedPublishingInterval() { return requestedPublishingInterval; } @@ -464,11 +519,28 @@ public MiloClientConfiguration clone() { } public String toCacheId() { + final String cacheId; if (this.clientId != null && !this.clientId.isEmpty()) { - return this.endpointUri + "|" + this.clientId + "|" + this.requestedPublishingInterval; + cacheId = this.endpointUri + "|" + this.clientId + "|" + this.requestedPublishingInterval; } else { - return this.endpointUri + "|" + this.requestedPublishingInterval; + cacheId = this.endpointUri + "|" + this.requestedPublishingInterval; + } + // endpoints that validate the server certificate differently must not share a connection + return cacheId + serverCertificateTrustCacheId(); + } + + private String serverCertificateTrustCacheId() { + if (this.certificateValidator != null) { + return "|certificateValidator=" + this.certificateValidator.getClass().getName() + "@" + + Integer.toHexString(System.identityHashCode(this.certificateValidator)); + } + if (this.trustListDirectory != null && !this.trustListDirectory.isEmpty()) { + return "|trustListDirectory=" + this.trustListDirectory; + } + if (this.trustAllCertificates) { + return "|trustAllCertificates"; } + return ""; } public OpcUaClientConfigBuilder newBuilder() { diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrust.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrust.java new file mode 100644 index 0000000000000..5e3d92a0459c4 --- /dev/null +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrust.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.milo.client.internal; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.camel.component.milo.client.MiloClientConfiguration; +import org.eclipse.milo.opcua.stack.core.StatusCodes; +import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.security.CertificateQuarantine; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.FileBasedCertificateQuarantine; +import org.eclipse.milo.opcua.stack.core.security.FileBasedTrustListManager; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; +import org.eclipse.milo.opcua.stack.core.util.validation.ValidationCheck; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Selects the validator for the certificate of the OPC UA server, and owns the file based trust list when one is + * configured. The trust list is created on first use and kept until {@link #close()}, so a reconnect reuses it. + */ +final class ServerCertificateTrust implements Closeable { + + /** + * The checks of the trust list validator besides the chain of trust. As OPC UA requires, a certificate issued by a + * CA is refused when the trust list holds no revocation list of that CA. A self-signed certificate of the trust + * list is not checked for revocation. + */ + static final Set TRUST_LIST_CHECKS + = Set.of(ValidationCheck.APPLICATION_URI, ValidationCheck.VALIDITY, ValidationCheck.REVOCATION); + + private static final Set CERTIFICATE_STATUS_CODES = Set.of( + StatusCodes.Bad_SecurityChecksFailed, + StatusCodes.Bad_CertificateInvalid, + StatusCodes.Bad_CertificateUntrusted, + StatusCodes.Bad_CertificateTimeInvalid, + StatusCodes.Bad_CertificateIssuerTimeInvalid, + StatusCodes.Bad_CertificateHostNameInvalid, + StatusCodes.Bad_CertificateUriInvalid, + StatusCodes.Bad_CertificateUseNotAllowed, + StatusCodes.Bad_CertificateIssuerUseNotAllowed, + StatusCodes.Bad_CertificateRevocationUnknown, + StatusCodes.Bad_CertificateIssuerRevocationUnknown, + StatusCodes.Bad_CertificateRevoked, + StatusCodes.Bad_CertificateIssuerRevoked, + StatusCodes.Bad_CertificateChainIncomplete, + StatusCodes.Bad_CertificatePolicyCheckFailed); + + private static final Logger LOG = LoggerFactory.getLogger(ServerCertificateTrust.class); + + private final MiloClientConfiguration configuration; + private final Lock lock = new ReentrantLock(); + + private FileBasedTrustListManager trustListManager; + private CertificateValidator trustListValidator; + private boolean trustAllLogged; + private boolean closed; + + ServerCertificateTrust(final MiloClientConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Returns the validator for the certificate of the server that provides the given endpoint. + * + * @param endpoint the endpoint selected from the discovery result, may be {@code null} + * @return the validator to use for the secure channel, the session and the identity token + * @throws NotConfiguredException if the endpoint uses a security policy other than None and no way to validate the + * server certificate is configured + * @throws IOException if the trust list directory cannot be read or created + */ + CertificateValidator validatorFor(final EndpointDescription endpoint) throws IOException { + if (this.configuration.getCertificateValidator() != null) { + return this.configuration.getCertificateValidator(); + } + + final String trustListDirectory = this.configuration.getTrustListDirectory(); + if (trustListDirectory != null && !trustListDirectory.isEmpty()) { + return trustListValidator(Paths.get(trustListDirectory)); + } + + if (this.configuration.isTrustAllCertificates()) { + if (isSecured(endpoint)) { + logTrustAllOnce(); + } + return new CertificateValidator.InsecureCertificateValidator(); + } + + if (isSecured(endpoint)) { + throw new NotConfiguredException( + "The certificate of the OPC UA server cannot be validated: endpoint " + endpoint.getEndpointUrl() + + " uses security policy " + endpoint.getSecurityPolicyUri() + + ", but neither trustListDirectory nor certificateValidator is configured." + + " Configure one of them, or set trustAllCertificates=true to accept any" + + " server certificate (insecure)."); + } + + // SecurityPolicy None: the server is not authenticated in any case, and its certificate is at most used to + // encrypt the user identity token + return new CertificateValidator.InsecureCertificateValidator(); + } + + /** + * Whether the endpoint uses a security policy other than None, in which case the server certificate is validated. + */ + static boolean isSecured(final EndpointDescription endpoint) { + return endpoint != null && !SecurityPolicy.None.getUri().equals(endpoint.getSecurityPolicyUri()); + } + + /** + * Finds the reason of a connection failure that a retry does not fix by itself: a missing trust configuration, or a + * certificate or security check that failed, on the client or on the server side. + * + * @param failure the failure of a connection attempt + * @return the exception that describes the reason, or {@code null} for any other failure + */ + static Throwable findCertificateFailure(final Throwable failure) { + Throwable current = failure; + for (int depth = 0; current != null && depth < 16; depth++) { + if (current instanceof NotConfiguredException) { + return current; + } + if (current instanceof UaException uaException + && CERTIFICATE_STATUS_CODES.contains(uaException.getStatusCode().getValue())) { + return current; + } + current = current.getCause(); + } + return null; + } + + private CertificateValidator trustListValidator(final Path directory) throws IOException { + this.lock.lock(); + try { + if (this.closed) { + throw new IllegalStateException("The trust list in " + directory + " has been closed"); + } + if (this.trustListValidator == null) { + final CertificateQuarantine quarantine + = FileBasedCertificateQuarantine.create(directory.resolve("rejected").resolve("certs")); + final FileBasedTrustListManager manager = FileBasedTrustListManager.createAndInitialize(directory); + + if (manager.getTrustedCertificates().isEmpty() && manager.getIssuerCertificates().isEmpty()) { + LOG.warn("The trust list in {} contains no certificates, so the certificate of the OPC UA server will be" + + " rejected. Add the server certificate, or the certificate of its CA, to {}.", + directory, directory.resolve("trusted").resolve("certs")); + } + + this.trustListManager = manager; + this.trustListValidator = new DefaultClientCertificateValidator(manager, TRUST_LIST_CHECKS, quarantine); + } + return this.trustListValidator; + } finally { + this.lock.unlock(); + } + } + + private void logTrustAllOnce() { + this.lock.lock(); + try { + if (this.trustAllLogged) { + return; + } + this.trustAllLogged = true; + } finally { + this.lock.unlock(); + } + LOG.warn("The milo client is configured with trustAllCertificates=true. The certificate of the OPC UA server is" + + " not validated, so the client does not authenticate the server it connects to. This should not be" + + " used in production environments."); + } + + @Override + public void close() throws IOException { + final FileBasedTrustListManager manager; + + this.lock.lock(); + try { + this.closed = true; + manager = this.trustListManager; + this.trustListManager = null; + this.trustListValidator = null; + } finally { + this.lock.unlock(); + } + + if (manager != null) { + manager.close(); + } + } + + /** + * The endpoint uses a security policy other than None, but no way to validate the server certificate is configured. + * Retrying does not help until the configuration changes. + */ + static final class NotConfiguredException extends IllegalStateException { + + private static final long serialVersionUID = 1L; + + NotConfiguredException(final String message) { + super(message); + } + } +} diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java index 32957dfaf103c..bacf42dcf0a99 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.milo.client.internal; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -59,6 +60,7 @@ import org.eclipse.milo.opcua.stack.core.NodeIds; import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId; @@ -652,9 +654,11 @@ nodeId, direction, NodeIds.References, includeSubTypes, uint(nodeClasses), private final MiloClientConfiguration configuration; private final ScheduledExecutorService executor; private final long reconnectTimeout; + private final ServerCertificateTrust serverCertificateTrust; private final Lock lock = new ReentrantLock(); private Connected connected; + private volatile Throwable lastConnectFailure; private boolean disposed; private Future reconnectJob; private final Map subscriptions = new HashMap<>(); @@ -665,6 +669,7 @@ public SubscriptionManager(final MiloClientConfiguration configuration, final Sc this.configuration = configuration; this.executor = executor; this.reconnectTimeout = reconnectTimeout; + this.serverCertificateTrust = new ServerCertificateTrust(configuration); connect(); } @@ -690,6 +695,7 @@ private void handleConnectionFailure(final Throwable e) { this.connected.dispose(); this.connected = null; } + this.lastConnectFailure = e; // log @@ -740,6 +746,7 @@ private void performAndEvalConnect() { LOG.debug("Update state : {} -> {}", this.connected, connected); final Connected oldConnected = this.connected; this.connected = connected; + this.lastConnectFailure = null; if (oldConnected != null) { LOG.debug("Dispose old state"); @@ -755,7 +762,8 @@ private void performAndEvalConnect() { lock.unlock(); } } catch (final Exception e) { - LOG.info("Failed to connect", e); + this.lastConnectFailure = e; + logConnectFailure(e); triggerReconnect(false); } @@ -763,6 +771,22 @@ private void performAndEvalConnect() { pendingValues.forEach(PendingValue::deliver); } + private static void logConnectFailure(final Exception e) { + final Throwable certificateFailure = ServerCertificateTrust.findCertificateFailure(e); + if (certificateFailure instanceof ServerCertificateTrust.NotConfiguredException) { + LOG.warn("Failed to connect: {}", certificateFailure.getMessage()); + LOG.debug("Failed to connect", e); + } else if (certificateFailure != null) { + // a certificate rejected by the trust list can be trusted later, so the client keeps retrying + // passed as text: SLF4J would log a trailing Throwable as the exception, with its stack trace + LOG.warn("Failed to connect, because a certificate or security check failed: {}", + String.valueOf(certificateFailure)); + LOG.debug("Failed to connect", e); + } else { + LOG.info("Failed to connect", e); + } + } + private Connected performConnect() throws Exception { // eval endpoint @@ -796,6 +820,9 @@ private Connected performConnect() throws Exception { LOG.debug("Selected endpoint: {}", endpoint); + // the same validator checks the server certificate for the secure channel, the session and the identity token + final CertificateValidator certificateValidator = this.serverCertificateTrust.validatorFor(endpoint); + // set identity providers final List providers = new LinkedList<>(); @@ -805,7 +832,8 @@ private Connected performConnect() throws Exception { final String explicitPassword = this.configuration.getPassword(); if (explicitUsername != null && !explicitUsername.isEmpty()) { LOG.debug("Enable username/password provider (explicit parameter): {}", explicitUsername); - providers.add(new UsernameProvider(explicitUsername, explicitPassword != null ? explicitPassword : "")); + providers.add(new UsernameProvider( + explicitUsername, explicitPassword != null ? explicitPassword : "", certificateValidator)); } else if (user != null && !user.isEmpty()) { final String[] creds = user.split(":", 2); if (creds != null) { @@ -813,7 +841,7 @@ private Connected performConnect() throws Exception { LOG.debug("Enable username/password provider: {}", creds[0]); } - providers.add(new UsernameProvider(creds[0], creds.length == 2 ? creds[1] : "")); + providers.add(new UsernameProvider(creds[0], creds.length == 2 ? creds[1] : "", certificateValidator)); } } @@ -822,6 +850,7 @@ private Connected performConnect() throws Exception { final OpcUaClientConfigBuilder cfg = this.configuration.newBuilder(); cfg.setIdentityProvider(new CompositeProvider(providers)); cfg.setEndpoint(endpoint); + cfg.setCertificateValidator(certificateValidator); // create client final OpcUaClient client = OpcUaClient.create( @@ -889,6 +918,12 @@ public void dispose() { // dispose outside of lock connected.dispose(); } + + try { + this.serverCertificateTrust.close(); + } catch (final IOException e) { + LOG.debug("Failed to close the trust list. This exception is ignored.", e); + } } private void triggerReconnect(final boolean immediate) { @@ -1005,9 +1040,10 @@ protected void whenConnected(final Worker worker) { } } - private static CompletableFuture newNotConnectedResult() { + private CompletableFuture newNotConnectedResult() { final CompletableFuture result = new CompletableFuture<>(); - result.completeExceptionally(new IllegalStateException("No connected")); + // the reason of the last failed connection attempt, if any, tells the caller why there is no connection + result.completeExceptionally(new IllegalStateException("No connected", this.lastConnectFailure)); return result; } diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/ServerCertificateValidationTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/ServerCertificateValidationTest.java new file mode 100644 index 0000000000000..98a822dd4085a --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/ServerCertificateValidationTest.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.milo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.X509Certificate; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.apache.camel.CamelContext; +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.milo.call.TestCertificateFactory; +import org.apache.camel.component.milo.server.MiloServerComponent; +import org.apache.camel.component.mock.MockEndpoint; +import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.DefaultApplicationGroup; +import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; +import org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.MemoryCertificateQuarantine; +import org.eclipse.milo.opcua.stack.core.security.MemoryCertificateStore; +import org.eclipse.milo.opcua.stack.core.security.MemoryTrustListManager; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The client validates the certificate of a server that only offers a secured endpoint. + */ +class ServerCertificateValidationTest extends AbstractMiloServerTest { + + private static final String DIRECT_START = "direct:start"; + + private static final String MILO_SERVER_ITEM = "milo-server:myitem1"; + + private static final String MILO_CLIENT = "milo-client:opc.tcp://foo:bar@localhost:@@port@@?node=" + + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "myitem1") + + "&keyStoreUrl=file:src/test/resources/keystore&keyStorePassword=testtest" + + "&keyPassword=test&keyAlias=test&allowedSecurityPolicies=Basic256Sha256" + + "&discoveryEndpointSuffix=/discovery&overrideHost=true"; + + private static final String SERVER_APPLICATION_URI = "urn:eclipse:milo:test"; + + @TempDir + static Path pki; + + private final RecordingCertificateValidator recordingValidator = new RecordingCertificateValidator(); + + @EndpointInject("mock:trustList") + MockEndpoint trustListEndpoint; + + @EndpointInject("mock:emptyTrustList") + MockEndpoint emptyTrustListEndpoint; + + @EndpointInject("mock:notConfigured") + MockEndpoint notConfiguredEndpoint; + + @EndpointInject("mock:trustAll") + MockEndpoint trustAllEndpoint; + + @EndpointInject("mock:certificateValidator") + MockEndpoint certificateValidatorEndpoint; + + @Produce(DIRECT_START) + ProducerTemplate producer; + + @Override + protected void configureContext(final CamelContext context) throws Exception { + super.configureContext(context); + context.getRegistry().bind("recordingValidator", this.recordingValidator); + } + + @Override + protected void configureMiloServer(final MiloServerComponent server) throws Exception { + super.configureMiloServer(server); + + // the server uses a self-signed certificate, and accepts any client: the client is under test here + final DefaultApplicationGroup certificateGroup = DefaultApplicationGroup.createAndInitialize( + new MemoryTrustListManager(), new MemoryCertificateStore(), new TestCertificateFactory(), + new CertificateValidator.InsecureCertificateValidator()); + final X509Certificate serverCertificate = certificateGroup.getCertificateEntries().get(0).certificateChain[0]; + + server.setApplicationUri(SERVER_APPLICATION_URI); + server.setCertificateManager(new DefaultCertificateManager(new MemoryCertificateQuarantine(), certificateGroup)); + server.setCertificate(serverCertificate); + server.setSecurityPolicies(EnumSet.of(SecurityPolicy.Basic256Sha256)); + server.setUsernameSecurityPolicyUri(SecurityPolicy.Basic256Sha256); + + final Path trustedCerts = Files.createDirectories(pki.resolve("trusted").resolve("trusted").resolve("certs")); + Files.write(trustedCerts.resolve("server.der"), serverCertificate.getEncoded()); + + this.recordingValidator.trust(serverCertificate); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from(DIRECT_START).to(MILO_SERVER_ITEM); + + from(resolve(trustListClient("trusted"))).to("mock:trustList"); + from(resolve(trustListClient("empty"))).to("mock:emptyTrustList"); + from(resolve(MILO_CLIENT)).to("mock:notConfigured"); + from(resolve(MILO_CLIENT + "&trustAllCertificates=true")).to("mock:trustAll"); + from(resolve(MILO_CLIENT + "&certificateValidator=#recordingValidator")).to("mock:certificateValidator"); + } + }; + } + + @Test + void onlyClientsThatCanValidateTheServerCertificateConnect() throws Exception { + this.trustListEndpoint.setExpectedCount(1); + this.trustAllEndpoint.setExpectedCount(1); + this.certificateValidatorEndpoint.setExpectedCount(1); + + this.emptyTrustListEndpoint.setExpectedCount(0); + this.emptyTrustListEndpoint.setSleepForEmptyTest(5_000); + this.notConfiguredEndpoint.setExpectedCount(0); + this.notConfiguredEndpoint.setSleepForEmptyTest(5_000); + + this.producer.sendBody("Foo"); + + MockEndpoint.assertIsSatisfied(context); + + // the server certificate rejected by the empty trust list is kept for review + final Path rejectedCerts = pki.resolve("empty").resolve("rejected").resolve("certs"); + await().atMost(10, TimeUnit.SECONDS).until(() -> countFiles(rejectedCerts) > 0); + + assertTrue(this.recordingValidator.validations.get() > 0, "The configured certificate validator was not used"); + + // a producer of a client that cannot connect gets the reason of the last connection attempt + assertConnectionFailure(MILO_CLIENT, "The certificate of the OPC UA server cannot be validated"); + assertConnectionFailure(trustListClient("empty"), "Bad_SecurityChecksFailed"); + } + + private void assertConnectionFailure(final String clientUri, final String expectedReason) { + final Exchange exchange = this.producer.send(resolve(clientUri + "&defaultAwaitWrites=true"), + e -> e.getMessage().setBody("Bar")); + + final StringBuilder causes = new StringBuilder(); + for (Throwable cause = exchange.getException(); cause != null; cause = cause.getCause()) { + causes.append(cause).append('\n'); + } + assertTrue(causes.indexOf("No connected") >= 0, causes::toString); + assertTrue(causes.indexOf(expectedReason) >= 0, causes::toString); + } + + private static String trustListClient(final String directory) { + return MILO_CLIENT + "&trustListDirectory=RAW(" + pki.resolve(directory) + ")"; + } + + private static long countFiles(final Path directory) throws IOException { + if (!Files.isDirectory(directory)) { + return 0; + } + try (Stream files = Files.list(directory)) { + return files.count(); + } + } + + private static final class RecordingCertificateValidator implements CertificateValidator { + + private final AtomicInteger validations = new AtomicInteger(); + private final MemoryTrustListManager trustList = new MemoryTrustListManager(); + private final CertificateValidator delegate + = new DefaultClientCertificateValidator(this.trustList, new MemoryCertificateQuarantine()); + + void trust(final X509Certificate certificate) { + this.trustList.addTrustedCertificate(certificate); + } + + @Override + public void validateCertificateChain( + final List certificateChain, final String applicationUri, final String[] validHostNames) + throws UaException { + this.validations.incrementAndGet(); + this.delegate.validateCertificateChain(certificateChain, applicationUri, validHostNames); + } + } +} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/MiloClientConfigurationTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/MiloClientConfigurationTest.java new file mode 100644 index 0000000000000..68d0a6a7c4157 --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/MiloClientConfigurationTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.milo.client; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MiloClientConfigurationTest { + + private static final String ENDPOINT_URI = "opc.tcp://localhost:4840"; + + @Test + void cacheIdIsUnchangedWithoutServerCertificateTrustSettings() { + assertEquals(ENDPOINT_URI + "|1000.0", configuration().toCacheId()); + } + + @Test + void endpointsValidatingTheServerDifferentlyDoNotShareAConnection() { + final MiloClientConfiguration trustAll = configuration(); + trustAll.setTrustAllCertificates(true); + final MiloClientConfiguration trustList = configuration(); + trustList.setTrustListDirectory("pki"); + final MiloClientConfiguration otherTrustList = configuration(); + otherTrustList.setTrustListDirectory("other-pki"); + final MiloClientConfiguration validator = configuration(); + validator.setCertificateValidator(validator()); + + final Set cacheIds = new HashSet<>( + Arrays.asList( + configuration().toCacheId(), trustAll.toCacheId(), trustList.toCacheId(), otherTrustList.toCacheId(), + validator.toCacheId())); + + assertEquals(5, cacheIds.size()); + } + + @Test + void copyAndCloneKeepServerCertificateTrustSettings() { + final CertificateValidator certificateValidator = validator(); + final MiloClientConfiguration original = configuration(); + original.setTrustListDirectory("pki"); + original.setCertificateValidator(certificateValidator); + original.setTrustAllCertificates(true); + + for (final MiloClientConfiguration copy : Arrays.asList(new MiloClientConfiguration(original), original.clone())) { + assertEquals("pki", copy.getTrustListDirectory()); + assertSame(certificateValidator, copy.getCertificateValidator()); + assertTrue(copy.isTrustAllCertificates()); + } + } + + private static MiloClientConfiguration configuration() { + final MiloClientConfiguration configuration = new MiloClientConfiguration(); + configuration.setEndpointUri(ENDPOINT_URI); + return configuration; + } + + private static CertificateValidator validator() { + return (certificateChain, applicationUri, validHostNames) -> { + }; + } +} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrustTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrustTest.java new file mode 100644 index 0000000000000..a9a05de0c8e07 --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/ServerCertificateTrustTest.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.milo.client.internal; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +import org.apache.camel.component.milo.call.TestCertificateFactory; +import org.apache.camel.component.milo.client.MiloClientConfiguration; +import org.eclipse.milo.opcua.stack.core.StatusCodes; +import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.DefaultClientCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte; +import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode; +import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ServerCertificateTrustTest { + + private static X509Certificate selfSignedServerCertificate; + private static TestPki pki; + + @TempDir + Path trustListDirectory; + + @BeforeAll + static void createCertificates() throws Exception { + final TestCertificateFactory certificateFactory = new TestCertificateFactory(); + final KeyPair keyPair = certificateFactory.createRsaSha256KeyPair(); + selfSignedServerCertificate = certificateFactory.createRsaSha256CertificateChain(keyPair)[0]; + pki = new TestPki("Camel Milo Test CA"); + } + + @Test + void securedEndpointWithoutTrustConfigurationIsRefused() { + final ServerCertificateTrust trust = new ServerCertificateTrust(new MiloClientConfiguration()); + + final ServerCertificateTrust.NotConfiguredException e = assertThrows( + ServerCertificateTrust.NotConfiguredException.class, + () -> trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256))); + + assertTrue(e.getMessage().contains("trustListDirectory"), e.getMessage()); + assertTrue(e.getMessage().contains("certificateValidator"), e.getMessage()); + assertTrue(e.getMessage().contains("trustAllCertificates"), e.getMessage()); + } + + @Test + void endpointWithoutSecurityDoesNotNeedTrustConfiguration() throws Exception { + final ServerCertificateTrust trust = new ServerCertificateTrust(new MiloClientConfiguration()); + + assertInstanceOf(CertificateValidator.InsecureCertificateValidator.class, + trust.validatorFor(endpoint(SecurityPolicy.None))); + } + + @Test + void trustAllCertificatesAcceptsAnyServerCertificate() throws Exception { + final MiloClientConfiguration configuration = new MiloClientConfiguration(); + configuration.setTrustAllCertificates(true); + final ServerCertificateTrust trust = new ServerCertificateTrust(configuration); + + final CertificateValidator validator = trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + assertDoesNotThrow(() -> validator.validateCertificateChain(List.of(selfSignedServerCertificate), null, null)); + } + + @Test + void certificateValidatorIsUsedBeforeOtherOptions() throws Exception { + final CertificateValidator custom = (certificateChain, applicationUri, validHostNames) -> { + }; + final MiloClientConfiguration configuration = new MiloClientConfiguration(); + configuration.setCertificateValidator(custom); + configuration.setTrustListDirectory(this.trustListDirectory.toString()); + configuration.setTrustAllCertificates(true); + + try (ServerCertificateTrust trust = new ServerCertificateTrust(configuration)) { + assertSame(custom, trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256))); + } + } + + @Test + void trustListAcceptsTrustedServerCertificate() throws Exception { + trust(selfSignedServerCertificate); + + try (ServerCertificateTrust trust = trustListTrust()) { + final CertificateValidator validator = trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + assertInstanceOf(DefaultClientCertificateValidator.class, validator); + assertDoesNotThrow( + () -> validator.validateCertificateChain(List.of(selfSignedServerCertificate), null, null)); + // a reconnect uses the same trust list + assertSame(validator, trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256))); + } + } + + @Test + void trustListRejectsAndKeepsUntrustedServerCertificate() throws Exception { + // the trust list holds a CA that did not issue the server certificate + trust(new TestPki("Another Test CA").certificate()); + + try (ServerCertificateTrust trust = trustListTrust()) { + final CertificateValidator validator = trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + // Milo reports a certificate without a chain up to a trusted certificate as a failed security check + final UaException e = assertThrows(UaException.class, + () -> validator.validateCertificateChain(List.of(selfSignedServerCertificate), null, null)); + assertEquals(StatusCodes.Bad_SecurityChecksFailed, e.getStatusCode().getValue(), e.toString()); + } + + assertTrue(Files.isDirectory(this.trustListDirectory.resolve("trusted").resolve("certs"))); + assertTrue(Files.isDirectory(this.trustListDirectory.resolve("issuer").resolve("certs"))); + assertEquals(1, countRejected()); + } + + @Test + void trustListAcceptsServerCertificateOfCaWithRevocationList() throws Exception { + final X509Certificate serverCertificate = validServerCertificate(); + trust(pki.certificate()); + trustRevocationList(pki.revocationList()); + + try (ServerCertificateTrust trust = trustListTrust()) { + final CertificateValidator validator = trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + assertDoesNotThrow(() -> validator.validateCertificateChain( + List.of(serverCertificate, pki.certificate()), TestPki.SERVER_APPLICATION_URI, null)); + } + } + + @Test + void trustListRefusesServerCertificateOfCaWithoutRevocationList() throws Exception { + final X509Certificate serverCertificate = validServerCertificate(); + trust(pki.certificate()); + + assertRefused(List.of(serverCertificate, pki.certificate()), TestPki.SERVER_APPLICATION_URI, + StatusCodes.Bad_CertificateRevocationUnknown); + } + + @Test + void trustListRefusesRevokedServerCertificate() throws Exception { + final X509Certificate serverCertificate = validServerCertificate(); + trust(pki.certificate()); + trustRevocationList(pki.revocationList(serverCertificate)); + + assertRefused(List.of(serverCertificate, pki.certificate()), TestPki.SERVER_APPLICATION_URI, + StatusCodes.Bad_CertificateRevoked); + } + + @Test + void trustListRefusesExpiredServerCertificate() throws Exception { + final Instant now = Instant.now(); + final X509Certificate serverCertificate = TestPki.selfSignedServerCertificate( + now.minus(2, ChronoUnit.DAYS), now.minus(1, ChronoUnit.DAYS)); + trust(serverCertificate); + + assertRefused(List.of(serverCertificate), TestPki.SERVER_APPLICATION_URI, StatusCodes.Bad_CertificateTimeInvalid); + } + + @Test + void trustListRefusesServerCertificateOfAnotherApplication() throws Exception { + final Instant now = Instant.now(); + final X509Certificate serverCertificate = TestPki.selfSignedServerCertificate( + now.minus(1, ChronoUnit.HOURS), now.plus(1, ChronoUnit.HOURS)); + trust(serverCertificate); + + assertRefused(List.of(serverCertificate), "urn:camel:milo:test:other", StatusCodes.Bad_CertificateUriInvalid); + } + + @Test + void closedTrustListIsNotReused() throws Exception { + final ServerCertificateTrust trust = trustListTrust(); + trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + trust.close(); + + assertThrows(IllegalStateException.class, () -> trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256))); + } + + @Test + void certificateFailuresAreFoundInTheCauseChain() { + final ServerCertificateTrust.NotConfiguredException notConfigured + = new ServerCertificateTrust.NotConfiguredException("not configured"); + final UaException untrusted = new UaException(StatusCodes.Bad_CertificateUntrusted, "untrusted"); + + assertSame(notConfigured, + ServerCertificateTrust.findCertificateFailure(new IllegalStateException("wrapped", notConfigured))); + assertSame(untrusted, ServerCertificateTrust.findCertificateFailure(new ExecutionException(untrusted))); + assertNull(ServerCertificateTrust.findCertificateFailure(new UaException(StatusCodes.Bad_Timeout, "timeout"))); + assertNull(ServerCertificateTrust.findCertificateFailure(new IOException("connection refused"))); + } + + private void assertRefused( + final List certificateChain, final String applicationUri, final long statusCode) + throws Exception { + try (ServerCertificateTrust trust = trustListTrust()) { + final CertificateValidator validator = trust.validatorFor(endpoint(SecurityPolicy.Basic256Sha256)); + + final UaException e = assertThrows(UaException.class, + () -> validator.validateCertificateChain(certificateChain, applicationUri, null)); + assertEquals(statusCode, e.getStatusCode().getValue(), e.toString()); + } + // only a certificate that does not chain up to a trusted certificate is kept for review + assertEquals(0, countRejected()); + } + + private static X509Certificate validServerCertificate() throws Exception { + final Instant now = Instant.now(); + return pki.issueServerCertificate(now.minus(1, ChronoUnit.HOURS), now.plus(1, ChronoUnit.HOURS)); + } + + private ServerCertificateTrust trustListTrust() { + final MiloClientConfiguration configuration = new MiloClientConfiguration(); + configuration.setTrustListDirectory(this.trustListDirectory.toString()); + return new ServerCertificateTrust(configuration); + } + + private void trust(final X509Certificate certificate) throws Exception { + final Path trustedCerts = Files.createDirectories(this.trustListDirectory.resolve("trusted").resolve("certs")); + Files.write(trustedCerts.resolve(certificate.getSerialNumber() + "-" + + Integer.toHexString(certificate.hashCode()) + ".der"), + certificate.getEncoded()); + } + + private void trustRevocationList(final X509CRL revocationList) throws Exception { + final Path trustedCrl = Files.createDirectories(this.trustListDirectory.resolve("trusted").resolve("crl")); + Files.write(trustedCrl.resolve("ca.crl"), revocationList.getEncoded()); + } + + private long countRejected() throws IOException { + final Path rejectedCerts = this.trustListDirectory.resolve("rejected").resolve("certs"); + if (!Files.isDirectory(rejectedCerts)) { + return 0; + } + try (Stream rejected = Files.list(rejectedCerts)) { + return rejected.count(); + } + } + + private static EndpointDescription endpoint(final SecurityPolicy securityPolicy) { + final MessageSecurityMode securityMode + = securityPolicy == SecurityPolicy.None ? MessageSecurityMode.None : MessageSecurityMode.SignAndEncrypt; + return new EndpointDescription( + "opc.tcp://localhost:4840", null, ByteString.NULL_VALUE, securityMode, securityPolicy.getUri(), null, null, + UByte.valueOf(0)); + } +} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/TestPki.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/TestPki.java new file mode 100644 index 0000000000000..0a0cca2805707 --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/internal/TestPki.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.milo.client.internal; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PublicKey; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.CRLReason; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.X509v2CRLBuilder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +/** + * A certificate authority for tests, which issues server certificates and certificate revocation lists. + */ +final class TestPki { + + static final String SERVER_APPLICATION_URI = "urn:camel:milo:test:server"; + + private final X500Name name; + private final KeyPair keyPair; + private final X509Certificate certificate; + private long serialNumber; + + TestPki(final String commonName) throws Exception { + this.name = new X500Name("CN=" + commonName); + this.keyPair = newKeyPair(); + + final Instant now = Instant.now(); + final X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + this.name, nextSerialNumber(), Date.from(now.minus(1, ChronoUnit.DAYS)), + Date.from(now.plus(1, ChronoUnit.DAYS)), this.name, this.keyPair.getPublic()); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)); + this.certificate = sign(builder); + } + + X509Certificate certificate() { + return this.certificate; + } + + /** + * Issues a certificate for a server with the application URI {@link #SERVER_APPLICATION_URI}. + */ + X509Certificate issueServerCertificate(final Instant notBefore, final Instant notAfter) throws Exception { + final X509v3CertificateBuilder builder = serverCertificateBuilder( + this.name, nextSerialNumber(), notBefore, notAfter, newKeyPair().getPublic(), 0); + return sign(builder); + } + + /** + * Creates a self-signed certificate for a server with the application URI {@link #SERVER_APPLICATION_URI}. As OPC + * UA requires for a self-signed application certificate, it may sign certificates, since it is its own issuer. + */ + static X509Certificate selfSignedServerCertificate(final Instant notBefore, final Instant notAfter) throws Exception { + final KeyPair keyPair = newKeyPair(); + final X509v3CertificateBuilder builder = serverCertificateBuilder( + new X500Name("CN=Camel Milo Test Server"), BigInteger.ONE, notBefore, notAfter, keyPair.getPublic(), + KeyUsage.keyCertSign); + return new JcaX509CertificateConverter().getCertificate( + builder.build(new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()))); + } + + private static X509v3CertificateBuilder serverCertificateBuilder( + final X500Name issuer, final BigInteger serialNumber, final Instant notBefore, final Instant notAfter, + final PublicKey publicKey, final int additionalKeyUsage) + throws Exception { + final X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + issuer, serialNumber, Date.from(notBefore), Date.from(notAfter), + new X500Name("CN=Camel Milo Test Server"), publicKey); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + builder.addExtension(Extension.keyUsage, true, new KeyUsage( + KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment + | additionalKeyUsage)); + builder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames( + new GeneralName[] { + new GeneralName(GeneralName.uniformResourceIdentifier, SERVER_APPLICATION_URI), + new GeneralName(GeneralName.dNSName, "localhost") })); + return builder; + } + + /** + * Issues a certificate revocation list that revokes the given certificates. + */ + X509CRL revocationList(final X509Certificate... revoked) throws Exception { + final Instant now = Instant.now(); + final X509v2CRLBuilder builder = new X509v2CRLBuilder(this.name, Date.from(now.minus(1, ChronoUnit.HOURS))); + builder.setNextUpdate(Date.from(now.plus(1, ChronoUnit.DAYS))); + for (final X509Certificate certificate : revoked) { + builder.addCRLEntry(certificate.getSerialNumber(), Date.from(now.minus(1, ChronoUnit.HOURS)), + CRLReason.keyCompromise); + } + return new JcaX509CRLConverter().getCRL(builder.build(signer())); + } + + private X509Certificate sign(final X509v3CertificateBuilder builder) throws Exception { + return new JcaX509CertificateConverter().getCertificate(builder.build(signer())); + } + + private ContentSigner signer() throws Exception { + return new JcaContentSignerBuilder("SHA256withRSA").build(this.keyPair.getPrivate()); + } + + private BigInteger nextSerialNumber() { + this.serialNumber++; + return BigInteger.valueOf(this.serialNumber); + } + + private static KeyPair newKeyPair() throws Exception { + final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + return generator.generateKeyPair(); + } +} diff --git a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java index fb63bfb301cc5..4713b7842e349 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java @@ -275,7 +275,9 @@ public record SecurityOption(String category, String insecureValue) { "component:awscloudtrail", "component:awsconfig", "component:awssecretsmanager", - "component:awssecurityhub")); + "component:awssecurityhub", + "component:milobrowse", + "component:miloclient")); owners.put("trustallpackages", Set.of( "component:activemq", "component:activemq6")); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index ebcf624762ff9..433bd2487e88d 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -275,6 +275,31 @@ now read as a local date-time without a zone (`getDate()` returns a `LocalDateTi definition of an earlier message, if there was one. A calendar that defines a `TZID` an earlier message has defined differently is now read with its own definition. +=== camel-milo - the OPC UA client validates the server certificate + +When the endpoint it selects uses a security policy other than `None`, the `milo-client` and `milo-browse` components +now validate the certificate of the OPC UA server. Before, any server certificate was accepted, and no option allowed +to validate it. + +Configure the trusted certificates with the new `trustListDirectory` option: put the certificate of the server, or of +the CA that issued it, in the `trusted/certs` directory of the trust list. A server certificate that does not chain up +to a trusted certificate is written to `rejected/certs`, from where it can be moved to `trusted/certs` once it has been +checked. The trust list also refuses a server certificate outside its validity period, revoked by a revocation list of +the trust list, or whose application URI differs from the one of the server. A server certificate issued by a CA is +refused with `Bad_CertificateRevocationUnknown` when the trust list holds no revocation list of that CA: add the CRL of +the CA (an empty one is enough) to `trusted/crl` or `issuer/crl`. A custom validator can be configured with the new +`certificateValidator` option instead. + +With neither option, the client does not connect to such an endpoint. It logs a `WARN` message +`Failed to connect: The certificate of the OPC UA server cannot be validated`, followed by the endpoint and its +security policy, and tries again every 10 seconds. A producer fails with `IllegalStateException: No connected`, caused +by the same message. A server certificate refused by the trust list or by a `certificateValidator` is logged at `WARN` +as `Failed to connect, because a certificate or security check failed`. To restore the previous behaviour, set the new +`trustAllCertificates` option to `true`. This is insecure: the client then does not authenticate the server. + +The certificate that the user name and password are encrypted with is checked by the same validator. Endpoints that +use the security policy `None` are not affected. + === camel-lra - only an LRA hosted by the configured coordinator is joined The LRA saga service now joins, completes or compensates an LRA only when its URL is under the configured LRA diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloBrowseComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloBrowseComponentBuilderFactory.java index 7f9b98d8a879e..832c3c13b8cb1 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloBrowseComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloBrowseComponentBuilderFactory.java @@ -462,6 +462,25 @@ default MiloBrowseComponentBuilder sessionTimeout(java.lang.Long sessionTimeout) return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * <code>org.eclipse.milo.opcua.stack.core.security.CertificateValidator</code> type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloBrowseComponentBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -479,6 +498,52 @@ default MiloBrowseComponentBuilder password(java.lang.String password) { return this; } + + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloBrowseComponentBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloBrowseComponentBuilder trustListDirectory(java.lang.String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } + /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special @@ -541,7 +606,10 @@ protected boolean setPropertyOnComponent( case "requestTimeout": getOrCreateConfiguration((MiloBrowseComponent) component).setRequestTimeout((java.lang.Long) value); return true; case "sessionName": getOrCreateConfiguration((MiloBrowseComponent) component).setSessionName((java.lang.String) value); return true; case "sessionTimeout": getOrCreateConfiguration((MiloBrowseComponent) component).setSessionTimeout((java.lang.Long) value); return true; + case "certificateValidator": getOrCreateConfiguration((MiloBrowseComponent) component).setCertificateValidator((org.eclipse.milo.opcua.stack.core.security.CertificateValidator) value); return true; case "password": getOrCreateConfiguration((MiloBrowseComponent) component).setPassword((java.lang.String) value); return true; + case "trustAllCertificates": getOrCreateConfiguration((MiloBrowseComponent) component).setTrustAllCertificates((boolean) value); return true; + case "trustListDirectory": getOrCreateConfiguration((MiloBrowseComponent) component).setTrustListDirectory((java.lang.String) value); return true; case "username": getOrCreateConfiguration((MiloBrowseComponent) component).setUsername((java.lang.String) value); return true; default: return false; } diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloClientComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloClientComponentBuilderFactory.java index 44d22e8023a0d..6fd19bb55fc64 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloClientComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MiloClientComponentBuilderFactory.java @@ -490,6 +490,25 @@ default MiloClientComponentBuilder sessionTimeout(java.lang.Long sessionTimeout) return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * <code>org.eclipse.milo.opcua.stack.core.security.CertificateValidator</code> type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientComponentBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -507,6 +526,52 @@ default MiloClientComponentBuilder password(java.lang.String password) { return this; } + + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientComponentBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloClientComponentBuilder trustListDirectory(java.lang.String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } + /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special @@ -570,7 +635,10 @@ protected boolean setPropertyOnComponent( case "requestTimeout": getOrCreateConfiguration((MiloClientComponent) component).setRequestTimeout((java.lang.Long) value); return true; case "sessionName": getOrCreateConfiguration((MiloClientComponent) component).setSessionName((java.lang.String) value); return true; case "sessionTimeout": getOrCreateConfiguration((MiloClientComponent) component).setSessionTimeout((java.lang.Long) value); return true; + case "certificateValidator": getOrCreateConfiguration((MiloClientComponent) component).setCertificateValidator((org.eclipse.milo.opcua.stack.core.security.CertificateValidator) value); return true; case "password": getOrCreateConfiguration((MiloClientComponent) component).setPassword((java.lang.String) value); return true; + case "trustAllCertificates": getOrCreateConfiguration((MiloClientComponent) component).setTrustAllCertificates((boolean) value); return true; + case "trustListDirectory": getOrCreateConfiguration((MiloClientComponent) component).setTrustListDirectory((java.lang.String) value); return true; case "username": getOrCreateConfiguration((MiloClientComponent) component).setUsername((java.lang.String) value); return true; default: return false; } diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloBrowseEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloBrowseEndpointBuilderFactory.java index 76ff5d3c39693..2371bf904c42a 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloBrowseEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloBrowseEndpointBuilderFactory.java @@ -685,6 +685,42 @@ default MiloBrowseEndpointBuilder sessionTimeout(String sessionTimeout) { doSetProperty("sessionTimeout", sessionTimeout); return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloBrowseEndpointBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option will be converted to a + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloBrowseEndpointBuilder certificateValidator(String certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -701,6 +737,67 @@ default MiloBrowseEndpointBuilder password(String password) { doSetProperty("password", password); return this; } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloBrowseEndpointBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloBrowseEndpointBuilder trustAllCertificates(String trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloBrowseEndpointBuilder trustListDirectory(String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloClientEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloClientEndpointBuilderFactory.java index fdde075f0dce3..c86e2e103d068 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloClientEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MiloClientEndpointBuilderFactory.java @@ -754,6 +754,42 @@ default MiloClientEndpointConsumerBuilder sessionTimeout(String sessionTimeout) doSetProperty("sessionTimeout", sessionTimeout); return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointConsumerBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option will be converted to a + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointConsumerBuilder certificateValidator(String certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -770,6 +806,67 @@ default MiloClientEndpointConsumerBuilder password(String password) { doSetProperty("password", password); return this; } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointConsumerBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointConsumerBuilder trustAllCertificates(String trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloClientEndpointConsumerBuilder trustListDirectory(String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special @@ -1621,6 +1718,42 @@ default MiloClientEndpointProducerBuilder sessionTimeout(String sessionTimeout) doSetProperty("sessionTimeout", sessionTimeout); return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointProducerBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option will be converted to a + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointProducerBuilder certificateValidator(String certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -1637,6 +1770,67 @@ default MiloClientEndpointProducerBuilder password(String password) { doSetProperty("password", password); return this; } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointProducerBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointProducerBuilder trustAllCertificates(String trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloClientEndpointProducerBuilder trustListDirectory(String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special @@ -2417,6 +2611,42 @@ default MiloClientEndpointBuilder sessionTimeout(String sessionTimeout) { doSetProperty("sessionTimeout", sessionTimeout); return this; } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option is a: + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointBuilder certificateValidator(org.eclipse.milo.opcua.stack.core.security.CertificateValidator certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } + /** + * A custom validator for the certificate of the OPC UA server. When + * set, it is used instead of the trust list configured with + * trustListDirectory. Endpoints share a connection only when they use + * the same validator instance. + * + * The option will be converted to a + * org.eclipse.milo.opcua.stack.core.security.CertificateValidator type. + * + * Group: security + * + * @param certificateValidator the value to set + * @return the dsl builder + */ + default MiloClientEndpointBuilder certificateValidator(String certificateValidator) { + doSetProperty("certificateValidator", certificateValidator); + return this; + } /** * The password for authentication. Use this instead of embedding * credentials in the endpoint URI when the password contains special @@ -2433,6 +2663,67 @@ default MiloClientEndpointBuilder password(String password) { doSetProperty("password", password); return this; } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option is a: boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointBuilder trustAllCertificates(boolean trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * Whether to accept any certificate presented by the OPC UA server. + * This is insecure: the connection is encrypted, but the client does + * not authenticate the server it talks to. It only applies when neither + * certificateValidator nor trustListDirectory is configured. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: security + * + * @param trustAllCertificates the value to set + * @return the dsl builder + */ + default MiloClientEndpointBuilder trustAllCertificates(String trustAllCertificates) { + doSetProperty("trustAllCertificates", trustAllCertificates); + return this; + } + /** + * The directory holding the trust list used to validate the certificate + * of the OPC UA server, in the layout used by Eclipse Milo: trusted + * certificates in trusted/certs, CRLs of trusted certificates in + * trusted/crl, certificates of issuing CAs in issuer/certs and their + * CRLs in issuer/crl. The directories are created if they do not exist. + * Besides the chain of trust, the validity period, the revocation by a + * CRL of the trust list and the application URI of the server + * certificate are checked. A certificate issued by a CA is refused when + * the trust list holds no CRL of that CA; an empty CRL is enough. A + * server certificate that does not chain up to a trusted certificate is + * written to rejected/certs, from where it can be moved to + * trusted/certs once it has been checked. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param trustListDirectory the value to set + * @return the dsl builder + */ + default MiloClientEndpointBuilder trustListDirectory(String trustListDirectory) { + doSetProperty("trustListDirectory", trustListDirectory); + return this; + } /** * The username for authentication. Use this instead of embedding * credentials in the endpoint URI when the username contains special