kafka ScramMessages 源码

  • 2022-10-20
  • 浏览 (426)

kafka ScramMessages 代码

文件路径:/clients/src/main/java/org/apache/kafka/common/security/scram/internals/ScramMessages.java

/*
 * 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.kafka.common.security.scram.internals;

import org.apache.kafka.common.utils.Utils;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.security.sasl.SaslException;

/**
 * SCRAM request/response message creation and parsing based on
 * <a href="https://tools.ietf.org/html/rfc5802">RFC 5802</a>
 *
 */
public class ScramMessages {

    static abstract class AbstractScramMessage {

        static final String ALPHA = "[A-Za-z]+";
        static final String VALUE_SAFE = "[\\x01-\\x7F&&[^=,]]+";
        static final String VALUE = "[\\x01-\\x7F&&[^,]]+";
        static final String PRINTABLE = "[\\x21-\\x7E&&[^,]]+";
        static final String SASLNAME = "(?:[\\x01-\\x7F&&[^=,]]|=2C|=3D)+";
        static final String BASE64_CHAR = "[a-zA-Z0-9/+]";
        static final String BASE64 = String.format("(?:%s{4})*(?:%s{3}=|%s{2}==)?", BASE64_CHAR, BASE64_CHAR, BASE64_CHAR);
        static final String RESERVED = String.format("(m=%s,)?", VALUE);
        static final String EXTENSIONS = String.format("(,%s=%s)*", ALPHA, VALUE);

        abstract String toMessage();

        public byte[] toBytes() {
            return toMessage().getBytes(StandardCharsets.UTF_8);
        }

        protected String toMessage(byte[] messageBytes) {
            return new String(messageBytes, StandardCharsets.UTF_8);
        }
    }

    /**
     * Format:
     *     gs2-header [reserved-mext ","] username "," nonce ["," extensions]
     * Limitations:
     *     Only gs2-header "n" is supported.
     *     Extensions are ignored.
     *
     */
    public static class ClientFirstMessage extends AbstractScramMessage {
        private static final Pattern PATTERN = Pattern.compile(String.format(
                "n,(a=(?<authzid>%s))?,%sn=(?<saslname>%s),r=(?<nonce>%s)(?<extensions>%s)",
                SASLNAME,
                RESERVED,
                SASLNAME,
                PRINTABLE,
                EXTENSIONS));


        private final String saslName;
        private final String nonce;
        private final String authorizationId;
        private final ScramExtensions extensions;
        public ClientFirstMessage(byte[] messageBytes) throws SaslException {
            String message = toMessage(messageBytes);
            Matcher matcher = PATTERN.matcher(message);
            if (!matcher.matches())
                throw new SaslException("Invalid SCRAM client first message format: " + message);
            String authzid = matcher.group("authzid");
            this.authorizationId = authzid != null ? authzid : "";
            this.saslName = matcher.group("saslname");
            this.nonce = matcher.group("nonce");
            String extString = matcher.group("extensions");

            this.extensions = extString.startsWith(",") ? new ScramExtensions(extString.substring(1)) : new ScramExtensions();
        }
        public ClientFirstMessage(String saslName, String nonce, Map<String, String> extensions) {
            this.saslName = saslName;
            this.nonce = nonce;
            this.extensions = new ScramExtensions(extensions);
            this.authorizationId = ""; // Optional authzid not specified in gs2-header
        }
        public String saslName() {
            return saslName;
        }
        public String nonce() {
            return nonce;
        }
        public String authorizationId() {
            return authorizationId;
        }
        public String gs2Header() {
            return "n," + authorizationId + ",";
        }
        public ScramExtensions extensions() {
            return extensions;
        }

        public String clientFirstMessageBare() {
            String extensionStr = Utils.mkString(extensions.map(), "", "", "=", ",");

            if (extensionStr.isEmpty())
                return String.format("n=%s,r=%s", saslName, nonce);
            else
                return String.format("n=%s,r=%s,%s", saslName, nonce, extensionStr);
        }
        String toMessage() {
            return gs2Header() + clientFirstMessageBare();
        }
    }

    /**
     * Format:
     *     [reserved-mext ","] nonce "," salt "," iteration-count ["," extensions]
     * Limitations:
     *     Extensions are ignored.
     *
     */
    public static class ServerFirstMessage extends AbstractScramMessage {
        private static final Pattern PATTERN = Pattern.compile(String.format(
                "%sr=(?<nonce>%s),s=(?<salt>%s),i=(?<iterations>[0-9]+)%s",
                RESERVED,
                PRINTABLE,
                BASE64,
                EXTENSIONS));

        private final String nonce;
        private final byte[] salt;
        private final int iterations;
        public ServerFirstMessage(byte[] messageBytes) throws SaslException {
            String message = toMessage(messageBytes);
            Matcher matcher = PATTERN.matcher(message);
            if (!matcher.matches())
                throw new SaslException("Invalid SCRAM server first message format: " + message);
            try {
                this.iterations = Integer.parseInt(matcher.group("iterations"));
                if (this.iterations <= 0)
                    throw new SaslException("Invalid SCRAM server first message format: invalid iterations " + iterations);
            } catch (NumberFormatException e) {
                throw new SaslException("Invalid SCRAM server first message format: invalid iterations");
            }
            this.nonce = matcher.group("nonce");
            String salt = matcher.group("salt");
            this.salt = Base64.getDecoder().decode(salt);
        }
        public ServerFirstMessage(String clientNonce, String serverNonce, byte[] salt, int iterations) {
            this.nonce = clientNonce + serverNonce;
            this.salt = salt;
            this.iterations = iterations;
        }
        public String nonce() {
            return nonce;
        }
        public byte[] salt() {
            return salt;
        }
        public int iterations() {
            return iterations;
        }
        String toMessage() {
            return String.format("r=%s,s=%s,i=%d", nonce, Base64.getEncoder().encodeToString(salt), iterations);
        }
    }
    /**
     * Format:
     *     channel-binding "," nonce ["," extensions]"," proof
     * Limitations:
     *     Extensions are ignored.
     *
     */
    public static class ClientFinalMessage extends AbstractScramMessage {
        private static final Pattern PATTERN = Pattern.compile(String.format(
                "c=(?<channel>%s),r=(?<nonce>%s)%s,p=(?<proof>%s)",
                BASE64,
                PRINTABLE,
                EXTENSIONS,
                BASE64));

        private final byte[] channelBinding;
        private final String nonce;
        private byte[] proof;
        public ClientFinalMessage(byte[] messageBytes) throws SaslException {
            String message = toMessage(messageBytes);
            Matcher matcher = PATTERN.matcher(message);
            if (!matcher.matches())
                throw new SaslException("Invalid SCRAM client final message format: " + message);

            this.channelBinding = Base64.getDecoder().decode(matcher.group("channel"));
            this.nonce = matcher.group("nonce");
            this.proof = Base64.getDecoder().decode(matcher.group("proof"));
        }
        public ClientFinalMessage(byte[] channelBinding, String nonce) {
            this.channelBinding = channelBinding;
            this.nonce = nonce;
        }
        public byte[] channelBinding() {
            return channelBinding;
        }
        public String nonce() {
            return nonce;
        }
        public byte[] proof() {
            return proof;
        }
        public void proof(byte[] proof) {
            this.proof = proof;
        }
        public String clientFinalMessageWithoutProof() {
            return String.format("c=%s,r=%s",
                    Base64.getEncoder().encodeToString(channelBinding),
                    nonce);
        }
        String toMessage() {
            return String.format("%s,p=%s",
                    clientFinalMessageWithoutProof(),
                    Base64.getEncoder().encodeToString(proof));
        }
    }
    /**
     * Format:
     *     ("e=" server-error-value | "v=" base64_server_signature) ["," extensions]
     * Limitations:
     *     Extensions are ignored.
     *
     */
    public static class ServerFinalMessage extends AbstractScramMessage {
        private static final Pattern PATTERN = Pattern.compile(String.format(
                "(?:e=(?<error>%s))|(?:v=(?<signature>%s))%s",
                VALUE_SAFE,
                BASE64,
                EXTENSIONS));

        private final String error;
        private final byte[] serverSignature;
        public ServerFinalMessage(byte[] messageBytes) throws SaslException {
            String message = toMessage(messageBytes);
            Matcher matcher = PATTERN.matcher(message);
            if (!matcher.matches())
                throw new SaslException("Invalid SCRAM server final message format: " + message);
            String error = null;
            try {
                error = matcher.group("error");
            } catch (IllegalArgumentException e) {
                // ignore
            }
            if (error == null) {
                this.serverSignature = Base64.getDecoder().decode(matcher.group("signature"));
                this.error = null;
            } else {
                this.serverSignature = null;
                this.error = error;
            }
        }
        public ServerFinalMessage(String error, byte[] serverSignature) {
            this.error = error;
            this.serverSignature = serverSignature;
        }
        public String error() {
            return error;
        }
        public byte[] serverSignature() {
            return serverSignature;
        }
        String toMessage() {
            if (error != null)
                return "e=" + error;
            else
                return "v=" + Base64.getEncoder().encodeToString(serverSignature);
        }
    }
}

相关信息

kafka 源码目录

相关文章

kafka ScramCredentialUtils 源码

kafka ScramExtensions 源码

kafka ScramFormatter 源码

kafka ScramMechanism 源码

kafka ScramSaslClient 源码

kafka ScramSaslClientProvider 源码

kafka ScramSaslServer 源码

kafka ScramSaslServerProvider 源码

kafka ScramServerCallbackHandler 源码

0  赞