Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ELY-2320] Add integrity support to FileSystemSecurityRealm #1709

Merged
merged 2 commits into from Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -153,6 +153,19 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 13006, value = "Filesystem-backed realm unable to encrypt identity")
RealmUnavailableException fileSystemRealmEncryptionFailed(@Cause Throwable cause);

@Message(id = 13007, value = "Filesystem-backed realm found an incompatible identity version. Requires at least version: %s")
RealmUnavailableException fileSystemRealmIncompatibleIdentityVersion(String expectedVersion);
@Message(id = 13007, value = "Signature for the following identity is invalid: %s.")
IntegrityException invalidIdentitySignature(String s);

@Message(id = 13008, value = "Unable to create a signature for the file: %s.")
RealmUnavailableException unableToGenerateSignature(String s);

@Message(id = 13009, value = "Unable to locate the signature element for the file: %s")
RealmUnavailableException cannotFindSignature(String s);

@Message(id = 13010, value = "Both PrivateKey and PublicKey must be defined for realm at: %s")
IllegalArgumentException invalidKeyPairArgument(String s);

@Message(id = 13011, value = "Integrity has not been enabled for the realm at: %s")
IllegalArgumentException integrityNotEnabled(String s);

}

Large diffs are not rendered by default.

Expand Up @@ -20,7 +20,9 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.util.function.Supplier;

import javax.crypto.SecretKey;
Expand All @@ -44,6 +46,8 @@ public class FileSystemSecurityRealmBuilder {
private Charset hashCharset;
private Encoding hashEncoding;
private SecretKey secretKey;
private PrivateKey privateKey;
private PublicKey publicKey;
private Supplier<Provider[]> providers;

FileSystemSecurityRealmBuilder() {
Expand Down Expand Up @@ -132,12 +136,42 @@ public FileSystemSecurityRealmBuilder setSecretKey(final SecretKey secretKey) {
return this;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this method moved? Just wondering why it appears here.

/**
* Set the providers to be used by the realm.
*
* @param providers the provider to be used (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setProviders(final Supplier<Provider[]> providers) {
Assert.checkNotNullParam("providers", providers);
this.providers = providers;
return this;
}

/**
* Set the PrivateKey to be used by the realm.
*
* @param privateKey the asymmetric PrivateKey used to sign the identity files used for file integrity (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setPrivateKey(final PrivateKey privateKey) {
Assert.checkNotNullParam("privateKey", privateKey);
this.privateKey = privateKey;
return this;
}

/**
* Set the PublicKey to be used by the realm.
*
* @param publicKey the asymmetric PublicKey used to verify the identity files used for file integrity (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setPublicKey(final PublicKey publicKey) {
Assert.checkNotNullParam("publicKey", publicKey);
this.publicKey = publicKey;
return this;
}

/**
* Builds a new {@link FileSystemSecurityRealm} instance based on configuration defined for this {@link FileSystemSecurityRealmBuilder} instance.
*
Expand All @@ -154,6 +188,10 @@ public FileSystemSecurityRealm build() {
if (hashCharset == null) {
hashCharset = StandardCharsets.UTF_8;
}
return new FileSystemSecurityRealm(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, providers, secretKey);
if (privateKey == null ^ publicKey == null) {
throw ElytronMessages.log.invalidKeyPairArgument(root.toString());
}

return new FileSystemSecurityRealm(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, providers, secretKey, privateKey, publicKey);
}
}
@@ -0,0 +1,71 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wildfly.security.auth.realm;

import java.io.IOException;

/**
* Exception to indicate a general failure related to the Integrity Verification of the Filesystem Realm.
*
* @author <a href="mailto:araskar@redhat.com">Ashpan Raskar</a>
*/
public class IntegrityException extends IOException {


private static final long serialVersionUID = 8889252552074803941L;

/**
* Constructs a new {@code IntegrityException} instance. The message is left blank ({@code null}), and no
* cause is specified.
*/
public IntegrityException() {
}

/**
* Constructs a new {@code IntegrityException} instance with an initial message. No cause is specified.
*
* @param msg the message
*/
public IntegrityException(final String msg) {
super(msg);
}

/**
* Constructs a new {@code IntegrityException} instance with an initial cause. If a non-{@code null} cause
* is specified, its message is used to initialize the message of this {@code IntegrityException}; otherwise
* the message is left blank ({@code null}).
*
* @param cause the cause
*/
public IntegrityException(final Throwable cause) {
super(cause);
}

/**
* Constructs a new {@code IntegrityException} instance with an initial message and cause.
*
* @param msg the message
* @param cause the cause
*/
public IntegrityException(final String msg, final Throwable cause) {
super(msg, cause);
}

}

152 changes: 152 additions & 0 deletions auth/realm/base/src/main/resources/schema/elytron-identity-1_2.xsd
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding a new schema file, it's good to first copy/paste the existing file and just update the version and include this in a commit. Then introduce the new element(s) in a follow-up commit. This makes it easier to see the specific changes introduced. We also tend to create a separate ELY issue to track adding the new client schema version.


<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2022 Red Hat, Inc., and individual contributors
~ as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:elytron:identity:1.2"
xmlns="urn:elytron:identity:1.2"
xmlns:xmldsig="http://www.w3.org/2000/09/xmldsig#"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.2">

<!-- File-backed realm elements -->

<xsd:element name="identity" type="identity-type"/>
<xsd:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd" />

<xsd:complexType name="identity-type">
<xsd:all minOccurs="1" maxOccurs="1">
<xsd:element name="principal" type="name-type" minOccurs="0" maxOccurs="1"/>
<xsd:element name="credentials" type="credentials-type" minOccurs="0" maxOccurs="1"/>
<xsd:element name="attributes" type="attributes-type" minOccurs="0" maxOccurs="1"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature element should be added here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an example here that shows how to import and make use of an element from another schema:

https://github.com/wildfly/wildfly-core/blob/7b9da5eced8a006f827769ede7c5847a8290370b/server/src/main/resources/schema/wildfly-config_18_0.xsd#L274

<xsd:element name="Signature" type="xmldsig:SignatureType" minOccurs="0" maxOccurs="1"/>
</xsd:all>
</xsd:complexType>

<xsd:complexType name="credentials-type">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="password" type="credential-type"/>
<xsd:element name="otp" type="otp-credential-type"/>
<xsd:element name="public-key" type="credential-type"/>
<xsd:element name="certificate" type="credential-type"/>
</xsd:choice>
</xsd:complexType>

<xsd:complexType name="attributes-type">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="attribute" type="attribute-type"/>
</xsd:choice>
</xsd:complexType>

<xsd:complexType name="credential-type">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="algorithm" type="xsd:string" use="optional"/>
<xsd:attribute name="format" type="xsd:string" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>

<xsd:complexType name="otp-credential-type">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="algorithm" type="xsd:string" use="optional"/>
<xsd:attribute name="hash" type="xsd:string" use="optional"/>
<xsd:attribute name="seed" type="xsd:string" use="optional"/>
<xsd:attribute name="sequence" type="xsd:string" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>

<xsd:complexType name="attribute-type">
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>

<!-- Common types -->

<xsd:complexType name="empty-type"/>

<xsd:complexType name="name-type">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>

<xsd:complexType name="abstract-type-type">
<xsd:attribute name="name" type="xsd:string" use="optional"/>
<xsd:attribute name="authority" type="xsd:string" use="optional"/>
</xsd:complexType>

<xsd:complexType name="optional-name-type">
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="selector-type">
<xsd:attribute name="selector" type="xsd:string" use="required"/>
</xsd:complexType>

<xsd:complexType name="module-ref-type">
<xsd:attribute name="module-name" type="xsd:string" use="optional"/>
</xsd:complexType>

<xsd:complexType name="port-number-type">
<xsd:attribute name="number" type="port-number-simple-type" use="required"/>
</xsd:complexType>

<xsd:simpleType name="port-number-simple-type">
<xsd:restriction base="xsd:positiveInteger">
<xsd:minInclusive value="1"/>
<xsd:maxInclusive value="65535"/>
</xsd:restriction>
</xsd:simpleType>

<xsd:complexType name="regex-substitution-type">
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
<xsd:attribute name="replacement" type="xsd:string" use="required"/>
</xsd:complexType>

<xsd:complexType name="names-list-type">
<xsd:attribute name="names" type="names-list-simple-type" use="required"/>
</xsd:complexType>

<xsd:simpleType name="names-list-simple-type">
<xsd:list itemType="xsd:string"/>
</xsd:simpleType>

<xsd:complexType name="uri-type">
<xsd:attribute name="uri" type="xsd:anyURI" use="required"/>
</xsd:complexType>

<xsd:complexType name="clear-password-type">
<xsd:attribute name="password" type="xsd:string" use="required"/>
</xsd:complexType>

<xsd:complexType name="properties-type">
<xsd:sequence>
<xsd:element name="property" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="value" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>


</xsd:schema>
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -103,6 +103,7 @@
<version.org.keycloak>17.0.0</version.org.keycloak>
<version.io.rest-assured>4.3.3</version.io.rest-assured>
<version.net.sourceforge.htmlunit.htmlunit>2.40.0</version.net.sourceforge.htmlunit.htmlunit>
<version.org.apache.santuario>2.3.0</version.org.apache.santuario>

<test.level>INFO</test.level>
<!-- Checkstyle configuration -->
Expand Down Expand Up @@ -1048,7 +1049,6 @@
<version>${version.org.bitbucket.b_c.jose4j}</version>
</dependency>


<!--
Test Scope Only
-->
Expand Down