pax_global_header00006660000000000000000000000064131173316230014512gustar00rootroot0000000000000052 comment=e9ac8f3ef2b47b21779a832b1c24ab76e48e80f6 scram-1.0.0-beta.2/000077500000000000000000000000001311733162300136665ustar00rootroot00000000000000scram-1.0.0-beta.2/.gitignore000066400000000000000000000001511311733162300156530ustar00rootroot00000000000000.idea/ target/ *.iml nbactions.xml nb-configuration.xml /bin/ .project .classpath .settings/ hs_err*.log scram-1.0.0-beta.2/.travis.yml000066400000000000000000000001301311733162300157710ustar00rootroot00000000000000dist: trusty language: java script: mvn -Psafer -B -e -T 1C verify jdk: - openjdk8 scram-1.0.0-beta.2/LICENSE000066400000000000000000000023551311733162300147000ustar00rootroot00000000000000Copyright 2017, OnGres. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. scram-1.0.0-beta.2/README.md000066400000000000000000000044531311733162300151530ustar00rootroot00000000000000# SCRAM Java Implementation [![Build Status](https://travis-ci.org/ongres/scram.svg?branch=master)](https://travis-ci.org/ongres/scram) ## Overview SCRAM (Salted Challenge Response Authentication Mechanism) is part of the family of Simple Authentication and Security Layer ([SASL, RFC 4422](https://tools.ietf.org/html/rfc4422)) authentication mechanisms. It is described as part of [RFC 5802](https://tools.ietf.org/html/rfc5802) and [RFC7677](https://tools.ietf.org/html/rfc7677). This project will serve for the basis of [PostgreSQL's](https://www.postgresql.org) [JDBC](https://jdbc.postgresql.org/) driver SCRAM support (coming in PostgreSQL 10). The code is licensed under the BSD "Simplified 2 Clause" license (see [LICENSE](LICENSE)). ## Goals This project aims to provide a complete clean-room implementation of SCRAM. It is written in Java and provided in a modular, re-usable way, independent of other software or programs. Current functionality includes: * [Common infrastructure](common) for building both client and server SCRAM implementations. * A [Client API](client) for using SCRAM as a client. * Support for both SHA-1 and SHA-256. * Basic support for channel binding. * No runtime external dependencies. * Well tested (+75 tests). Current limitations: * SASLPrep is not implemented yet. * Server API and integration tests will be added soon. ## How to use the client API Please read [Client's README.md](client). Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/client.svg?label=client)](http://javadoc.io/doc/com.ongres.scram/client) ## Common API 'common' is the module that contains code common to both client and server SCRAM projects. If you with to develop either a client or server API, you may very well build on top of this API. Import maven dependency: com.ongres.scram common and check the Javadoc: [![Javadocs](http://javadoc.io/badge/com.ongres.scram/common.svg)](http://javadoc.io/doc/com.ongres.scram/common) ## Contributing Please submit [Push Requests](https://github.com/ongres/scram) for code contributions. Make sure to compile with `mvn -Psafer` before submitting a PR. Releases (on the master branch only) must be verified with: mvn -Psafer -Pmaster-branch scram-1.0.0-beta.2/client/000077500000000000000000000000001311733162300151445ustar00rootroot00000000000000scram-1.0.0-beta.2/client/README.md000066400000000000000000000070301311733162300164230ustar00rootroot00000000000000# SCRAM Client API [![Javadocs](http://javadoc.io/badge/com.ongres.scram/client.svg?label=client)](http://javadoc.io/doc/com.ongres.scram/client) For general description, please refer to [the main README.md](https://github.com/ongres/scram). ## How to use the client API 1. Add Maven (or equivalent) dependencies : ```xml com.ongres.scram client VERSION ``` 2. Get a ```ScramClient```. A ```ScramClient``` can be configured with several parameters, and matches a given server. From the client, several ```ScramSession```s can be created, for potentially different users. Under certain conditions (read Javadoc) it is thread safe. A simple example could be: ```java ScramClient scramClient = ScramClient .channelBinding(ChannelBinding.NO) .stringPreparation(NO_PREPARATION) .serverMechanisms("SCRAM-SHA-1") .setup(); ``` More configuration methods and options are available, as shown below: ```java ScramClient scramClient = ScramClient .channelBinding(ChannelBinding.YES) .stringPreparation(NO_PREPARATION) .serverMechanisms("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS") .nonceSupplier(() -> generateNonce()) .secureRandomAlgorithmProvider("algorithm", "provider") .setup(); ``` 3. For each authentication round and/or user, create a ```ScramSession``` and get the client-first-message: ```java ScramSession scramSession = scramClient.scramSession("user"); scramSession.clientFirstMessage() ``` 4. Receive the server-first-message: ```java ScramSession.ServerFirstProcessor serverFirst = scramSession.receiveServerFirstMessage(message); // Read the salt and iterations: serverFirst.getSalt() serverFirst.getIteration() ``` 5. Generate the client-last-message: ```java ScramSession.ClientFinalProcessor clientFinal = serverFirst.finalMessagesHandler("password"); clientFinal.clientFinalMessage() ``` 6. Receive the server-last-message, check if is valid or error, etc: ```java ScramSession.ServerFinalProcessor serverFinal = clientFinal.receiveServerFinalMessage(message); // Methods to check if it is error, get error, verify signature serverFinal.isError() serverFinal.getErrorMessage() serverFinal.verifyServerSignature() ``` ## Example The following example runs a full SCRAM session, using the RFC's data as an example. ```java ScramClient scramClient = ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") .nonceSupplier(() -> "fyko+d2lbbFgONRv9qkxdawL") .setup(); ScramSession scramSession = scramClient.scramSession("user"); System.out.println(scramSession.clientFirstMessage()); ScramSession.ServerFirstProcessor serverFirstProcessor = scramSession.receiveServerFirstMessage( "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096" ); System.out.println("Salt: " + serverFirstProcessor.getSalt() + ", i: " + serverFirstProcessor.getIteration()); ScramSession.ClientFinalProcessor clientFinalProcessor = serverFirstProcessor.clientFinalProcessor("pencil"); System.out.println(clientFinalProcessor.clientFinalMessage()); ScramSession.ServerFinalProcessor serverFinalProcessor = clientFinalProcessor.receiveServerFinalMessage("v=rmF9pqV8S7suAoZWja4dJRkFsKQ="); if(serverFinalProcessor.isError()) { // throw authentication exception } System.out.println(serverFinalProcessor.verifyServerSignature()); ``` scram-1.0.0-beta.2/client/pom.xml000066400000000000000000000075071311733162300164720ustar00rootroot00000000000000 4.0.0 parent com.ongres.scram 1.0.0-beta.2 client SCRAM - client com.ongres.scram common ${project.version} junit junit test com.ongres.scram common ${project.version} test test-jar javadoc.io-links !maven.javadoc.skip org.apache.maven.plugins maven-dependency-plugin 2.8 unpack-javadoc package unpack ${project.groupId} common javadoc ${project.version} false ${project.build.directory}/common-javadoc org.apache.maven.plugins maven-javadoc-plugin http://static.javadoc.io/${project.groupId}/common/${project.version} ${project.build.directory}/common-javadoc scram-1.0.0-beta.2/client/src/000077500000000000000000000000001311733162300157335ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/000077500000000000000000000000001311733162300166575ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/000077500000000000000000000000001311733162300176005ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/com/000077500000000000000000000000001311733162300203565ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/com/ongres/000077500000000000000000000000001311733162300216535ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/com/ongres/scram/000077500000000000000000000000001311733162300227605ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/com/ongres/scram/client/000077500000000000000000000000001311733162300242365ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/main/java/com/ongres/scram/client/ScramClient.java000066400000000000000000000434531311733162300273160ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.client; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.ScramMechanisms; import com.ongres.scram.common.gssapi.Gs2CbindFlag; import com.ongres.scram.common.stringprep.StringPreparation; import com.ongres.scram.common.util.CryptoUtil; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; import static com.ongres.scram.common.util.Preconditions.*; /** * A class that can be parametrized to generate {@link ScramSession}s. * This class supports the channel binding and string preparation mechanisms that are provided by module scram-common. * * The class is fully configurable, including options to selected the desired channel binding, * automatically pick the best client SCRAM mechanism based on those supported (advertised) by the server, * selecting an externally-provided SecureRandom instance or an external nonceProvider, or choosing the nonce length. * * This class is thread-safe if the two following conditions are met: * * So this class, once instantiated via the {@link Builder#setup()}} method, can serve for multiple users and * authentications. */ public class ScramClient { /** * Length (in characters, bytes) of the nonce generated by default (if no nonce supplier is provided) */ public static final int DEFAULT_NONCE_LENGTH = 24; /** * Select whether this client will support channel binding or not */ public enum ChannelBinding { /** * Don't use channel binding. Server must support at least one non-channel binding mechanism. */ NO(Gs2CbindFlag.CLIENT_NOT), /** * Force use of channel binding. Server must support at least one channel binding mechanism. * Channel binding data will need to be provided as part of the ClientFirstMessage. */ YES(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED), /** * Channel binding is preferred. Non-channel binding mechanisms will be used if either the server does not * support channel binding, or no channel binding data is provided as part of the ClientFirstMessage */ IF_SERVER_SUPPORTS_IT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT) ; private final Gs2CbindFlag gs2CbindFlag; ChannelBinding(Gs2CbindFlag gs2CbindFlag) { this.gs2CbindFlag = gs2CbindFlag; } public Gs2CbindFlag gs2CbindFlag() { return gs2CbindFlag; } } private final ChannelBinding channelBinding; private final StringPreparation stringPreparation; private final ScramMechanism scramMechanism; private final SecureRandom secureRandom; private final Supplier nonceSupplier; private ScramClient( ChannelBinding channelBinding, StringPreparation stringPreparation, Optional nonChannelBindingMechanism, Optional channelBindingMechanism, SecureRandom secureRandom, Supplier nonceSupplier ) { assert null != channelBinding : "channelBinding"; assert null != stringPreparation : "stringPreparation"; assert nonChannelBindingMechanism.isPresent() || channelBindingMechanism.isPresent() : "Either a channel-binding or a non-binding mechanism must be present"; assert null != secureRandom : "secureRandom"; assert null != nonceSupplier : "nonceSupplier"; this.channelBinding = channelBinding; this.stringPreparation = stringPreparation; this.scramMechanism = nonChannelBindingMechanism.orElseGet(() -> channelBindingMechanism.get()); this.secureRandom = secureRandom; this.nonceSupplier = nonceSupplier; } /** * Selects for the client whether to use channel binding. * Refer to {@link ChannelBinding} documentation for the description of the possible values. * @param channelBinding The channel binding setting * @return The next step in the chain (PreBuilder1). * @throws IllegalArgumentException If channelBinding is null */ public static PreBuilder1 channelBinding(ChannelBinding channelBinding) throws IllegalArgumentException { return new PreBuilder1(checkNotNull(channelBinding, "channelBinding")); } /** * This class is not meant to be used directly. * Use {@link ScramClient#channelBinding(ChannelBinding)} instead. */ public static class PreBuilder1 { protected final ChannelBinding channelBinding; private PreBuilder1(ChannelBinding channelBinding) { this.channelBinding = channelBinding; } /** * Selects the string preparation algorithm to use by the client. * @param stringPreparation The string preparation algorithm * @throws IllegalArgumentException If stringPreparation is null */ public PreBuilder2 stringPreparation(StringPreparation stringPreparation) throws IllegalArgumentException { return new PreBuilder2(channelBinding, checkNotNull(stringPreparation, "stringPreparation")); } } /** * This class is not meant to be used directly. * Use {@link ScramClient#channelBinding(ChannelBinding)}.{#stringPreparation(StringPreparation)} instead. */ public static class PreBuilder2 extends PreBuilder1 { protected final StringPreparation stringPreparation; protected Optional nonChannelBindingMechanism = Optional.empty(); protected Optional channelBindingMechanism = Optional.empty(); private PreBuilder2(ChannelBinding channelBinding, StringPreparation stringPreparation) { super(channelBinding); this.stringPreparation = stringPreparation; } /** * Inform the client of the SCRAM mechanisms supported by the server. * Based on this list, the channel binding settings previously specified, * and the relative strength of the supported SCRAM mechanisms for this client, * the client will have enough data to select which mechanism to use for future interactions with the server. * All names provided here need to be standar IANA Registry names for SCRAM mechanisms, or will be ignored. * * @see * SASL SCRAM Family Mechanisms * * @param serverMechanisms One or more IANA-registered SCRAM mechanism names, as advertised by the server * @throws IllegalArgumentException If no server mechanisms are provided */ public Builder selectMechanismBasedOnServerAdvertised(String... serverMechanisms) { checkArgument(null != serverMechanisms && serverMechanisms.length > 0, "serverMechanisms"); nonChannelBindingMechanism = ScramMechanisms.selectMatchingMechanism(false, serverMechanisms); if(channelBinding == ChannelBinding.NO && ! nonChannelBindingMechanism.isPresent()) { throw new IllegalArgumentException("Server does not support non channel binding mechanisms"); } channelBindingMechanism = ScramMechanisms.selectMatchingMechanism(true, serverMechanisms); if(channelBinding == ChannelBinding.YES && ! channelBindingMechanism.isPresent()) { throw new IllegalArgumentException("Server does not support channel binding mechanisms"); } if(! (channelBindingMechanism.isPresent() || nonChannelBindingMechanism.isPresent())) { throw new IllegalArgumentException("There are no matching mechanisms between client and server"); } return new Builder(channelBinding, stringPreparation, nonChannelBindingMechanism, channelBindingMechanism); } /** * Inform the client of the SCRAM mechanisms supported by the server. * Calls {@link Builder#selectMechanismBasedOnServerAdvertised(String...)} * with the results of splitting the received comma-separated values. * @param serverMechanismsCsv A CSV (Comma-Separated Values) String, containining all the SCRAM mechanisms * supported by the server * @throws IllegalArgumentException If selectMechanismBasedOnServerAdvertisedCsv is null */ public Builder selectMechanismBasedOnServerAdvertisedCsv(String serverMechanismsCsv) throws IllegalArgumentException { return selectMechanismBasedOnServerAdvertised( checkNotNull(serverMechanismsCsv, "serverMechanismsCsv").split(",") ); } /** * Select a fixed client mechanism. It must be compatible with the channel binding selection previously * performed. If automatic selection based on server advertised mechanisms is preferred, please use methods * {@link Builder#selectMechanismBasedOnServerAdvertised(String...)} or * {@link Builder#selectMechanismBasedOnServerAdvertisedCsv(String)}. * @param scramMechanism The selected scram mechanism * @throws IllegalArgumentException If the selected mechanism is null or not compatible with the prior * channel binding selection, * or channel binding selection is dependent on the server advertised methods */ public Builder selectClientMechanism(ScramMechanism scramMechanism) { checkNotNull(scramMechanism, "scramMechanism"); if(channelBinding == ChannelBinding.IF_SERVER_SUPPORTS_IT) { throw new IllegalArgumentException( "If server selection is considered, no direct client selection should be performed" ); } if( channelBinding == ChannelBinding.YES && ! scramMechanism.supportsChannelBinding() || channelBinding == ChannelBinding.NO && scramMechanism.supportsChannelBinding() ) { throw new IllegalArgumentException("Incompatible selection of mechanism and channel binding"); } if(scramMechanism.supportsChannelBinding()) { return new Builder(channelBinding, stringPreparation, Optional.empty(), Optional.of(scramMechanism)); } else { return new Builder(channelBinding, stringPreparation, Optional.of(scramMechanism), Optional.empty()); } } } /** * This class is not meant to be used directly. * Use instead {@link ScramClient#channelBinding(ChannelBinding)} and chained methods. */ public static class Builder extends PreBuilder2 { private final Optional nonChannelBindingMechanism; private final Optional channelBindingMechanism; private SecureRandom secureRandom = new SecureRandom(); private Supplier nonceSupplier; private int nonceLength = DEFAULT_NONCE_LENGTH; private Builder( ChannelBinding channelBinding, StringPreparation stringPreparation, Optional nonChannelBindingMechanism, Optional channelBindingMechanism ) { super(channelBinding, stringPreparation); this.nonChannelBindingMechanism = nonChannelBindingMechanism; this.channelBindingMechanism = channelBindingMechanism; } /** * Optional call. Selects a non-default SecureRandom instance, * based on the given algorithm and optionally provider. * This SecureRandom instance will be used to generate secure random values, * like the ones required to generate the nonce * (unless an external nonce provider is given via {@link Builder#nonceSupplier(Supplier)}). * Algorithm and provider names are those supported by the {@link SecureRandom} class. * @param algorithm The name of the algorithm to use. * @param provider The name of the provider of SecureRandom. Might be null. * @return The same class * @throws IllegalArgumentException If algorithm is null, or either the algorithm or provider are not supported */ public Builder secureRandomAlgorithmProvider(String algorithm, String provider) throws IllegalArgumentException { checkNotNull(algorithm, "algorithm"); try { secureRandom = null == provider ? SecureRandom.getInstance(algorithm) : SecureRandom.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new IllegalArgumentException("Invalid algorithm or provider", e); } return this; } /** * Optional call. The client will use a default nonce generator, * unless an external one is provided by this method. * * @param nonceSupplier A supplier of valid nonce Strings. * Please note that according to the * SCRAM RFC * only ASCII printable characters (except the comma, ',') are permitted on a nonce. * Length is not limited. * @return The same class * @throws IllegalArgumentException If nonceSupplier is null */ public Builder nonceSupplier(Supplier nonceSupplier) throws IllegalArgumentException { this.nonceSupplier = checkNotNull(nonceSupplier, "nonceSupplier"); return this; } /** * Sets a non-default ({@link ScramClient#DEFAULT_NONCE_LENGTH}) length for the nonce generation, * if no alternate nonceSupplier is provided via {@link Builder#nonceSupplier(Supplier)}. * @param length The length of the nonce. Must be positive and greater than 0 * @return The same class * @throws IllegalArgumentException If length is less than 1 */ public Builder nonceLength(int length) throws IllegalArgumentException { this.nonceLength = gt0(length, "length"); return this; } /** * Gets the client, fully constructed and configured, with the provided channel binding, string preparation * properties, and the selected SCRAM mechanism based on server supported mechanisms. * If no SecureRandom algorithm and provider were provided, a default one would be used. * If no nonceSupplier was provided, a default nonce generator would be used, * of the {@link ScramClient#DEFAULT_NONCE_LENGTH} length, unless {@link Builder#nonceLength(int)} is called. * @return The fully built instance. */ public ScramClient setup() { return new ScramClient( channelBinding, stringPreparation, nonChannelBindingMechanism, channelBindingMechanism, secureRandom, nonceSupplier != null ? nonceSupplier : () -> CryptoUtil.nonce(nonceLength, secureRandom) ); } } public StringPreparation getStringPreparation() { return stringPreparation; } public ScramMechanism getScramMechanism() { return scramMechanism; } /** * List all the supported SCRAM mechanisms by this client implementation * @return A list of the IANA-registered, SCRAM supported mechanisms */ public static List supportedMechanisms() { return Arrays.stream(ScramMechanisms.values()).map(m -> m.getName()).collect(Collectors.toList()); } /** * Instantiates a {@link ScramSession} for the specified user and this parametrized generator. * @param user The username of the authentication exchange * @return The ScramSession instance */ public ScramSession scramSession(String user) { return new ScramSession(scramMechanism, stringPreparation, checkNotEmpty(user, "user"), nonceSupplier.get()); } } scram-1.0.0-beta.2/client/src/main/java/com/ongres/scram/client/ScramSession.java000066400000000000000000000313071311733162300275160ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.client; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ScramServerErrorException; import com.ongres.scram.common.gssapi.Gs2CbindFlag; import com.ongres.scram.common.message.ClientFirstMessage; import com.ongres.scram.common.message.ClientFinalMessage; import com.ongres.scram.common.message.ServerFinalMessage; import com.ongres.scram.common.message.ServerFirstMessage; import com.ongres.scram.common.stringprep.StringPreparation; import java.util.Base64; import java.util.Optional; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * A class that represents a SCRAM client. Use this class to perform a SCRAM negotiation with a SCRAM server. * This class performs an authentication execution for a given user, and has state related to it. * Thus, it cannot be shared across users or authentication executions. */ public class ScramSession { private final ScramMechanism scramMechanism; private final StringPreparation stringPreparation; private final String user; private final String nonce; private ClientFirstMessage clientFirstMessage; private String serverFirstMessageString; /** * Constructs a SCRAM client, to perform an authentication for a given user. * This class can be instantiated directly, * but it is recommended that a {@link ScramClient} is used instead. * @param scramMechanism The SCRAM mechanism that will be using this client * @param stringPreparation * @param user * @param nonce */ public ScramSession(ScramMechanism scramMechanism, StringPreparation stringPreparation, String user, String nonce) { this.scramMechanism = checkNotNull(scramMechanism, "scramMechanism"); this.stringPreparation = checkNotNull(stringPreparation, "stringPreparation"); this.user = checkNotEmpty(user, "user"); this.nonce = checkNotEmpty(nonce, "nonce"); } private String setAndReturnClientFirstMessage(ClientFirstMessage clientFirstMessage) { this.clientFirstMessage = clientFirstMessage; return clientFirstMessage.toString(); } /** * Returns the text representation of a SCRAM client-first-message, with the GSS-API header values indicated. * @param gs2CbindFlag The channel binding flag * @param cbindName The channel binding algorithm name, if channel binding is supported, or null * @param authzid The optional * @return The message */ public String clientFirstMessage(Gs2CbindFlag gs2CbindFlag, String cbindName, String authzid) { return setAndReturnClientFirstMessage(new ClientFirstMessage(gs2CbindFlag, authzid, cbindName, user, nonce)); } /** * Returns the text representation of a SCRAM client-first-message, with no channel binding nor authzid. * @return The message */ public String clientFirstMessage() { return setAndReturnClientFirstMessage(new ClientFirstMessage(user, nonce)); } /** * Process a received server-first-message. * Generate by calling {@link #receiveServerFirstMessage(String)}. */ public class ServerFirstProcessor { private final ServerFirstMessage serverFirstMessage; private ServerFirstProcessor(String receivedServerFirstMessage) throws ScramParseException { serverFirstMessageString = receivedServerFirstMessage; serverFirstMessage = ServerFirstMessage.parseFrom(receivedServerFirstMessage, nonce); } public String getSalt() { return serverFirstMessage.getSalt(); } public int getIteration() { return serverFirstMessage.getIteration(); } /** * Generates a {@link ClientFinalProcessor}, that allows to generate the client-final-message and also * receive and parse the server-first-message. It is based on the user's password. * @param password The user's password * @return The handler * @throws IllegalArgumentException If the message is null or empty */ public ClientFinalProcessor clientFinalProcessor(String password) throws IllegalArgumentException { return new ClientFinalProcessor( serverFirstMessage.getNonce(), checkNotEmpty(password, "password"), getSalt(), getIteration() ); } /** * Generates a {@link ClientFinalProcessor}, that allows to generate the client-final-message and also * receive and parse the server-first-message. It is based on the clientKey and storedKey, * which, if available, provide an optimized path versus providing the original user's password. * @param clientKey The client key, as per the SCRAM algorithm. * It can be generated with: * {@link ScramFunctions#clientKey(ScramMechanism, StringPreparation, String, byte[], int)} * @param storedKey The stored key, as per the SCRAM algorithm. * It can be generated from the client key with: * {@link ScramFunctions#storedKey(ScramMechanism, byte[])} * @return The handler * @throws IllegalArgumentException If the message is null or empty */ public ClientFinalProcessor clientFinalProcessor(byte[] clientKey, byte[] storedKey) throws IllegalArgumentException { return new ClientFinalProcessor( serverFirstMessage.getNonce(), checkNotNull(clientKey, "clientKey"), checkNotNull(storedKey, "storedKey") ); } } /** * Processor that allows to generate the client-final-message, * as well as process the server-final-message and verify server's signature. * Generate the processor by calling either {@link ServerFirstProcessor#clientFinalProcessor(String)} * or {@link ServerFirstProcessor#clientFinalProcessor(byte[], byte[])}. */ public class ClientFinalProcessor { private final String nonce; private final byte[] clientKey; private final byte[] storedKey; private final byte[] serverKey; private String authMessage; private ClientFinalProcessor(String nonce, byte[] clientKey, byte[] storedKey, byte[] serverKey) { assert null != clientKey : "clientKey"; assert null != storedKey : "storedKey"; assert null != serverKey : "serverKey"; this.nonce = nonce; this.clientKey = clientKey; this.storedKey = storedKey; this.serverKey = serverKey; } private ClientFinalProcessor(String nonce, byte[] clientKey, byte[] serverKey) { this(nonce, clientKey, ScramFunctions.storedKey(scramMechanism, clientKey), serverKey); } private ClientFinalProcessor(String nonce, byte[] saltedPassword) { this( nonce, ScramFunctions.clientKey(scramMechanism, saltedPassword), ScramFunctions.serverKey(scramMechanism, saltedPassword) ); } private ClientFinalProcessor(String nonce, String password, String salt, int iteration) { this( nonce, ScramFunctions.saltedPassword( scramMechanism, stringPreparation, password, Base64.getDecoder().decode(salt), iteration ) ); } private synchronized void generateAndCacheAuthMessage(Optional cbindData) { if(null != authMessage) { return; } authMessage = clientFirstMessage.writeToWithoutGs2Header(new StringBuffer()) .append(",") .append(serverFirstMessageString) .append(",") .append(ClientFinalMessage.writeToWithoutProof(clientFirstMessage.getGs2Header(), cbindData, nonce)) .toString(); } private String clientFinalMessage(Optional cbindData) { if(null == authMessage) { generateAndCacheAuthMessage(cbindData); } ClientFinalMessage clientFinalMessage = new ClientFinalMessage( clientFirstMessage.getGs2Header(), cbindData, nonce, ScramFunctions.clientProof( clientKey, ScramFunctions.clientSignature(scramMechanism, storedKey, authMessage) ) ); return clientFinalMessage.toString(); } /** * Generates the SCRAM representation of the client-final-message, including the given channel-binding data. * @param cbindData The bytes of the channel-binding data * @return The message * @throws IllegalArgumentException If the channel binding data is null */ public String clientFinalMessage(byte[] cbindData) throws IllegalArgumentException { return clientFinalMessage(Optional.of(checkNotNull(cbindData, "cbindData"))); } /** * Generates the SCRAM representation of the client-final-message. * @return The message */ public String clientFinalMessage() { return clientFinalMessage(Optional.empty()); } /** * Receive and process the server-final-message. * Server SCRAM signatures is verified. * @param serverFinalMessage The received server-final-message * @throws ScramParseException If the message is not a valid server-final-message * @throws ScramServerErrorException If the server-final-message contained an error * @throws IllegalArgumentException If the message is null or empty */ public void receiveServerFinalMessage(String serverFinalMessage) throws ScramParseException, ScramServerErrorException, ScramInvalidServerSignatureException, IllegalArgumentException { checkNotEmpty(serverFinalMessage, "serverFinalMessage"); ServerFinalMessage message = ServerFinalMessage.parseFrom(serverFinalMessage); if(message.isError()) { throw new ScramServerErrorException(message.getError().get()); } if(! ScramFunctions.verifyServerSignature( scramMechanism, serverKey, authMessage, message.getVerifier().get() )) { throw new ScramInvalidServerSignatureException("Invalid server SCRAM signature"); } } } /** * Constructs a handler for the server-first-message, from its String representation. * @param serverFirstMessage The message * @return The handler * @throws ScramParseException If the message is not a valid server-first-message * @throws IllegalArgumentException If the message is null or empty */ public ServerFirstProcessor receiveServerFirstMessage(String serverFirstMessage) throws ScramParseException, IllegalArgumentException { return new ServerFirstProcessor(checkNotEmpty(serverFirstMessage, "serverFirstMessage")); } } scram-1.0.0-beta.2/client/src/test/000077500000000000000000000000001311733162300167125ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/000077500000000000000000000000001311733162300176335ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/com/000077500000000000000000000000001311733162300204115ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/com/ongres/000077500000000000000000000000001311733162300217065ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/com/ongres/scram/000077500000000000000000000000001311733162300230135ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/com/ongres/scram/client/000077500000000000000000000000001311733162300242715ustar00rootroot00000000000000scram-1.0.0-beta.2/client/src/test/java/com/ongres/scram/client/ScramClientTest.java000066400000000000000000000211441311733162300302020ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.client; import com.ongres.scram.common.ScramMechanism; import com.ongres.scram.common.ScramMechanisms; import com.ongres.scram.common.stringprep.StringPreparations; import com.ongres.scram.common.util.CryptoUtil; import org.junit.Test; import java.util.Arrays; import java.util.stream.Stream; import static org.junit.Assert.*; public class ScramClientTest { @Test public void getValid() { ScramClient client1 = ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") .setup(); ScramClient client2 = ScramClient .channelBinding(ScramClient.ChannelBinding.YES) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-256-PLUS") .nonceLength(64) .setup(); ScramClient client3 = ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") .nonceSupplier(() -> CryptoUtil.nonce(36)) .setup(); ScramClient client4 = ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertisedCsv("SCRAM-SHA-1,SCRAM-SHA-256-PLUS") .secureRandomAlgorithmProvider("SHA1PRNG", "SUN") .nonceLength(64) .setup(); ScramClient client5 = ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertisedCsv("SCRAM-SHA-1,SCRAM-SHA-256-PLUS") .secureRandomAlgorithmProvider("SHA1PRNG", null) .nonceLength(64) .setup(); ScramClient client6 = ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) .setup(); ScramClient client7 = ScramClient .channelBinding(ScramClient.ChannelBinding.YES) .stringPreparation(StringPreparations.NO_PREPARATION) .selectClientMechanism(ScramMechanisms.SCRAM_SHA_256_PLUS) .setup(); Stream.of(client1, client2, client3, client4, client5, client6, client7).forEach(c -> assertNotNull(c)); } @Test public void getInvalid() { int n = 0; try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1-PLUS") .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.YES) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1-PLUS,SCRAM-SAH-256-PLUS") .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("INVALID-SCRAM-MECHANISM") .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") .nonceSupplier(null) .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") .nonceLength(0) .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") .secureRandomAlgorithmProvider("Invalid algorithm", null) .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1", "SCRAM-SHA-1-PLUS") .secureRandomAlgorithmProvider("SHA1PRNG", "Invalid provider") .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.YES) .stringPreparation(StringPreparations.NO_PREPARATION) .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1_PLUS) .setup() ); } catch (IllegalArgumentException e) { n++; } try { assertNotNull(ScramClient .channelBinding(ScramClient.ChannelBinding.IF_SERVER_SUPPORTS_IT) .stringPreparation(StringPreparations.NO_PREPARATION) .selectClientMechanism(ScramMechanisms.SCRAM_SHA_1) .setup() ); } catch (IllegalArgumentException e) { n++; } assertEquals(10, n); } @Test public void supportedMechanismsTestAll() { assertArrayEquals( Arrays.stream( new String[] { "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" } ).sorted().toArray(), ScramClient.supportedMechanisms().stream().sorted().toArray() ); } } scram-1.0.0-beta.2/client/src/test/java/com/ongres/scram/client/ScramSessionTest.java000066400000000000000000000056311311733162300304120ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.client; import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ScramServerErrorException; import com.ongres.scram.common.stringprep.StringPreparations; import org.junit.Test; import static com.ongres.scram.common.RfcExample.*; import static org.junit.Assert.*; public class ScramSessionTest { private final ScramClient scramClient = ScramClient .channelBinding(ScramClient.ChannelBinding.NO) .stringPreparation(StringPreparations.NO_PREPARATION) .selectMechanismBasedOnServerAdvertised("SCRAM-SHA-1") .nonceSupplier(() -> CLIENT_NONCE) .setup(); @Test public void completeTest() throws ScramParseException, ScramInvalidServerSignatureException, ScramServerErrorException { ScramSession scramSession = scramClient.scramSession(USER); assertEquals(CLIENT_FIRST_MESSAGE, scramSession.clientFirstMessage()); ScramSession.ServerFirstProcessor serverFirstProcessor = scramSession.receiveServerFirstMessage( SERVER_FIRST_MESSAGE ); assertEquals(SERVER_SALT, serverFirstProcessor.getSalt()); assertEquals(SERVER_ITERATIONS, serverFirstProcessor.getIteration()); ScramSession.ClientFinalProcessor clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(PASSWORD); assertEquals(CLIENT_FINAL_MESSAGE, clientFinalProcessor.clientFinalMessage()); clientFinalProcessor.receiveServerFinalMessage(SERVER_FINAL_MESSAGE); } } scram-1.0.0-beta.2/common/000077500000000000000000000000001311733162300151565ustar00rootroot00000000000000scram-1.0.0-beta.2/common/pom.xml000066400000000000000000000023211311733162300164710ustar00rootroot00000000000000 4.0.0 parent com.ongres.scram 1.0.0-beta.2 common SCRAM - common junit junit test org.apache.maven.plugins maven-jar-plugin 2.6 test-jar scram-1.0.0-beta.2/common/src/000077500000000000000000000000001311733162300157455ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/000077500000000000000000000000001311733162300166715ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/000077500000000000000000000000001311733162300176125ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/000077500000000000000000000000001311733162300203705ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/000077500000000000000000000000001311733162300216655ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/000077500000000000000000000000001311733162300227725ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/000077500000000000000000000000001311733162300242625ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramAttributeValue.java000066400000000000000000000051151311733162300310550ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.util.AbstractCharAttributeValue; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Parse and write SCRAM Attribute-Value pairs. */ public class ScramAttributeValue extends AbstractCharAttributeValue { public ScramAttributeValue(ScramAttributes attribute, String value) { super(attribute, checkNotNull(value, "value")); } public static StringBuffer writeTo(StringBuffer sb, ScramAttributes attribute, String value) { return new ScramAttributeValue(attribute, value).writeTo(sb); } /** * Parses a potential ScramAttributeValue String. * @param value The string that contains the Attribute-Value pair. * @return The parsed class * @throws ScramParseException If the argument is empty or an invalid Attribute-Value */ public static ScramAttributeValue parse(String value) throws ScramParseException { if(null == value || value.length() < 3 || value.charAt(1) != '=') { throw new ScramParseException("Invalid ScramAttributeValue '" + value + "'"); } return new ScramAttributeValue(ScramAttributes.byChar(value.charAt(0)), value.substring(2)); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramAttributes.java000066400000000000000000000146751311733162300302560ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.util.CharAttribute; import java.util.HashMap; import java.util.Map; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * SCRAM Attributes as defined in Section 5.1 of the RFC. * * Not all the available attributes may be available in this implementation. */ public enum ScramAttributes implements CharAttribute { /** * This attribute specifies the name of the user whose password is used for authentication * (a.k.a. "authentication identity" [RFC4422]). * If the "a" attribute is not specified (which would normally be the case), this username is also the identity * that will be associated with the connection subsequent to authentication and authorization. * * The client SHOULD prepare the username using the "SASLprep" profile * [RFC4013] of the "stringprep" algorithm * [RFC3454] treating it as a query string * (i.e., unassigned Unicode code points are allowed). * * The characters ',' or '=' in usernames are sent as '=2C' and '=3D' respectively. */ USERNAME('n'), /** * This is an optional attribute, and is part of the GS2 [RFC5801] * bridge between the GSS-API and SASL. This attribute specifies an authorization identity. * A client may include it in its first message to the server if it wants to authenticate as one user, * but subsequently act as a different user. This is typically used by an administrator to perform some management * task on behalf of another user, or by a proxy in some situations. * * If this attribute is omitted (as it normally would be), the authorization identity is assumed to be derived * from the username specified with the (required) "n" attribute. * * The server always authenticates the user specified by the "n" attribute. * If the "a" attribute specifies a different user, the server associates that identity with the connection after * successful authentication and authorization checks. * * The syntax of this field is the same as that of the "n" field with respect to quoting of '=' and ','. */ AUTHZID('a'), /** * This attribute specifies a sequence of random printable ASCII characters excluding ',' * (which forms the nonce used as input to the hash function). No quoting is applied to this string. */ NONCE('r'), /** * This REQUIRED attribute specifies the base64-encoded GS2 header and channel binding data. * The attribute data consist of: *
    *
  • * the GS2 header from the client's first message * (recall that the GS2 header contains a channel binding flag and an optional authzid). * This header is going to include channel binding type prefix * (see [RFC5056]), * if and only if the client is using channel binding; *
  • *
  • * followed by the external channel's channel binding data, * if and only if the client is using channel binding. *
  • *
*/ CHANNEL_BINDING('c'), /** * This attribute specifies the base64-encoded salt used by the server for this user. */ SALT('s'), /** * This attribute specifies an iteration count for the selected hash function and user. */ ITERATION('i'), /** * This attribute specifies a base64-encoded ClientProof. */ CLIENT_PROOF('p'), /** * This attribute specifies a base64-encoded ServerSignature. */ SERVER_SIGNATURE('v'), /** * This attribute specifies an error that occurred during authentication exchange. * Can help diagnose the reason for the authentication exchange failure. */ ERROR('e') ; private final char attributeChar; ScramAttributes(char attributeChar) { this.attributeChar = checkNotNull(attributeChar, "attributeChar"); } @Override public char getChar() { return attributeChar; } private static final Map REVERSE_MAPPING = new HashMap(); static { for(ScramAttributes scramAttribute : values()) { REVERSE_MAPPING.put(scramAttribute.getChar(), scramAttribute); } } /** * Find a SCRAMAttribute by its character. * @param c The character. * @return The SCRAMAttribute that has that character. * @throws ScramParseException If no SCRAMAttribute has this character. */ public static ScramAttributes byChar(char c) throws ScramParseException { if(! REVERSE_MAPPING.containsKey(c)) { throw new ScramParseException("Attribute with char '" + c + "' does not exist"); } return REVERSE_MAPPING.get(c); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramFunctions.java000066400000000000000000000225321311733162300300670ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import com.ongres.scram.common.stringprep.StringPreparation; import com.ongres.scram.common.util.CryptoUtil; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * Utility functions (e.g. crypto) for SCRAM. */ public class ScramFunctions { private static final byte[] CLIENT_KEY_HMAC_KEY = "Client Key".getBytes(StandardCharsets.UTF_8); private static final byte[] SERVER_KEY_HMAC_KEY = "Server Key".getBytes(StandardCharsets.UTF_8); /** * Compute the salted password, based on the given SCRAM mechanism, the String preparation algorithm, * the provided salt and the number of iterations. * * {@code * SaltedPassword := Hi(Normalize(password), salt, i) * } * * @param scramMechanism The SCRAM mechanism * @param stringPreparation The String preparation * @param password The non-salted password * @param salt The bytes representing the salt * @param iteration The number of iterations * @return The salted password */ public static byte[] saltedPassword( ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, int iteration ) { return CryptoUtil.hi( scramMechanism.secretKeyFactory(), scramMechanism.algorithmKeyLength(), stringPreparation.normalize(password), salt, iteration ); } /** * Computes the HMAC of the message and key, using the given SCRAM mechanism. * @param scramMechanism The SCRAM mechanism * @param message The message to compute the HMAC * @param key The key used to initialize the MAC * @return The computed HMAC */ public static byte[] hmac(ScramMechanism scramMechanism, byte[] message, byte[] key) { return CryptoUtil.hmac(scramMechanism.secretKeySpec(key), scramMechanism.getMacInstance(), message); } /** * Generates a client key, from the salted password. * * {@code * ClientKey := HMAC(SaltedPassword, "Client Key") * } * * @param scramMechanism The SCRAM mechanism * @param saltedPassword The salted password * @return The client key */ public static byte[] clientKey(ScramMechanism scramMechanism, byte[] saltedPassword) { return hmac(scramMechanism, CLIENT_KEY_HMAC_KEY, saltedPassword); } /** * Generates a client key from the password and salt. * * {@code * SaltedPassword := Hi(Normalize(password), salt, i) * ClientKey := HMAC(SaltedPassword, "Client Key") * } * * @param scramMechanism The SCRAM mechanism * @param stringPreparation The String preparation * @param password The non-salted password * @param salt The bytes representing the salt * @param iteration The number of iterations * @return The client key */ public static byte[] clientKey( ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, int iteration ) { return clientKey(scramMechanism, saltedPassword(scramMechanism, stringPreparation, password, salt, iteration)); } /** * Generates a server key, from the salted password. * * {@code * ServerKey := HMAC(SaltedPassword, "Server Key") * } * * @param scramMechanism The SCRAM mechanism * @param saltedPassword The salted password * @return The server key */ public static byte[] serverKey(ScramMechanism scramMechanism, byte[] saltedPassword) { return hmac(scramMechanism, SERVER_KEY_HMAC_KEY, saltedPassword); } /** * Generates a server key from the password and salt. * * {@code * SaltedPassword := Hi(Normalize(password), salt, i) * ServerKey := HMAC(SaltedPassword, "Server Key") * } * * @param scramMechanism The SCRAM mechanism * @param stringPreparation The String preparation * @param password The non-salted password * @param salt The bytes representing the salt * @param iteration The number of iterations * @return The server key */ public static byte[] serverKey( ScramMechanism scramMechanism, StringPreparation stringPreparation, String password, byte[] salt, int iteration ) { return serverKey(scramMechanism, saltedPassword(scramMechanism, stringPreparation, password, salt, iteration)); } /** * Computes the hash function of a given value, based on the SCRAM mechanism hash function. * @param scramMechanism The SCRAM mechanism * @param value The value to hash * @return The hashed value */ public static byte[] hash(ScramMechanism scramMechanism, byte[] value) { return scramMechanism.getMessageDigestInstance().digest(value); } /** * Generates a stored key, from the salted password. * * {@code * StoredKey := H(ClientKey) * } * * @param scramMechanism The SCRAM mechanism * @param clientKey The client key * @return The stored key */ public static byte[] storedKey(ScramMechanism scramMechanism, byte[] clientKey) { return hash(scramMechanism, clientKey); } /** * Computes the SCRAM client signature. * * {@code * ClientSignature := HMAC(StoredKey, AuthMessage) * } * * @param scramMechanism The SCRAM mechanism * @param storedKey The stored key * @param authMessage The auth message * @return The client signature */ public static byte[] clientSignature(ScramMechanism scramMechanism, byte[] storedKey, String authMessage) { return hmac(scramMechanism, authMessage.getBytes(StandardCharsets.UTF_8), storedKey); } /** * Computes the SCRAM client proof to be sent to the server on the client-final-message. * * {@code * ClientProof := ClientKey XOR ClientSignature * } * * @param clientKey The client key * @param clientSignature The client signature * @return The client proof */ public static byte[] clientProof(byte[] clientKey, byte[] clientSignature) { return CryptoUtil.xor(clientKey, clientSignature); } /** * Compute the SCRAM server signature. * * {@code * ServerSignature := HMAC(ServerKey, AuthMessage) * } * * @param scramMechanism The SCRAM mechanism * @param serverKey The server key * @param authMessage The auth message * @return The server signature */ public static byte[] serverSignature(ScramMechanism scramMechanism, byte[] serverKey, String authMessage) { return clientSignature(scramMechanism, serverKey, authMessage); } /** * Verifies that a provided client proof is correct. * @param scramMechanism The SCRAM mechanism * @param clientProof The provided client proof * @param storedKey The stored key * @param authMessage The auth message * @return True if the client proof is correct */ public static boolean verifyClientProof( ScramMechanism scramMechanism, byte[] clientProof, byte[] storedKey, String authMessage ) { byte[] clientSignature = clientSignature(scramMechanism, storedKey, authMessage); byte[] clientKey = CryptoUtil.xor(clientSignature, clientProof); byte[] computedStoredKey = hash(scramMechanism, clientKey); return Arrays.equals(storedKey, computedStoredKey); } /** * Verifies that a provided server proof is correct. * @param scramMechanism The SCRAM mechanism * @param serverKey The server key * @param authMessage The auth message * @param serverSignature The provided server signature * @return True if the server signature is correct */ public static boolean verifyServerSignature( ScramMechanism scramMechanism, byte[] serverKey, String authMessage, byte[] serverSignature ) { return Arrays.equals(serverSignature(scramMechanism, serverKey, authMessage), serverSignature); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramMechanism.java000066400000000000000000000066261311733162300300310ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import javax.crypto.Mac; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; /** * Definition of the functionality to be provided by every ScramMechanism. * * Every ScramMechanism implemented must provide implementations of their respective {@link MessageDigest} * and {@link Mac} that will not throw a RuntimeException on any JVM, to guarantee true portability of this library. */ public interface ScramMechanism { /** * The name of the mechanism, which must be a value registered under IANA: * * SASL SCRAM Family Mechanisms * @return The mechanism name */ String getName(); /** * Gets a constructed {@link MessageDigest} instance, according to the algorithm of the SCRAM mechanism. * @return The MessageDigest instance * @throws RuntimeException If the MessageDigest instance of the algorithm is not provided by current JVM */ MessageDigest getMessageDigestInstance() throws RuntimeException; /** * Gets a constructed {@link Mac} instance, according to the algorithm of the SCRAM mechanism. * @return The Mac instance * @throws RuntimeException If the Mac instance of the algorithm is not provided by current JVM */ Mac getMacInstance() throws RuntimeException; /** * Generates a key of the algorith used, based on the key given. * @param key The bytes of the key to use * @return The instance of SecretKeySpec */ SecretKeySpec secretKeySpec(byte[] key); /** * Gets a SecretKeyFactory for the given algorithm. * @return The SecretKeyFactory */ SecretKeyFactory secretKeyFactory(); /** * Returns the length of the key length of the algorithm. * @return The length (in bits) */ int algorithmKeyLength(); /** * Whether this mechanism supports channel binding * @return True if it supports channel binding, false otherwise */ boolean supportsChannelBinding(); } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramMechanisms.java000066400000000000000000000176051311733162300302130ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import javax.crypto.Mac; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import static com.ongres.scram.common.util.Preconditions.checkNotNull; import static com.ongres.scram.common.util.Preconditions.gt0; /** * SCRAM Mechanisms supported by this library. * At least, SCRAM-SHA-1 and SCRAM-SHA-256 are provided, since both the hash and the HMAC implementations * are provided by the Java JDK version 6 or greater. * * {@link java.security.MessageDigest}: "Every implementation of the Java platform is required to support the * following standard MessageDigest algorithms: MD5, SHA-1, SHA-256". * * {@link javax.crypto.Mac}: "Every implementation of the Java platform is required to support the following * standard Mac algorithms: HmacMD5, HmacSHA1, HmacSHA256". * * @see * SASL SCRAM Family Mechanisms */ public enum ScramMechanisms implements ScramMechanism { SCRAM_SHA_1 ( "SHA-1", "SHA-1", 160, "HmacSHA1", false, 1 ), SCRAM_SHA_1_PLUS ( "SHA-1", "SHA-1", 160, "HmacSHA1", true, 1 ), SCRAM_SHA_256 ( "SHA-256", "SHA-256", 256, "HmacSHA256", false, 10 ), SCRAM_SHA_256_PLUS ( "SHA-256", "SHA-256", 256, "HmacSHA256", true, 10 ) ; private static final String SCRAM_MECHANISM_NAME_PREFIX = "SCRAM-"; private static final String CHANNEL_BINDING_SUFFIX = "-PLUS"; private static final String PBKDF2_PREFIX_ALGORITHM_NAME = "PBKDF2With"; private static final Map BY_NAME_MAPPING = Arrays.stream(values()).collect(Collectors.toMap(v -> v.getName(), v -> v)); private final String mechanismName; private final String hashAlgorithmName; private final int keyLength; private final String hmacAlgorithmName; private final boolean channelBinding; private final int priority; ScramMechanisms( String name, String hashAlgorithmName, int keyLength, String hmacAlgorithmName, boolean channelBinding, int priority ) { this.mechanismName = SCRAM_MECHANISM_NAME_PREFIX + checkNotNull(name, "name") + (channelBinding ? CHANNEL_BINDING_SUFFIX : "") ; this.hashAlgorithmName = checkNotNull(hashAlgorithmName, "hashAlgorithmName"); this.keyLength = gt0(keyLength, "keyLength"); this.hmacAlgorithmName = checkNotNull(hmacAlgorithmName, "hmacAlgorithmName"); this.channelBinding = channelBinding; this.priority = gt0(priority, "priority"); } /** * Method that returns the name of the hash algorithm. * It is protected since should be of no interest for direct users. * The instance is supposed to provide abstractions over the algorithm names, * and are not meant to be directly exposed. * @return The name of the hash algorithm */ protected String getHashAlgorithmName() { return hashAlgorithmName; } /** * Method that returns the name of the HMAC algorithm. * It is protected since should be of no interest for direct users. * The instance is supposed to provide abstractions over the algorithm names, * and are not meant to be directly exposed. * @return The name of the HMAC algorithm */ protected String getHmacAlgorithmName() { return hmacAlgorithmName; } @Override public String getName() { return mechanismName; } @Override public boolean supportsChannelBinding() { return channelBinding; } @Override public MessageDigest getMessageDigestInstance() { try { return MessageDigest.getInstance(hashAlgorithmName); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Algorithm " + hashAlgorithmName + " not present in current JVM"); } } @Override public Mac getMacInstance() { try { return Mac.getInstance(hmacAlgorithmName); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MAC Algorithm " + hmacAlgorithmName + " not present in current JVM"); } } @Override public SecretKeySpec secretKeySpec(byte[] key) { return new SecretKeySpec(key, hmacAlgorithmName); } @Override public SecretKeyFactory secretKeyFactory() { try { return SecretKeyFactory.getInstance(PBKDF2_PREFIX_ALGORITHM_NAME + hmacAlgorithmName); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Unsupported PBKDF2 for " + mechanismName); } } @Override public int algorithmKeyLength() { return keyLength; } /** * Gets a SCRAM mechanism, given its standard IANA name. * @param name The standard IANA full name of the mechanism. * @return An Optional instance that contains the ScramMechanism if it was found, or empty otherwise. */ public static Optional byName(String name) { checkNotNull(name, "name"); return Optional.ofNullable(BY_NAME_MAPPING.get(name)); } /** * This class classifies SCRAM mechanisms by two properties: whether they support channel binding; * and a priority, which is higher for safer algorithms (like SHA-256 vs SHA-1). * * Given a list of SCRAM mechanisms supported by the peer, pick one that matches the channel binding requirements * and has the highest priority. * * @param channelBinding The type of matching mechanism searched for * @param peerMechanisms The mechanisms supported by the other peer * @return The selected mechanism, or null if no mechanism matched */ public static Optional selectMatchingMechanism(boolean channelBinding, String... peerMechanisms) { return Arrays.stream(peerMechanisms) .map(s -> BY_NAME_MAPPING.get(s)) .filter(m -> m != null) // Filter out invalid names .flatMap(m -> Arrays.stream(values()) .filter( v -> channelBinding == v.channelBinding && v.mechanismName.equals(m.mechanismName) ) ) .max(Comparator.comparing(c -> c.priority)) .map(m -> (ScramMechanism) m) ; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/ScramStringFormatting.java000066400000000000000000000130741311733162300314210ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import java.nio.charset.StandardCharsets; import java.util.Base64; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Class with static methods that provide support for converting to/from salNames. * @see [RFC5802] Section 7: Formal Syntax */ public class ScramStringFormatting { private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); /** * Given a value-safe-char (normalized UTF-8 String), * return one where characters ',' and '=' are represented by '=2C' or '=3D', respectively. * @param value The value to convert so saslName * @return The saslName, with caracter escaped (if any) */ public static String toSaslName(String value) { if(null == value || value.isEmpty()) { return value; } int nComma = 0, nEqual = 0; char[] originalChars = value.toCharArray(); // Fast path for(char c : originalChars) { if(',' == c) { nComma++; } else if('=' == c) { nEqual++; } } if(nComma == 0 && nEqual == 0) { return value; } // Replace chars char[] saslChars = new char[originalChars.length + nComma * 2 + nEqual * 2]; int i = 0; for(char c : originalChars) { if(',' == c) { saslChars[i++] = '='; saslChars[i++] = '2'; saslChars[i++] = 'C'; } else if('=' == c) { saslChars[i++] = '='; saslChars[i++] = '3'; saslChars[i++] = 'D'; } else { saslChars[i++] = c; } } return new String(saslChars); } /** * Given a saslName, return a non-escaped String. * @param value The saslName * @return The saslName, unescaped * @throws IllegalArgumentException If a ',' character is present, or a '=' not followed by either '2C' or '3D' */ public static String fromSaslName(String value) throws IllegalArgumentException { if(null == value || value.isEmpty()) { return value; } int nEqual = 0; char[] orig = value.toCharArray(); // Fast path for(int i = 0; i < orig.length; i++) { if(orig[i] == ',') { throw new IllegalArgumentException("Invalid ',' character present in saslName"); } if(orig[i] == '=') { nEqual++; if(i + 2 > orig.length - 1) { throw new IllegalArgumentException("Invalid '=' character present in saslName"); } if(! (orig[i+1] == '2' && orig[i+2] == 'C' || orig[i+1] == '3' && orig[i+2] == 'D')) { throw new IllegalArgumentException( "Invalid char '=" + orig[i+1] + orig[i+2] + "' found in saslName" ); } } } if(nEqual == 0) { return value; } // Replace characters char[] replaced = new char[orig.length - nEqual * 2]; for(int r = 0, o = 0; r < replaced.length; r++) { if('=' == orig[o]) { if(orig[o+1] == '2' && orig[o+2] == 'C') { replaced[r] = ','; } else if(orig[o+1] == '3' && orig[o+2] == 'D') { replaced[r] = '='; } o += 3; } else { replaced[r] = orig[o]; o += 1; } } return new String(replaced); } public static String base64Encode(byte[] value) throws IllegalArgumentException { return BASE64_ENCODER.encodeToString(checkNotNull(value, "value")); } public static String base64Encode(String value) throws IllegalArgumentException { return base64Encode(checkNotEmpty(value, "value").getBytes(StandardCharsets.UTF_8)); } public static byte[] base64Decode(String value) throws IllegalArgumentException { return BASE64_DECODER.decode(checkNotEmpty(value, "value")); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/exception/000077500000000000000000000000001311733162300262605ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/exception/ScramException.java000066400000000000000000000041041311733162300320460ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.exception; import javax.security.sasl.SaslException; /** * This class represents an error when using SCRAM, which is a SASL method. * * {@link SaslException} */ public class ScramException extends SaslException { /** * Constructs a new instance of ScramException with a detailed message. * @param detail A String containing details about the exception */ public ScramException(String detail) { super(detail); } /** * Constructs a new instance of ScramException with a detailed message and a root cause. * @param detail A String containing details about the exception * @param ex The root exception */ public ScramException(String detail, Throwable ex) { super(detail, ex); } } ScramInvalidServerSignatureException.java000066400000000000000000000041351311733162300363530ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/exception/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.exception; /** * This class represents an error when parsing SCRAM messages */ public class ScramInvalidServerSignatureException extends ScramException { /** * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message. * @param detail A String containing details about the exception */ public ScramInvalidServerSignatureException(String detail) { super(detail); } /** * Constructs a new instance of ScramInvalidServerSignatureException with a detailed message and a root cause. * @param detail A String containing details about the exception * @param ex The root exception */ public ScramInvalidServerSignatureException(String detail, Throwable ex) { super(detail, ex); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/exception/ScramParseException.java000066400000000000000000000040101311733162300330350ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.exception; /** * This class represents an error when parsing SCRAM messages */ public class ScramParseException extends ScramException { /** * Constructs a new instance of ScramParseException with a detailed message. * @param detail A String containing details about the exception */ public ScramParseException(String detail) { super(detail); } /** * Constructs a new instance of ScramParseException with a detailed message and a root cause. * @param detail A String containing details about the exception * @param ex The root exception */ public ScramParseException(String detail, Throwable ex) { super(detail, ex); } } ScramServerErrorException.java000066400000000000000000000047261311733162300342020ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/exception/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.exception; import com.ongres.scram.common.message.ServerFinalMessage; /** * This class represents an error when parsing SCRAM messages */ public class ScramServerErrorException extends ScramException { private final ServerFinalMessage.Error error; private static String toString(ServerFinalMessage.Error error) { return "Server-final-message is an error message. Error: " + error.getErrorMessage(); } /** * Constructs a new instance of ScramServerErrorException with a detailed message. * @param error The SCRAM error in the message */ public ScramServerErrorException(ServerFinalMessage.Error error) { super(toString(error)); this.error = error; } /** * Constructs a new instance of ScramServerErrorException with a detailed message and a root cause. * @param error The SCRAM error in the message * @param ex The root exception */ public ScramServerErrorException(ServerFinalMessage.Error error, Throwable ex) { super(toString(error), ex); this.error = error; } public ServerFinalMessage.Error getError() { return error; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/gssapi/000077500000000000000000000000001311733162300255505ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/gssapi/Gs2AttributeValue.java000066400000000000000000000051611311733162300317320ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import com.ongres.scram.common.util.AbstractCharAttributeValue; /** * Parse and write GS2 Attribute-Value pairs. */ public class Gs2AttributeValue extends AbstractCharAttributeValue { public Gs2AttributeValue(Gs2Attributes attribute, String value) { super(attribute, value); } public static StringBuffer writeTo(StringBuffer sb, Gs2Attributes attribute, String value) { return new Gs2AttributeValue(attribute, value).writeTo(sb); } /** * Parses a potential Gs2AttributeValue String. * @param value The string that contains the Attribute-Value pair (where value is optional). * @return The parsed class, or null if the String was null. * @throws IllegalArgumentException If the String is an invalid Gs2AttributeValue */ public static Gs2AttributeValue parse(String value) throws IllegalArgumentException { if(null == value) { return null; } if(value.length() < 1 || value.length() == 2 || (value.length() > 2 && value.charAt(1) != '=')) { throw new IllegalArgumentException("Invalid Gs2AttributeValue"); } return new Gs2AttributeValue( Gs2Attributes.byChar(value.charAt(0)), value.length() > 2 ? value.substring(2) : null ); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Attributes.java000066400000000000000000000056771311733162300311340ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.util.CharAttribute; /** * Possible values of a GS2 Attribute. * * @see [RFC5802] Formal Syntax */ public enum Gs2Attributes implements CharAttribute { /** * Channel binding attribute. Client doesn't support channel binding. */ CLIENT_NOT(Gs2CbindFlag.CLIENT_NOT.getChar()), /** * Channel binding attribute. Client does support channel binding but thinks the server does not. */ CLIENT_YES_SERVER_NOT(Gs2CbindFlag.CLIENT_YES_SERVER_NOT.getChar()), /** * Channel binding attribute. Client requires channel binding. The selected channel binding follows "p=". */ CHANNEL_BINDING_REQUIRED(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED.getChar()), /** * SCRAM attribute. This attribute specifies an authorization identity. */ AUTHZID(ScramAttributes.AUTHZID.getChar()) ; private final char flag; Gs2Attributes(char flag) { this.flag = flag; } @Override public char getChar() { return flag; } public static Gs2Attributes byChar(char c) { switch(c) { case 'n': return CLIENT_NOT; case 'y': return CLIENT_YES_SERVER_NOT; case 'p': return CHANNEL_BINDING_REQUIRED; case 'a': return AUTHZID; } throw new IllegalArgumentException("Invalid GS2Attribute character '" + c + "'"); } public static Gs2Attributes byGS2CbindFlag(Gs2CbindFlag cbindFlag) { return byChar(cbindFlag.getChar()); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/gssapi/Gs2CbindFlag.java000066400000000000000000000050241311733162300306010ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import com.ongres.scram.common.util.CharAttribute; /** * Possible values of a GS2 Cbind Flag (channel binding; part of GS2 header). * These values are sent by the client, and so are interpreted from this perspective. * * @see [RFC5802] Formal Syntax */ public enum Gs2CbindFlag implements CharAttribute { /** * Client doesn't support channel binding. */ CLIENT_NOT('n'), /** * Client does support channel binding but thinks the server does not. */ CLIENT_YES_SERVER_NOT('y'), /** * Client requires channel binding. The selected channel binding follows "p=". */ CHANNEL_BINDING_REQUIRED('p') ; private final char flag; Gs2CbindFlag(char flag) { this.flag = flag; } @Override public char getChar() { return flag; } public static Gs2CbindFlag byChar(char c) { switch(c) { case 'n': return CLIENT_NOT; case 'y': return CLIENT_YES_SERVER_NOT; case 'p': return CHANNEL_BINDING_REQUIRED; } throw new IllegalArgumentException("Invalid Gs2CbindFlag character '" + c + "'"); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/gssapi/Gs2Header.java000066400000000000000000000133201311733162300301560ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import com.ongres.scram.common.util.StringWritableCsv; import com.ongres.scram.common.ScramStringFormatting; import com.ongres.scram.common.util.AbstractStringWritable; import java.util.Optional; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * GSS Header. Format: * * {@code * gs2-header = gs2-cbind-flag "," [ authzid ] "," * gs2-cbind-flag = ("p=" cb-name) / "n" / "y" * authzid = "a=" saslname * } * * Current implementation does not support channel binding. * If p is used as the cbind flag, the cb-name value is not validated. * * @see [RFC5802] Formal Syntax */ public class Gs2Header extends AbstractStringWritable { private final Gs2AttributeValue cbind; private final Optional authzid; /** * Construct and validates a Gs2Header. * Only provide the channel binding name if the channel binding flag is set to required. * @param cbindFlag The channel binding flag * @param cbName The channel-binding name. Should be not null iif channel binding is required * @param authzid The optional SASL authorization identity * @throws IllegalArgumentException If the channel binding flag and argument are invalid */ public Gs2Header(Gs2CbindFlag cbindFlag, String cbName, String authzid) throws IllegalArgumentException { checkNotNull(cbindFlag, "cbindFlag"); if(cbindFlag == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED ^ cbName != null) { throw new IllegalArgumentException("Specify channel binding flag and value together, or none"); } // TODO: cbName is not being properly validated cbind = new Gs2AttributeValue(Gs2Attributes.byGS2CbindFlag(cbindFlag), cbName); this.authzid = authzid == null ? Optional.empty() : Optional.of( new Gs2AttributeValue(Gs2Attributes.AUTHZID, ScramStringFormatting.toSaslName(authzid)) ) ; } /** * Construct and validates a Gs2Header with no authzid. * Only provide the channel binding name if the channel binding flag is set to required. * @param cbindFlag The channel binding flag * @param cbName The channel-binding name. Should be not null iif channel binding is required * @throws IllegalArgumentException If the channel binding flag and argument are invalid */ public Gs2Header(Gs2CbindFlag cbindFlag, String cbName) throws IllegalArgumentException { this(cbindFlag, cbName, null); } /** * Construct and validates a Gs2Header with no authzid nor channel binding. * @param cbindFlag The channel binding flag * @throws IllegalArgumentException If the channel binding is supported (no cbname can be provided here) */ public Gs2Header(Gs2CbindFlag cbindFlag) { this(cbindFlag, null, null); } public Gs2CbindFlag getChannelBindingFlag() { return Gs2CbindFlag.byChar(cbind.getChar()); } public Optional getChannelBindingName() { return Optional.ofNullable(cbind.getValue()); } public Optional getAuthzid() { return authzid.map(a -> a.getValue()); } @Override public StringBuffer writeTo(StringBuffer sb) { return StringWritableCsv.writeTo(sb, cbind, authzid.orElse(null)); } /** * Read a Gs2Header from a String. String may contain trailing fields that will be ignored. * @param message The String containing the Gs2Header * @return The parsed Gs2Header object * @throws IllegalArgumentException If the format/values of the String do not conform to a Gs2Header */ public static Gs2Header parseFrom(String message) throws IllegalArgumentException { checkNotNull(message, "Null message"); String[] gs2HeaderSplit = StringWritableCsv.parseFrom(message, 2); if(gs2HeaderSplit.length == 0) { throw new IllegalArgumentException("Invalid number of fields for the GS2 Header"); } Gs2AttributeValue gs2cbind = Gs2AttributeValue.parse(gs2HeaderSplit[0]); return new Gs2Header( Gs2CbindFlag.byChar(gs2cbind.getChar()), gs2cbind.getValue(), gs2HeaderSplit[1] == null || gs2HeaderSplit[1].isEmpty() ? null : Gs2AttributeValue.parse(gs2HeaderSplit[1]).getValue() ); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/message/000077500000000000000000000000001311733162300257065ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/message/ClientFinalMessage.java000066400000000000000000000131111311733162300322430ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.ScramAttributeValue; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.ScramStringFormatting; import com.ongres.scram.common.gssapi.Gs2Header; import com.ongres.scram.common.util.StringWritable; import com.ongres.scram.common.util.StringWritableCsv; import java.util.Optional; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Constructs and parses client-final-messages. Formal syntax is: * * {@code * client-final-message-without-proof = channel-binding "," nonce ["," extensions] * client-final-message = client-final-message-without-proof "," proof * } * * Note that extensions are not supported. * * @see [RFC5802] Section 7 */ public class ClientFinalMessage implements StringWritable { private final String cbind; private final String nonce; private final byte[] proof; private static String generateCBind(Gs2Header gs2Header, Optional cbindData) { StringBuffer sb = new StringBuffer(); gs2Header.writeTo(sb) .append(','); cbindData.ifPresent( v -> new ScramAttributeValue( ScramAttributes.CHANNEL_BINDING, ScramStringFormatting.base64Encode(cbindData.get()) ).writeTo(sb) ); return sb.toString(); } /** * Constructus a client-final-message with the provided gs2Header (the same one used in the client-first-message), * optionally the channel binding data, and the nonce. * This method is intended to be used by SCRAM clients, and not to be constructed directly. * @param gs2Header The GSS-API header * @param cbindData If using channel binding, the channel binding data * @param nonce The nonce * @param proof The bytes representing the computed client proof */ public ClientFinalMessage(Gs2Header gs2Header, Optional cbindData, String nonce, byte[] proof) { this.cbind = generateCBind( checkNotNull(gs2Header, "gs2Header"), checkNotNull(cbindData, "cbindData") ); this.nonce = checkNotEmpty(nonce, "nonce"); this.proof = checkNotNull(proof, "proof"); } private static StringBuffer writeToWithoutProof(StringBuffer sb, String cbind, String nonce) { return StringWritableCsv.writeTo( sb, new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, ScramStringFormatting.base64Encode(cbind)), new ScramAttributeValue(ScramAttributes.NONCE, nonce) ); } private static StringBuffer writeToWithoutProof( StringBuffer sb, Gs2Header gs2Header, Optional cbindData, String nonce ) { return writeToWithoutProof( sb, generateCBind( checkNotNull(gs2Header, "gs2Header"), checkNotNull(cbindData, "cbindData") ), nonce ); } /** * Returns a StringBuffer filled in with the formatted output of a client-first-message without the proof value. * This is useful for computing the auth-message, used in turn to compute the proof. * @param gs2Header The GSS-API header * @param cbindData The optional channel binding data * @param nonce The nonce * @return The String representation of the part of the message that excludes the proof */ public static StringBuffer writeToWithoutProof(Gs2Header gs2Header, Optional cbindData, String nonce) { return writeToWithoutProof(new StringBuffer(), gs2Header, cbindData, nonce); } @Override public StringBuffer writeTo(StringBuffer sb) { writeToWithoutProof(sb, cbind, nonce); return StringWritableCsv.writeTo( sb, null, // This marks the position of writeToWithoutProof, required for the "," new ScramAttributeValue(ScramAttributes.CLIENT_PROOF, ScramStringFormatting.base64Encode(proof)) ); } @Override public String toString() { return writeTo(new StringBuffer()).toString(); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/message/ClientFirstMessage.java000066400000000000000000000206601311733162300323100ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.ScramAttributeValue; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.ScramStringFormatting; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.gssapi.Gs2CbindFlag; import com.ongres.scram.common.gssapi.Gs2Header; import com.ongres.scram.common.util.StringWritable; import com.ongres.scram.common.util.StringWritableCsv; import java.util.Optional; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Constructs and parses client-first-messages. * Message contains a {@link Gs2Header}, a username and a nonce. Formal syntax is: * * {@code * client-first-message-bare = [reserved-mext ","] username "," nonce ["," extensions] client-first-message = gs2-header client-first-message-bare * } * * Note that extensions are not supported. * * @see [RFC5802] Section 7 */ public class ClientFirstMessage implements StringWritable { private final Gs2Header gs2Header; private final String user; private final String nonce; /** * Constructs a client-first-message for the given user, nonce and gs2Header. * This constructor is intended to be instantiated by a scram client, and not directly. * The client should be providing the header, and nonce (and probably the user too). * @param gs2Header The GSS-API header * @param user The SCRAM user * @param nonce The nonce for this session * @throws IllegalArgumentException If any of the arguments is null or empty */ public ClientFirstMessage(Gs2Header gs2Header, String user, String nonce) throws IllegalArgumentException { this.gs2Header = checkNotNull(gs2Header, "gs2Header"); this.user = checkNotEmpty(user, "user"); this.nonce = checkNotEmpty(nonce, "nonce"); } private static Gs2Header gs2Header(Gs2CbindFlag gs2CbindFlag, String authzid, String cbindName) { checkNotNull(gs2CbindFlag, "gs2CbindFlag"); if(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED == gs2CbindFlag && null == cbindName) { throw new IllegalArgumentException("Channel binding name is required if channel binding is specified"); } return new Gs2Header(gs2CbindFlag, cbindName, authzid); } /** * Constructs a client-first-message for the given parameters. * Under normal operation, this constructor is intended to be instantiated by a scram client, and not directly. * However, this constructor is more user- or test-friendly, as the arguments are easier to provide without * building other indirect object parameters. * @param gs2CbindFlag The channel-binding flag * @param authzid The optional authzid * @param cbindName The optional channel binding name * @param user The SCRAM user * @param nonce The nonce for this session * @throws IllegalArgumentException If the flag, user or nonce are null or empty */ public ClientFirstMessage(Gs2CbindFlag gs2CbindFlag, String authzid, String cbindName, String user, String nonce) { this(gs2Header(gs2CbindFlag, authzid, cbindName), user, nonce); } /** * Constructs a client-first-message for the given parameters, with no channel binding nor authzid. * Under normal operation, this constructor is intended to be instantiated by a scram client, and not directly. * However, this constructor is more user- or test-friendly, as the arguments are easier to provide without * building other indirect object parameters. * @param user The SCRAM user * @param nonce The nonce for this session * @throws IllegalArgumentException If the user or nonce are null or empty */ public ClientFirstMessage(String user, String nonce) { this(gs2Header(Gs2CbindFlag.CLIENT_NOT, null, null), user, nonce); } public Gs2CbindFlag getChannelBindingFlag() { return gs2Header.getChannelBindingFlag(); } public boolean isChannelBinding() { return gs2Header.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED; } public Optional getChannelBindingName() { return gs2Header.getChannelBindingName(); } public Optional getAuthzid() { return gs2Header.getAuthzid(); } public Gs2Header getGs2Header() { return gs2Header; } public String getUser() { return user; } public String getNonce() { return nonce; } /** * Limited version of the {@link StringWritableCsv#toString()} method, that doesn't write the GS2 header. * This method is useful to construct the auth message used as part of the SCRAM algorithm. * @param sb A StringBuffer where to write the data to. * @return The same StringBuffer */ public StringBuffer writeToWithoutGs2Header(StringBuffer sb) { return StringWritableCsv.writeTo( sb, new ScramAttributeValue(ScramAttributes.USERNAME, ScramStringFormatting.toSaslName(user)), new ScramAttributeValue(ScramAttributes.NONCE, nonce) ); } @Override public StringBuffer writeTo(StringBuffer sb) { StringWritableCsv.writeTo( sb, gs2Header, null // This marks the position of the rest of the elements, required for the "," ); return writeToWithoutGs2Header(sb); } /** * Construct a {@link ClientFirstMessage} instance from a message (String) * @param clientFirstMessage The String representing the client-first-message * @return The instance * @throws ScramParseException If the message is not a valid client-first-message * @throws IllegalArgumentException If the message is null or empty */ public static ClientFirstMessage parseFrom(String clientFirstMessage) throws ScramParseException, IllegalArgumentException { checkNotEmpty(clientFirstMessage, "clientFirstMessage"); Gs2Header gs2Header = Gs2Header.parseFrom(clientFirstMessage); // Takes first two fields String[] userNonceString; try { userNonceString = StringWritableCsv.parseFrom(clientFirstMessage, 2, 2); } catch (IllegalArgumentException e) { throw new ScramParseException("Illegal series of attributes in client-first-message", e); } ScramAttributeValue user = ScramAttributeValue.parse(userNonceString[0]); if(ScramAttributes.USERNAME.getChar() != user.getChar()) { throw new ScramParseException("user must be the 3rd element of the client-first-message"); } ScramAttributeValue nonce = ScramAttributeValue.parse(userNonceString[1]); if(ScramAttributes.NONCE.getChar() != nonce.getChar()) { throw new ScramParseException("nonce must be the 4th element of the client-first-message"); } return new ClientFirstMessage(gs2Header, user.getValue(), nonce.getValue()); } @Override public String toString() { return writeTo(new StringBuffer()).toString(); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/message/ServerFinalMessage.java000066400000000000000000000202551311733162300323020ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.*; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.util.StringWritable; import com.ongres.scram.common.util.StringWritableCsv; import java.util.Arrays; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Constructs and parses server-final-messages. Formal syntax is: * * {@code * server-error = "e=" server-error-value * * server-error-value = "invalid-encoding" / * "extensions-not-supported" / ; unrecognized 'm' value * "invalid-proof" / * "channel-bindings-dont-match" / * "server-does-support-channel-binding" / * ; server does not support channel binding * "channel-binding-not-supported" / * "unsupported-channel-binding-type" / * "unknown-user" / * "invalid-username-encoding" / * ; invalid username encoding (invalid UTF-8 or * ; SASLprep failed) * "no-resources" / * "other-error" / * server-error-value-ext * ; Unrecognized errors should be treated as "other-error". * ; In order to prevent information disclosure, the server * ; may substitute the real reason with "other-error". * * server-error-value-ext = value * ; Additional error reasons added by extensions * ; to this document. * * verifier = "v=" base64 * ;; base-64 encoded ServerSignature. * * server-final-errorMessage = (server-error / verifier) * ["," extensions] * } * * Note that extensions are not supported (and, consequently, error message extensions). * * @see [RFC5802] Section 7 */ public class ServerFinalMessage implements StringWritable { /** * Possible error messages sent on a server-final-message. */ public enum Error { INVALID_ENCODING("invalid-encoding"), EXTENSIONS_NOT_SUPPORTED("extensions-not-supported"), INVALID_PROOF("invalid-proof"), CHANNEL_BINDINGS_DONT_MATCH("channel-bindings-dont-match"), SERVER_DOES_SUPPORT_CHANNEL_BINDING("server-does-support-channel-binding"), CHANNEL_BINDING_NOT_SUPPORTED("channel-binding-not-supported"), UNSUPPORTED_CHANNEL_BINDING_TYPE("unsupported-channel-binding-type"), UNKNOWN_USER("unknown-user"), INVALID_USERNAME_ENCODING("invalid-username-encoding"), NO_RESOURCES("no-resources"), OTHER_ERROR("other-error") ; private static final Map BY_NAME_MAPPING = Arrays.stream(values()).collect(Collectors.toMap(v -> v.errorMessage, v -> v)); private final String errorMessage; Error(String errorMessage) { this.errorMessage = errorMessage; } public String getErrorMessage() { return errorMessage; } public static Error getByErrorMessage(String errorMessage) throws IllegalArgumentException { checkNotEmpty(errorMessage, "errorMessage"); if(! BY_NAME_MAPPING.containsKey(errorMessage)) { throw new IllegalArgumentException("Invalid error message '" + errorMessage + "'"); } return BY_NAME_MAPPING.get(errorMessage); } } private final Optional verifier; private final Optional error; /** * Constructs a server-final-message with no errors, and the provided server verifier * @param verifier The bytes of the computed signature * @throws IllegalArgumentException If the verifier is null */ public ServerFinalMessage(byte[] verifier) throws IllegalArgumentException { this.verifier = Optional.of(checkNotNull(verifier, "verifier")); this.error = Optional.empty(); } /** * Constructs a server-final-message which represents a SCRAM error. * @param error The error * @throws IllegalArgumentException If the error is null */ public ServerFinalMessage(Error error) throws IllegalArgumentException { this.error = Optional.of(checkNotNull(error, "error")); this.verifier = Optional.empty(); } /** * Whether this server-final-message contains an error * @return True if it contains an error, false if it contains a verifier */ public boolean isError() { return error.isPresent(); } public Optional getVerifier() { return verifier; } public Optional getError() { return error; } @Override public StringBuffer writeTo(StringBuffer sb) { return StringWritableCsv.writeTo( sb, isError() ? new ScramAttributeValue(ScramAttributes.ERROR, error.get().errorMessage) : new ScramAttributeValue( ScramAttributes.SERVER_SIGNATURE, ScramStringFormatting.base64Encode(verifier.get()) ) ); } /** * Parses a server-final-message from a String. * @param serverFinalMessage The message * @return A constructed server-final-message instance * @throws ScramParseException If the argument is not a valid server-final-message * @throws IllegalArgumentException If the message is null or empty */ public static ServerFinalMessage parseFrom(String serverFinalMessage) throws ScramParseException, IllegalArgumentException { checkNotEmpty(serverFinalMessage, "serverFinalMessage"); String[] attributeValues = StringWritableCsv.parseFrom(serverFinalMessage, 1, 0); if(attributeValues == null || attributeValues.length != 1) { throw new ScramParseException("Invalid server-final-message"); } ScramAttributeValue attributeValue = ScramAttributeValue.parse(attributeValues[0]); if(ScramAttributes.SERVER_SIGNATURE.getChar() == attributeValue.getChar()) { byte[] verifier = ScramStringFormatting.base64Decode(attributeValue.getValue()); return new ServerFinalMessage(verifier); } else if(ScramAttributes.ERROR.getChar() == attributeValue.getChar()) { return new ServerFinalMessage(Error.getByErrorMessage(attributeValue.getValue())); } else { throw new ScramParseException( "Invalid server-final-message: it must contain either a verifier or an error attribute" ); } } @Override public String toString() { return writeTo(new StringBuffer()).toString(); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/message/ServerFirstMessage.java000066400000000000000000000147211311733162300323410ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.ScramAttributeValue; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.util.StringWritable; import com.ongres.scram.common.util.StringWritableCsv; import static com.ongres.scram.common.util.Preconditions.checkArgument; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; /** * Constructs and parses server-first-messages. Formal syntax is: * * {@code * server-first-message = [reserved-mext ","] nonce "," salt "," * iteration-count ["," extensions] * } * * Note that extensions are not supported. * * @see [RFC5802] Section 7 */ public class ServerFirstMessage implements StringWritable { /** * Minimum allowed value for the iteration, as per the RFC. */ public static final int ITERATION_MIN_VALUE = 4096; private final String clientNonce; private final String serverNonce; private final String salt; private final int iteration; /** * Constructs a server-first-message from a client-first-message and the additional required data. * @param clientNonce String representing the client-first-message * @param serverNonce Server serverNonce * @param salt The salt * @param iteration The iteration count (must be <= 4096) * @throws IllegalArgumentException If clientFirstMessage, serverNonce or salt are null or empty, * or iteration < 4096 */ public ServerFirstMessage( String clientNonce, String serverNonce, String salt, int iteration ) throws IllegalArgumentException { this.clientNonce = checkNotEmpty(clientNonce, "clientNonce"); this.serverNonce = checkNotEmpty(serverNonce, "serverNonce"); this.salt = checkNotEmpty(salt, "salt"); checkArgument(iteration >= ITERATION_MIN_VALUE, "iteration must be >= " + ITERATION_MIN_VALUE); this.iteration = iteration; } public String getClientNonce() { return clientNonce; } public String getServerNonce() { return serverNonce; } public String getNonce() { return clientNonce + serverNonce; } public String getSalt() { return salt; } public int getIteration() { return iteration; } @Override public StringBuffer writeTo(StringBuffer sb) { return StringWritableCsv.writeTo( sb, new ScramAttributeValue(ScramAttributes.NONCE, getNonce()), new ScramAttributeValue(ScramAttributes.SALT, salt), new ScramAttributeValue(ScramAttributes.ITERATION, iteration + "") ); } /** * Parses a server-first-message from a String. * @param serverFirstMessage The string representing the server-first-message * @param clientNonce The serverNonce that is present in the client-first-message * @return The parsed instance * @throws ScramParseException If the argument is not a valid server-first-message * @throws IllegalArgumentException If either argument is empty or serverFirstMessage is not a valid message */ public static ServerFirstMessage parseFrom(String serverFirstMessage, String clientNonce) throws ScramParseException, IllegalArgumentException { checkNotEmpty(serverFirstMessage, "serverFirstMessage"); checkNotEmpty(clientNonce, "clientNonce"); String[] attributeValues = StringWritableCsv.parseFrom(serverFirstMessage, 3, 0); if(attributeValues.length != 3) { throw new ScramParseException("Invalid server-first-message"); } ScramAttributeValue nonce = ScramAttributeValue.parse(attributeValues[0]); if(ScramAttributes.NONCE.getChar() != nonce.getChar()) { throw new ScramParseException("serverNonce must be the 1st element of the server-first-message"); } if(! nonce.getValue().startsWith(clientNonce)) { throw new ScramParseException("parsed serverNonce does not start with client serverNonce"); } ScramAttributeValue salt = ScramAttributeValue.parse(attributeValues[1]); if(ScramAttributes.SALT.getChar() != salt.getChar()) { throw new ScramParseException("salt must be the 2nd element of the server-first-message"); } ScramAttributeValue iteration = ScramAttributeValue.parse(attributeValues[2]); if(ScramAttributes.ITERATION.getChar() != iteration.getChar()) { throw new ScramParseException("iteration must be the 3rd element of the server-first-message"); } int iterationInt; try { iterationInt = Integer.parseInt(iteration.getValue()); } catch (NumberFormatException e) { throw new ScramParseException("invalid iteration"); } return new ServerFirstMessage( clientNonce, nonce.getValue().substring(clientNonce.length()), salt.getValue(), iterationInt ); } @Override public String toString() { return writeTo(new StringBuffer()).toString(); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/stringprep/000077500000000000000000000000001311733162300264575ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparation.java000066400000000000000000000034021311733162300327740ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.stringprep; /** * Interface for all possible String Preparations mechanisms. */ public interface StringPreparation { /** * Normalize a UTF-8 String according to this String Preparation rules. * @param value The String to prepare * @return The prepared String * @throws IllegalArgumentException If the String to prepare is not valid. */ String normalize(String value) throws IllegalArgumentException; } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/stringprep/StringPreparations.java000066400000000000000000000050501311733162300331600ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.stringprep; import com.ongres.scram.common.util.UsAsciiUtils; import static com.ongres.scram.common.util.Preconditions.checkNotEmpty; public enum StringPreparations implements StringPreparation { /** * Implementation of StringPreparation that performs no preparation. * Non US-ASCII characters will produce an exception. * Even though the [RFC5802] is not very clear about it, * this implementation will normalize non-printable US-ASCII characters similarly to what SaslPrep does * (i.e., removing them). */ NO_PREPARATION { @Override protected String doNormalize(String value) throws IllegalArgumentException { return UsAsciiUtils.toPrintable(value); } } ; protected abstract String doNormalize(String value) throws IllegalArgumentException; public String normalize(String value) throws IllegalArgumentException { checkNotEmpty(value, "value"); String normalized = doNormalize(value); if(null == normalized || normalized.isEmpty()) { throw new IllegalArgumentException("null or empty value after normalization"); } return normalized; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/000077500000000000000000000000001311733162300252375ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/AbstractCharAttributeValue.java000066400000000000000000000047331311733162300333330ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Construct and write generic CharAttribute-Value pairs. * * Concrete sub-classes should also provide a static parse(String) creation method. */ public class AbstractCharAttributeValue extends AbstractStringWritable implements CharAttributeValue { private final CharAttribute charAttribute; private final String value; public AbstractCharAttributeValue(CharAttribute charAttribute, String value) throws IllegalArgumentException { this.charAttribute = checkNotNull(charAttribute, "attribute"); if(null != value && value.isEmpty()) { throw new IllegalArgumentException("Value should be either null or non-empty"); } this.value = value; } @Override public char getChar() { return charAttribute.getChar(); } @Override public String getValue() { return value; } @Override public StringBuffer writeTo(StringBuffer sb) { sb.append(charAttribute.getChar()); if(null != value) { sb.append('=').append(value); } return sb; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/AbstractStringWritable.java000066400000000000000000000031411311733162300325250ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; /** * Basic implementation of the StringWritable interface, that overrides the toString() method. */ public abstract class AbstractStringWritable implements StringWritable { public String toString() { return writeTo(new StringBuffer()).toString(); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/CharAttribute.java000066400000000000000000000031261311733162300306450ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; /** * Represents an attribute (a key name) that is represented by a single char. */ public interface CharAttribute { /** * Return the char used to represent this attribute * @return The character of the attribute */ char getChar(); } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/CharAttributeValue.java000066400000000000000000000033041311733162300316400ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; /** * Augments a {@link CharAttribute} with a String value and the method(s) to write its data to a StringBuffer. */ public interface CharAttributeValue extends CharAttribute, StringWritable { /** * Returns the value associated with the {@link CharAttribute} * @return The String value or null if no value is associated */ String getValue(); } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/CryptoUtil.java000066400000000000000000000153721311733162300302300ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import static com.ongres.scram.common.util.Preconditions.checkArgument; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Utility static methods for cryptography related tasks. */ public class CryptoUtil { private static final int MIN_ASCII_PRINTABLE_RANGE = 0x21; private static final int MAX_ASCII_PRINTABLE_RANGE = 0x7e; private static final int EXCLUDED_CHAR = (int) ','; // 0x2c private static class SecureRandomHolder { private static final SecureRandom INSTANCE = new SecureRandom(); } /** * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except comma (','). * @param size The length of the nonce, in characters/bytes * @param random The SecureRandom to use * @return The String representing the nonce */ public static String nonce(int size, SecureRandom random) { if(size <= 0) { throw new IllegalArgumentException("Size must be positive"); } char[] chars = new char[size]; int r; for(int i = 0; i < size;) { r = random.nextInt(MAX_ASCII_PRINTABLE_RANGE - MIN_ASCII_PRINTABLE_RANGE + 1) + MIN_ASCII_PRINTABLE_RANGE; if(r != EXCLUDED_CHAR) { chars[i++] = (char) r; } } return new String(chars); } /** * Generates a random string (called a 'nonce'), composed of ASCII printable characters, except comma (','). * It uses a default SecureRandom instance. * @param size The length of the nonce, in characters/bytes * @return The String representing the nonce */ public static String nonce(int size) { return nonce(size, SecureRandomHolder.INSTANCE); } /** * Compute the "Hi" function for SCRAM. * * {@code * Hi(str, salt, i): * * U1 := HMAC(str, salt + INT(1)) * U2 := HMAC(str, U1) * ... * Ui-1 := HMAC(str, Ui-2) * Ui := HMAC(str, Ui-1) * * Hi := U1 XOR U2 XOR ... XOR Ui * * where "i" is the iteration count, "+" is the string concatenation * operator, and INT(g) is a 4-octet encoding of the integer g, most * significant octet first. * * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the * pseudorandom function (PRF) and with dkLen == output length of * HMAC() == output length of H(). * } * * @param secretKeyFactory The SecretKeyFactory to generate the SecretKey * @param keyLength The length of the key (in bits) * @param value The String to compute the Hi function * @param salt The salt * @param iterations The number of iterations * @return The bytes of the computed Hi value */ public static byte[] hi( SecretKeyFactory secretKeyFactory, int keyLength, String value, byte[] salt, int iterations ) { try { PBEKeySpec spec = new PBEKeySpec(value.toCharArray(), salt, iterations, keyLength); SecretKey key = secretKeyFactory.generateSecret(spec); return key.getEncoded(); } catch(InvalidKeySpecException e) { throw new RuntimeException("Platform error: unsupported PBEKeySpec"); } } /** * Computes the HMAC of a given message. * * {@code * HMAC(key, str): Apply the HMAC keyed hash algorithm (defined in * [RFC2104]) using the octet string represented by "key" as the key * and the octet string "str" as the input string. The size of the * result is the hash result size for the hash function in use. For * example, it is 20 octets for SHA-1 (see [RFC3174]). * } * * @param secretKeySpec A key of the given algorithm * @param mac A MAC instance of the given algorithm * @param message The message to compute the HMAC * @return The bytes of the computed HMAC value */ public static byte[] hmac(SecretKeySpec secretKeySpec, Mac mac, byte[] message) { try { mac.init(secretKeySpec); } catch (InvalidKeyException e) { throw new RuntimeException("Platform error: unsupported key for HMAC algorithm"); } return mac.doFinal(message); } /** * Computes a byte-by-byte xor operation. * * {@code * XOR: Apply the exclusive-or operation to combine the octet string * on the left of this operator with the octet string on the right of * this operator. The length of the output and each of the two * inputs will be the same for this use. * } * * @param value1 * @param value2 * @return * @throws IllegalArgumentException */ public static byte[] xor(byte[] value1, byte[] value2) throws IllegalArgumentException { checkNotNull(value1, "value1"); checkNotNull(value2, "value2"); checkArgument(value1.length == value2.length, "Both values must have the same length"); byte[] result = new byte[value1.length]; for(int i = 0; i < value1.length; i++) { result[i] = (byte) (value1[i] ^ value2[i]); } return result; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/Preconditions.java000066400000000000000000000071241311733162300307260ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; /** * Simple methods similar to Precondition class. Avoid importing full library. */ public class Preconditions { /** * Checks that the argument is not null. * @param value The value to be checked * @param valueName The name of the value that is checked in the method * @param The type of the value * @return The same value passed as argument * @throws IllegalArgumentException If value is null */ public static T checkNotNull(T value, String valueName) throws IllegalArgumentException { if(null == value) { throw new IllegalArgumentException("Null value for '" + valueName + "'"); } return value; } /** * Checks that the String is not null and not empty * @param value The String to check * @param valueName The name of the value that is checked in the method * @return The same String passed as argument * @throws IllegalArgumentException If value is null or empty */ public static String checkNotEmpty(String value, String valueName) throws IllegalArgumentException { if(checkNotNull(value, valueName).isEmpty()) { throw new IllegalArgumentException("Empty string '" + valueName + "'"); } return value; } /** * Checks that the argument is valid, based in a check boolean condition. * @param check The boolean check * @param valueName The name of the value that is checked in the method * @throws IllegalArgumentException */ public static void checkArgument(boolean check, String valueName) throws IllegalArgumentException { if(! check) { throw new IllegalArgumentException("Argument '" + valueName + "' is not valid"); } } /** * Checks that the integer argument is positive. * @param value The value to be checked * @param valueName The name of the value that is checked in the method * @return The same value passed as argument * @throws IllegalArgumentException If value is null */ public static int gt0(int value, String valueName) throws IllegalArgumentException { if(value <= 0) { throw new IllegalArgumentException("'" + valueName + "' must be positive"); } return value; } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/StringWritable.java000066400000000000000000000032121311733162300310400ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; /** * Interface to denote classes which can write to a StringBuffer. */ public interface StringWritable { /** * Write the class information to the given StringBuffer. * @param sb Where to write the data. * @return The same StringBuffer. */ StringBuffer writeTo(StringBuffer sb); } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/StringWritableCsv.java000066400000000000000000000122751311733162300315250ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import java.util.Arrays; import static com.ongres.scram.common.util.Preconditions.checkNotNull; /** * Helper class to generate Comma Separated Values of {@link StringWritable}s */ public class StringWritableCsv { private static void writeStringWritableToStringBuffer(StringWritable value, StringBuffer sb) { if(null != value) { value.writeTo(sb); } } /** * Write a sequence of {@link StringWritableCsv}s to a StringBuffer. * Null {@link StringWritable}s are not printed, but separator is still used. * Separator is a comma (',') * @param sb The sb to write to * @param values Zero or more attribute-value pairs to write * @return The same sb, with data filled in (if any) * @throws IllegalArgumentException If sb is null */ public static StringBuffer writeTo(StringBuffer sb, StringWritable... values) throws IllegalArgumentException { checkNotNull(sb, "sb"); if(null == values || values.length == 0) { return sb; } writeStringWritableToStringBuffer(values[0], sb); int i = 1; while (i < values.length) { sb.append(','); writeStringWritableToStringBuffer(values[i], sb); i++; } return sb; } /** * Parse a String with a {@link StringWritableCsv} into its composing Strings * represented as Strings. No validation is performed on the individual attribute-values returned. * @param value The String with the set of attribute-values * @param n Number of entries to return (entries will be null of there were not enough). 0 means unlimited * @param offset How many entries to skip before start returning * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null or either n or offset are negative */ public static String[] parseFrom(String value, int n, int offset) throws IllegalArgumentException { checkNotNull(value, "value"); if(n < 0 || offset < 0) { throw new IllegalArgumentException("Limit and offset have to be >= 0"); } if(value.isEmpty()) { return new String[0]; } String[] split = value.split(","); if(split.length < offset) { throw new IllegalArgumentException("Not enough items for the given offset"); } return Arrays.copyOfRange( split, offset, (n == 0 ? split.length : n) + offset ); } /** * Parse a String with a {@link StringWritableCsv} into its composing Strings * represented as Strings. No validation is performed on the individual attribute-values returned. * Elements are returned starting from the first available attribute-value. * @param value The String with the set of attribute-values * @param n Number of entries to return (entries will be null of there were not enough). 0 means unlimited * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null or n is negative */ public static String[] parseFrom(String value, int n) throws IllegalArgumentException { return parseFrom(value, n, 0); } /** * Parse a String with a {@link StringWritableCsv} into its composing Strings * represented as Strings. No validation is performed on the individual attribute-values returned. * All the available attribute-values will be returned. * @param value The String with the set of attribute-values * @return An array of Strings which represent the individual attribute-values * @throws IllegalArgumentException If value is null */ public static String[] parseFrom(String value) throws IllegalArgumentException{ return parseFrom(value, 0, 0); } } scram-1.0.0-beta.2/common/src/main/java/com/ongres/scram/common/util/UsAsciiUtils.java000066400000000000000000000045111311733162300304640ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import static com.ongres.scram.common.util.Preconditions.checkNotNull; public class UsAsciiUtils { /** * Removes non-printable characters from the US-ASCII String. * @param value The original String * @return The possibly modified String, without non-printable US-ASCII characters. * @throws IllegalArgumentException If the String is null or contains non US-ASCII characters. */ public static String toPrintable(String value) throws IllegalArgumentException { checkNotNull(value, "value"); char[] printable = new char[value.length()]; int i = 0; for(char chr : value.toCharArray()) { int c = (int) chr; if (c < 0 || c >= 127) { throw new IllegalArgumentException("value contains character '" + chr + "' which is non US-ASCII"); } else if (c > 32) { printable[i++] = chr; } } return i == value.length() ? value : new String(printable, 0, i); } } scram-1.0.0-beta.2/common/src/test/000077500000000000000000000000001311733162300167245ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/000077500000000000000000000000001311733162300176455ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/000077500000000000000000000000001311733162300204235ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/000077500000000000000000000000001311733162300217205ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/000077500000000000000000000000001311733162300230255ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/000077500000000000000000000000001311733162300243155ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/RfcExample.java000066400000000000000000000054521311733162300272140ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; public class RfcExample { public static final String USER = "user"; public static final String PASSWORD = "pencil"; public static final String CLIENT_NONCE = "fyko+d2lbbFgONRv9qkxdawL"; public static final String CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER = "n=" + USER + ",r=" + CLIENT_NONCE; public static final String CLIENT_FIRST_MESSAGE = "n," + "," + CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER; public static final String SERVER_SALT = "QSXCR+Q6sek8bf92"; public static final int SERVER_ITERATIONS = 4096; public static final String SERVER_NONCE = "3rfcNHYJY1ZVvWVs7j"; public static final String FULL_NONCE = CLIENT_NONCE + SERVER_NONCE; public static final String SERVER_FIRST_MESSAGE = "r=" + FULL_NONCE + ",s=" + SERVER_SALT + ",i=" + SERVER_ITERATIONS; public static final String GS2_HEADER_BASE64 = "biws"; public static final String CLIENT_FINAL_MESSAGE_WITHOUT_PROOF = "c=" + GS2_HEADER_BASE64 + ",r=" + FULL_NONCE; public static final String AUTH_MESSAGE = CLIENT_FIRST_MESSAGE_WITHOUT_GS2_HEADER + "," + SERVER_FIRST_MESSAGE + "," + CLIENT_FINAL_MESSAGE_WITHOUT_PROOF; public static final String CLIENT_FINAL_MESSAGE_PROOF = "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; public static final String CLIENT_FINAL_MESSAGE = CLIENT_FINAL_MESSAGE_WITHOUT_PROOF + ",p=" + CLIENT_FINAL_MESSAGE_PROOF; public static final String SERVER_FINAL_MESSAGE = "v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/ScramAttributeValueTest.java000066400000000000000000000100311311733162300317410ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.message.ServerFinalMessage; import org.junit.Test; import static com.ongres.scram.common.RfcExample.*; import static com.ongres.scram.common.ScramAttributes.CLIENT_PROOF; import static com.ongres.scram.common.ScramAttributes.USERNAME; import static org.junit.Assert.*; public class ScramAttributeValueTest { @Test public void constructorDoesNotAllowNullValue() { try { assertNotNull(new ScramAttributeValue(USERNAME, null)); } catch(IllegalArgumentException e) { return; } fail("A null value must throw an IllegalArgumentException"); } @Test public void parseIllegalValuesStructure() { String[] values = new String[] { null, "", "asdf", "asdf=a", CLIENT_PROOF.getChar() + "=", CLIENT_PROOF.getChar() + ",a" }; int n = 0; for(String value : values) { try { assertNotNull(ScramAttributeValue.parse(value)); } catch(ScramParseException e) { n++; } } assertEquals("Not every illegal value thrown ScramParseException", values.length, n); } @Test public void parseIllegalValuesInvalidSCRAMAttibute() { // SCRAM allows for extensions. If a new attribute is supported and its value has been used below, // test will fail and will need to be fixed String[] values = new String[] { "z=asdfasdf", "!=value" }; int n = 0; for(String value : values) { try { assertNotNull(ScramAttributeValue.parse(value)); } catch(ScramParseException e) { n++; } } assertEquals("Not every illegal value thrown ScramParseException", values.length, n); } @Test public void parseLegalValues() throws ScramParseException { String[] legalValues = new String[] { CLIENT_PROOF.getChar() + "=" + "proof", USERNAME.getChar() + "=" + "username", "n=" + USER, "r=" + CLIENT_NONCE, "r=" + FULL_NONCE, "s=" + SERVER_SALT, "i=" + SERVER_ITERATIONS, "c=" + GS2_HEADER_BASE64, "p=" + CLIENT_FINAL_MESSAGE_PROOF, SERVER_FINAL_MESSAGE, }; for(String value : legalValues) { assertNotNull(ScramAttributeValue.parse(value)); } // Test all possible error messages for(ServerFinalMessage.Error e : ServerFinalMessage.Error.values()) { assertNotNull(ScramAttributeValue.parse("e=" + e.getErrorMessage())); } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/ScramFunctionsTest.java000066400000000000000000000122611311733162300307600ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import com.ongres.scram.common.stringprep.StringPreparations; import org.junit.Test; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Base64; import static com.ongres.scram.common.RfcExample.AUTH_MESSAGE; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; public class ScramFunctionsTest { private static final Base64.Decoder BASE_64_DECODER = Base64.getDecoder(); private void assertBytesEqualsBase64(String expected, byte[] actual) { assertArrayEquals(BASE_64_DECODER.decode(expected), actual); } @Test public void hmac() throws UnsupportedEncodingException { String message = "The quick brown fox jumps over the lazy dog"; byte[] key = "key".getBytes(StandardCharsets.UTF_8); assertBytesEqualsBase64( "3nybhbi3iqa8ino29wqQcBydtNk=", ScramFunctions.hmac(ScramMechanisms.SCRAM_SHA_1, message.getBytes(StandardCharsets.US_ASCII), key) ); assertBytesEqualsBase64( "97yD9DBThCSxMpjmqm+xQ+9NWaFJRhdZl0edvC0aPNg=", ScramFunctions.hmac(ScramMechanisms.SCRAM_SHA_256, message.getBytes(StandardCharsets.US_ASCII), key) ); } private byte[] generateSaltedPassword() { return ScramFunctions.saltedPassword( ScramMechanisms.SCRAM_SHA_1, StringPreparations.NO_PREPARATION, "pencil", BASE_64_DECODER.decode("QSXCR+Q6sek8bf92"), 4096 ); } @Test public void saltedPassword() { assertBytesEqualsBase64("HZbuOlKbWl+eR8AfIposuKbhX30=", generateSaltedPassword()); } private byte[] generateClientKey() { return ScramFunctions.clientKey(ScramMechanisms.SCRAM_SHA_1, generateSaltedPassword()); } @Test public void clientKey() { assertBytesEqualsBase64("4jTEe/bDZpbdbYUrmaqiuiZVVyg=", generateClientKey()); } private byte[] generateStoredKey() { return ScramFunctions.storedKey(ScramMechanisms.SCRAM_SHA_1, generateClientKey()); } @Test public void storedKey() { assertBytesEqualsBase64("6dlGYMOdZcOPutkcNY8U2g7vK9Y=", generateStoredKey()); } private byte[] generateServerKey() { return ScramFunctions.serverKey(ScramMechanisms.SCRAM_SHA_1, generateSaltedPassword()); } @Test public void serverKey() { assertBytesEqualsBase64("D+CSWLOshSulAsxiupA+qs2/fTE=", generateServerKey()); } private byte[] generateClientSignature() { return ScramFunctions.clientSignature(ScramMechanisms.SCRAM_SHA_1, generateStoredKey(), AUTH_MESSAGE); } @Test public void clientSignature() { assertBytesEqualsBase64("XXE4xIawv6vfSePi2ovW5cedthM=", generateClientSignature()); } private byte[] generateClientProof() { return ScramFunctions.clientProof(generateClientKey(), generateClientSignature()); } @Test public void clientProof() { assertBytesEqualsBase64("v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", generateClientProof()); } private byte[] generateServerSignature() { return ScramFunctions.serverSignature(ScramMechanisms.SCRAM_SHA_1, generateServerKey(), AUTH_MESSAGE); } @Test public void serverSignature() { assertBytesEqualsBase64("rmF9pqV8S7suAoZWja4dJRkFsKQ=", generateServerSignature()); } @Test public void verifyClientProof() { assertTrue( ScramFunctions.verifyClientProof( ScramMechanisms.SCRAM_SHA_1, generateClientProof(), generateStoredKey(), AUTH_MESSAGE ) ); } @Test public void verifyServerSignature() { assertTrue( ScramFunctions.verifyServerSignature( ScramMechanisms.SCRAM_SHA_1, generateServerKey(), AUTH_MESSAGE, generateServerSignature() ) ); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/ScramMechanismsTest.java000066400000000000000000000116411311733162300311000ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import org.junit.Test; import javax.crypto.Mac; import java.security.MessageDigest; import java.util.Arrays; import java.util.Optional; import java.util.function.Predicate; import static org.junit.Assert.*; public class ScramMechanismsTest { @Test public void TestHashSupportedByJVM() { MessageDigest messageDigest; for(ScramMechanisms scramMechanism : ScramMechanisms.values()) { try { messageDigest = scramMechanism.getMessageDigestInstance(); } catch(RuntimeException ex) { fail(ex.getMessage()); return; } assertNotNull("got a null MessageDigest", messageDigest); assertEquals( "algorithm name and obtained algorithm name differ", scramMechanism.getHashAlgorithmName(), messageDigest.getAlgorithm() ); } } @Test public void TestHMACSupportedByJVM() { Mac hmac; for(ScramMechanisms scramMechanism : ScramMechanisms.values()) { try { hmac = scramMechanism.getMacInstance(); } catch(RuntimeException ex) { fail(ex.getMessage()); return; } assertNotNull("got a null HMAC", hmac); assertEquals( "algorithm name and obtained algorithm name differ", scramMechanism.getHmacAlgorithmName(), hmac.getAlgorithm() ); } } private void testNames(String[] names, Predicate> predicate) { assertEquals( names.length, Arrays.stream(names).map(s -> ScramMechanisms.byName(s)).filter(predicate).count() ); } @Test public void byNameValid() { testNames( new String[] { "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" }, v -> v.isPresent() ); } @Test public void byNameInvalid() { testNames( new String[] { "SCRAM-SHA", "SHA-1-PLUS", "SCRAM-SHA-256-", "SCRAM-SHA-256-PLUS!" }, v -> ! v.isPresent() ); } private void selectMatchingMechanismTest(ScramMechanisms scramMechanisms, boolean channelBinding, String... names) { assertEquals( scramMechanisms, ScramMechanisms.selectMatchingMechanism(channelBinding, names).orElse(null) ); } @Test public void selectMatchingMechanism() { selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_1, false, "SCRAM-SHA-1" ); selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_256_PLUS, true, "SCRAM-SHA-256-PLUS" ); selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_256, false, "SCRAM-SHA-1", "SCRAM-SHA-256" ); selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_256, false, "SCRAM-SHA-1", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" ); selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_1_PLUS, true, "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256" ); selectMatchingMechanismTest( ScramMechanisms.SCRAM_SHA_256_PLUS, true, "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" ); selectMatchingMechanismTest( null, true, "SCRAM-SHA-1", "SCRAM-SHA-256" ); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/ScramStringFormattingTest.java000066400000000000000000000065421311733162300323160ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ScramStringFormattingTest { private static final String[] VALUES_NO_CHARS_TO_BE_ESCAPED = new String[] { "asdf", "''--%%21", " ttt???" }; private static final String[] VALUES_TO_BE_ESCAPED = new String[] { ",", "=", "a,b", "===", "a=", ",=,", "=2C", "=3D" }; private static final String[] ESCAPED_VALUES = new String[] { "=2C", "=3D", "a=2Cb", "=3D=3D=3D", "a=3D", "=2C=3D=2C", "=3D2C", "=3D3D" }; private static final String[] INVALID_SASL_NAMES = new String[] { "=", "as,df", "a=b", " ttt???=2D" }; @Test public void toSaslNameNoCharactersToBeEscaped() { for(String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { assertEquals(s, ScramStringFormatting.toSaslName(s)); } } @Test public void toSaslNameWithCharactersToBeEscaped() { for(int i = 0; i < VALUES_TO_BE_ESCAPED.length; i++) { assertEquals(ESCAPED_VALUES[i], ScramStringFormatting.toSaslName(VALUES_TO_BE_ESCAPED[i])); } } @Test public void fromSaslNameNoCharactersToBeEscaped() { for(String s : VALUES_NO_CHARS_TO_BE_ESCAPED) { assertEquals(s, ScramStringFormatting.fromSaslName(s)); } } @Test public void fromSaslNameWithCharactersToBeUnescaped() { for(int i = 0; i < ESCAPED_VALUES.length; i++) { assertEquals(VALUES_TO_BE_ESCAPED[i], ScramStringFormatting.fromSaslName(ESCAPED_VALUES[i])); } } @Test public void fromSaslNameWithInvalidCharacters() { int n = 0; for(String s : INVALID_SASL_NAMES) { try { assertEquals(s, ScramStringFormatting.fromSaslName(s)); } catch (IllegalArgumentException e) { n++; } } assertTrue("Not all values produced IllegalArgumentException", n == INVALID_SASL_NAMES.length); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/gssapi/000077500000000000000000000000001311733162300256035ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/gssapi/Gs2AttributeValueTest.java000066400000000000000000000060521311733162300326250ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import org.junit.Test; import static org.junit.Assert.*; public class Gs2AttributeValueTest { @Test public void constructorAllowsNullValue() { try { assertNotNull(new Gs2AttributeValue(Gs2Attributes.CHANNEL_BINDING_REQUIRED, null)); } catch(IllegalArgumentException e) { fail("A null value is valid and cannot throw an IllegalArgumentException"); } } @Test public void parseNullValue() { assertNull(Gs2AttributeValue.parse(null)); } @Test public void parseIllegalValuesStructure() { String[] values = new String[] { "", "as", "asdfjkl", Gs2Attributes.CHANNEL_BINDING_REQUIRED.getChar() + "=" }; int n = 0; for(String value : values) { try { assertNotNull(Gs2AttributeValue.parse(value)); } catch(IllegalArgumentException e) { n++; } } assertEquals("Not every illegal value thrown IllegalArgumentException", values.length, n); } @Test public void parseIllegalValuesInvalidGS2Attibute() { String[] values = new String[] { "z=asdfasdf", "i=value" }; int n = 0; for(String value : values) { try { assertNotNull(Gs2AttributeValue.parse(value)); } catch(IllegalArgumentException e) { n++; } } assertEquals("Not every illegal value thrown IllegalArgumentException", values.length, n); } @Test public void parseLegalValues() { String[] values = new String[] { "n", "y", "p=value", "a=authzid" }; for(String value : values) { assertNotNull(Gs2AttributeValue.parse(value)); } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/gssapi/Gs2HeaderTest.java000066400000000000000000000073461311733162300310640ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.gssapi; import org.junit.Test; import static org.junit.Assert.assertEquals; public class Gs2HeaderTest { private static final String[] VALID_GS2HEADER_STRINGS = new String[] { "n,", "y,", "n,a=blah", "p=cb,", "p=cb,a=b" }; private static final Gs2Header[] VALID_GS_2_HEADERS = new Gs2Header[] { new Gs2Header(Gs2CbindFlag.CLIENT_NOT), new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT), new Gs2Header(Gs2CbindFlag.CLIENT_NOT, null, "blah"), new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "cb"), new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "cb", "b") }; private void assertGS2Header(String expected, Gs2Header gs2Header) { assertEquals(expected, gs2Header.writeTo(new StringBuffer()).toString()); } @Test public void constructorValid() { for(int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { assertGS2Header(VALID_GS2HEADER_STRINGS[i], VALID_GS_2_HEADERS[i]); } } @Test(expected = IllegalArgumentException.class) public void constructorInvalid1() { new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null); } @Test(expected = IllegalArgumentException.class) public void constructorInvalid2() { new Gs2Header(Gs2CbindFlag.CLIENT_NOT, "blah"); } @Test(expected = IllegalArgumentException.class) public void constructorInvalid3() { new Gs2Header(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, "blah"); } @Test(expected = IllegalArgumentException.class) public void constructorInvalid4() { new Gs2Header(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "b"); } @Test public void parseFromInvalid() { String[] invalids = new String[] { "Z,", "n,Z=blah", "p,", "n=a," }; int n = 0; for(String invalid : invalids) { try { Gs2Header.parseFrom(invalid); System.out.println(invalid); } catch (IllegalArgumentException e) { n++; } } assertEquals(invalids.length, n); } @Test public void parseFromValid() { for(int i = 0; i < VALID_GS2HEADER_STRINGS.length; i++) { assertGS2Header( VALID_GS_2_HEADERS[i].writeTo(new StringBuffer()).toString(), Gs2Header.parseFrom(VALID_GS2HEADER_STRINGS[i]) ); } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/message/000077500000000000000000000000001311733162300257415ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/message/ClientFinalMessageTest.java000066400000000000000000000036601311733162300331460ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.RfcExample; import com.ongres.scram.common.gssapi.Gs2CbindFlag; import com.ongres.scram.common.gssapi.Gs2Header; import org.junit.Test; import java.util.Optional; import static org.junit.Assert.assertEquals; public class ClientFinalMessageTest { @Test public void writeToWithoutProofValid() { StringBuffer sb = ClientFinalMessage.writeToWithoutProof( new Gs2Header(Gs2CbindFlag.CLIENT_NOT), Optional.empty(), RfcExample.FULL_NONCE ); assertEquals(RfcExample.CLIENT_FINAL_MESSAGE_WITHOUT_PROOF, sb.toString()); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/message/ClientFirstMessageTest.java000066400000000000000000000137011311733162300332010ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.gssapi.Gs2CbindFlag; import org.junit.Test; import static com.ongres.scram.common.RfcExample.CLIENT_NONCE; import static org.junit.Assert.*; public class ClientFirstMessageTest { @Test(expected = IllegalArgumentException.class) public void constructorTestInvalid1() { assertNotNull(new ClientFirstMessage(null, "a", CLIENT_NONCE)); } @Test(expected = IllegalArgumentException.class) public void constructorTestInvalid2() { assertNotNull( new ClientFirstMessage(Gs2CbindFlag.CLIENT_NOT, null, "cbind", "a", CLIENT_NONCE) ); } @Test(expected = IllegalArgumentException.class) public void constructorTestInvalid3() { assertNotNull( new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, "cbind", "a", CLIENT_NONCE) ); } @Test(expected = IllegalArgumentException.class) public void constructorTestInvalid4() { assertNotNull(new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, null, "a", CLIENT_NONCE)); } @Test(expected = IllegalArgumentException.class) public void constructorTestInvalid5() { assertNotNull(new ClientFirstMessage(Gs2CbindFlag.CLIENT_NOT, "authzid", null, null, CLIENT_NONCE)); } private void assertClientFirstMessage(String expected, ClientFirstMessage clientFirstMessage) { assertEquals(expected, clientFirstMessage.writeTo(new StringBuffer()).toString()); } @Test public void writeToValidValues() { assertClientFirstMessage( "n,,n=user,r=" + CLIENT_NONCE, new ClientFirstMessage("user", CLIENT_NONCE) ); assertClientFirstMessage( "y,,n=user,r=" + CLIENT_NONCE, new ClientFirstMessage(Gs2CbindFlag.CLIENT_YES_SERVER_NOT, null, null, "user", CLIENT_NONCE) ); assertClientFirstMessage( "p=blah,,n=user,r=" + CLIENT_NONCE, new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, null, "blah", "user", CLIENT_NONCE) ); assertClientFirstMessage( "p=blah,a=authzid,n=user,r=" + CLIENT_NONCE, new ClientFirstMessage(Gs2CbindFlag.CHANNEL_BINDING_REQUIRED, "authzid", "blah", "user", CLIENT_NONCE) ); } @Test public void parseFromValidValues() throws ScramParseException { ClientFirstMessage m1 = ClientFirstMessage.parseFrom("n,,n=user,r=" + CLIENT_NONCE); assertTrue( ! m1.isChannelBinding() && m1.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_NOT && ! m1.getAuthzid().isPresent() && "user".equals(m1.getUser()) && CLIENT_NONCE.equals(m1.getNonce()) ); ClientFirstMessage m2 = ClientFirstMessage.parseFrom("y,,n=user,r=" + CLIENT_NONCE); assertTrue( ! m2.isChannelBinding() && m2.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT && ! m2.getAuthzid().isPresent() && "user".equals(m2.getUser()) && CLIENT_NONCE.equals(m2.getNonce()) ); ClientFirstMessage m3 = ClientFirstMessage.parseFrom("y,a=user2,n=user,r=" + CLIENT_NONCE); assertTrue( ! m3.isChannelBinding() && m3.getChannelBindingFlag() == Gs2CbindFlag.CLIENT_YES_SERVER_NOT && m3.getAuthzid().isPresent() && "user2".equals(m3.getAuthzid().get()) && "user".equals(m3.getUser()) && CLIENT_NONCE.equals(m3.getNonce()) ); ClientFirstMessage m4 = ClientFirstMessage.parseFrom("p=channel,a=user2,n=user,r=" + CLIENT_NONCE); assertTrue( m4.isChannelBinding() && m4.getChannelBindingFlag() == Gs2CbindFlag.CHANNEL_BINDING_REQUIRED && m4.getChannelBindingName().isPresent() && "channel".equals(m4.getChannelBindingName().get()) && m4.getAuthzid().isPresent() && "user2".equals(m4.getAuthzid().get()) && "user".equals(m4.getUser()) && CLIENT_NONCE.equals(m4.getNonce()) ); } @Test public void parseFromInvalidValues() { String[] invalidValues = new String[] { "n,,r=user,r=" + CLIENT_NONCE, "n,,z=user,r=" + CLIENT_NONCE, "n,,n=user", "n,", "n,,", "n,,n=user,r", "n,,n=user,r=" }; int n = 0; for(String s : invalidValues) { try { assertNotNull(ClientFirstMessage.parseFrom(s)); } catch (ScramParseException e) { n++; } } assertEquals(invalidValues.length, n); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/message/ServerFinalMessageTest.java000066400000000000000000000066711311733162300332030ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.ScramFunctions; import com.ongres.scram.common.ScramMechanisms; import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.stringprep.StringPreparations; import org.junit.Test; import java.util.Base64; import static com.ongres.scram.common.RfcExample.*; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ServerFinalMessageTest { @Test public void validConstructor() { byte[] serverKey = ScramFunctions.serverKey( ScramMechanisms.SCRAM_SHA_1, StringPreparations.NO_PREPARATION, PASSWORD, Base64.getDecoder().decode(SERVER_SALT), SERVER_ITERATIONS ); ServerFinalMessage serverFinalMessage1 = new ServerFinalMessage( ScramFunctions.serverSignature(ScramMechanisms.SCRAM_SHA_1, serverKey, AUTH_MESSAGE) ); assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); assertFalse(serverFinalMessage1.isError()); ServerFinalMessage serverFinalMessage2 = new ServerFinalMessage(ServerFinalMessage.Error.UNKNOWN_USER); assertEquals(ScramAttributes.ERROR.getChar() + "=" + "unknown-user", serverFinalMessage2.toString()); assertTrue(serverFinalMessage2.isError()); } @Test public void validParseFrom() throws ScramParseException { ServerFinalMessage serverFinalMessage1 = ServerFinalMessage.parseFrom(SERVER_FINAL_MESSAGE); assertEquals(SERVER_FINAL_MESSAGE, serverFinalMessage1.toString()); assertFalse(serverFinalMessage1.isError()); ServerFinalMessage serverFinalMessage2 = ServerFinalMessage.parseFrom("e=channel-binding-not-supported"); assertEquals("e=channel-binding-not-supported", serverFinalMessage2.toString()); assertTrue(serverFinalMessage2.isError()); assertTrue(serverFinalMessage2.getError().get() == ServerFinalMessage.Error.CHANNEL_BINDING_NOT_SUPPORTED); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/message/ServerFirstMessageTest.java000066400000000000000000000043471311733162300332370ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.message; import com.ongres.scram.common.exception.ScramParseException; import org.junit.Test; import static com.ongres.scram.common.RfcExample.CLIENT_NONCE; import static com.ongres.scram.common.RfcExample.SERVER_FIRST_MESSAGE; import static org.junit.Assert.assertEquals; public class ServerFirstMessageTest { @Test public void validConstructor() { ServerFirstMessage serverFirstMessage = new ServerFirstMessage( CLIENT_NONCE, "3rfcNHYJY1ZVvWVs7j", "QSXCR+Q6sek8bf92", 4096 ); assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); } @Test public void validParseFrom() throws ScramParseException { ServerFirstMessage serverFirstMessage = ServerFirstMessage.parseFrom(SERVER_FIRST_MESSAGE, CLIENT_NONCE); assertEquals(SERVER_FIRST_MESSAGE, serverFirstMessage.toString()); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/stringprep/000077500000000000000000000000001311733162300265125ustar00rootroot00000000000000StringPreparationTest.java000066400000000000000000000201101311733162300336030ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/stringprep/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.stringprep; import org.junit.Test; import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class StringPreparationTest { private static final String[] ONLY_NON_PRINTABLE_STRINGS = new String[] { " ", (char) 13 + "", (char) 13 + " " }; @Test public void doNormalizeNullEmpty() { String[] nullEmpty = new String[] { null, "" }; int n = 0; for(StringPreparation stringPreparation : StringPreparations.values()) { for(String s : nullEmpty) { try { stringPreparation.normalize(s); } catch (IllegalArgumentException e) { n++; } } } assertTrue( "IllegalArgumentException not thrown for either null or empty input", n == nullEmpty.length * StringPreparations.values().length ); } @Test public void doNormalizeValidAsciiCases() { // 200 usernames from http://jimpix.co.uk/words/random-username-list.asp String[] validAsciiUsernames = new String[] { "toastingxenotime", "infecttolerant", "cobblerjack", "zekedigital", "freshscarisdale", "lamwaylon", "lagopodousmonkeys", "fanfarecheesy", "willowfinnegan", "canoeamoeba", "stinkeroddball", "terracecomet", "cakebrazos", "headersidesaddle", "cloudultracrepidarian", "grimegastropub", "stallchilli", "shawnapentagon", "chapeltarp", "rydbergninja", "differencegym", "europiummuscle", "swilledonce", "defensivesyntaxis", "desktopredundant", "stakingsky", "goofywaiting", "boundsemm", "pipermonstrous", "faintfrog", "riskinsist", "constantjunkie", "rejectbroth", "ceilbeau", "ponyjaialai", "burnishselfies", "unamusedglenmore", "parmesanporcupine", "suteconcerto", "ribstony", "sassytwelve", "coursesnasturtium", "singlecinders", "kinkben", "chiefpussface", "unknownivery", "robterra", "wearycubes", "bearcontent", "aquifertrip", "insulinlick", "batterypeace", "rubigloo", "fixessnizort", "coalorecheesy", "logodarthvader", "equipmentbizarre", "charitycolne", "gradecomputer", "incrediblegases", "ingotflyingfish", "abaftmounting", "kissingfluke", "chesterdinky", "anthropicdip", "portalcairo", "purebredhighjump", "jamaicansteeping", "skaterscoins", "chondrulelocust", "modespretty", "otisnadrid", "lagoonone", "arrivepayday", "lawfulpatsy", "customersdeleted", "superiorarod", "abackwarped", "footballcyclic", "sawtshortstop", "waskerleysanidine", "polythenehead", "carpacciosierra", "gnashgabcheviot", "plunkarnisdale", "surfacebased", "wickedpark", "capitalistivan", "kinglassmuse", "adultsceiriog", "medrones", "climaxshops", "archeangolfer", "tomfront", "kobeshift", "nettleaugustus", "bitesizedlion", "crickedbunting", "englishrichard", "dangerousdelmonico", "sparklemicrosoft", "kneepadsfold", "enunciatesunglasses", "parchmentsteak", "meigpiton", "puttingcitrusy", "eyehash", "newtonatomiser", "witchesburberry", "positionwobbly", "clipboardamber", "ricolobster", "calendarpetal", "shinywound", "dealemral", "moonrakerfinnish", "banditliberated", "whippedfanatical", "jargongreasy", "yumlayla", "dwarfismtransition", "doleriteduce", "sikickball", "columngymnastics", "draybowmont", "jupitersnorkling", "siderealmolding", "dowdyrosary", "novaskeeter", "whickerpulley", "rutlandsliders", "categoryflossed", "coiltiedogfish", "brandwaren", "altairlatigo", "acruxyouthscape", "harmonicdash", "jasperserver", "slicedaggie", "gravityfern", "bitsstorm", "readymadehobby", "surfeitgrape", "pantheonslabs", "ammandecent", "skicrackers", "speyfashions", "languagedeeno", "pettyconfit", "minutesshimmering", "thinhopeangellist", "sleevelesscadmium", "controlarc", "robinvolvox", "postboxskylark", "tortepleasing", "lutzdillinger", "amnioteperl", "burntmaximize", "gamblingearn", "bumsouch", "coronagraphdown", "bodgeelearning", "hackingscraper", "hartterbium", "mindyurgonian", "leidlebalki", "labelthumbs", "lincolncrisps", "pearhamster", "termsfiona", "tickingsomber", "hatellynfi", "northumberlandgrotesque", "harpistcaramel", "gentryswiss", "illusionnooks", "easilyrows", "highgluten", "backedallegiance", "laelsitesearch", "methodfix", "teethminstral", "chemicalchildish", "likablepace", "alikealeph", "nalasincere", "investbaroque", "conditionenvelope", "splintsmccue", "carnonprompt", "resultharvey", "acceptsheba", "redditmonsoon", "multiplepostbox", "invitationchurch", "drinksgaliath", "ordersvivid", "mugsgit", "clumpingfreak" }; for(StringPreparation stringPreparation : StringPreparations.values()) { for(String s : validAsciiUsernames) { assertEquals(s, stringPreparation.normalize(s)); } } } /* * Some simple random testing won't hurt. If a test would fail, create new test with the generated word. */ @Test public void doNormalizeValidAsciiRandom() { int n = 10 * 1000; int maxLenght = 64; Random random = new Random(); String[] values = new String[n]; for(int i = 0; i < n; i++) { char[] charValue = new char[random.nextInt(maxLenght) + 1]; for(int j = 0; j < charValue.length; j++) { charValue[j] = (char) (random.nextInt(127 - 33) + 33); } values[i] = new String(charValue); } for(StringPreparation stringPreparation : StringPreparations.values()) { for(String s : values) { assertEquals( "'" + s + "' is a printable ASCII string, should not be changed by normalize()", s, stringPreparation.normalize(s) ); } } } @Test public void doNormalizeNoPreparationEmptyAfterNormalization() { int n = 0; for(String s : ONLY_NON_PRINTABLE_STRINGS) { try { StringPreparations.NO_PREPARATION.normalize(s); } catch (IllegalArgumentException e) { n++; } } assertTrue( "IllegalArgumentException not thrown for either null or empty output after normalization", n == ONLY_NON_PRINTABLE_STRINGS.length ); } @Test public void doNormalizeNoPreparationNonEmptyAfterNormalization() { // No exception should be thrown for(String s : ONLY_NON_PRINTABLE_STRINGS) { StringPreparations.NO_PREPARATION.normalize(s + "a"); } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/util/000077500000000000000000000000001311733162300252725ustar00rootroot00000000000000AbstractCharAttributeValueTest.java000066400000000000000000000066141311733162300341470ustar00rootroot00000000000000scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/util/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import org.junit.Test; import static org.junit.Assert.*; public class AbstractCharAttributeValueTest { private class MockCharAttribute implements CharAttribute { private final char c; public MockCharAttribute(char c) { this.c = c; } @Override public char getChar() { return c; } } @Test public void constructorNullAttribute() { try { assertNotNull(new AbstractCharAttributeValue((CharAttribute) null, "value")); } catch(IllegalArgumentException e) { return; } fail("IllegalArgumentException must be thrown if the CharAttribute is null"); } @Test public void constructorEmptyValue() { try { assertNotNull(new AbstractCharAttributeValue(new MockCharAttribute('c'), "")); } catch(IllegalArgumentException e) { return; } fail("IllegalArgumentException must be thrown if the value is empty"); } @Test public void writeToNonNullValues() { String[] legalValues = new String[] { "a", "----", "value" }; char c = 'c'; for(String s : legalValues) { assertEquals( "" + c + '=' + s, new AbstractCharAttributeValue(new MockCharAttribute(c), s).toString() ); } } @Test public void writeToNullValue() { char c = 'd'; assertEquals( "" + c, new AbstractCharAttributeValue(new MockCharAttribute(c), null).toString() ); } @Test public void writeToEscapedValues() { char c = 'a'; MockCharAttribute mockCharAttribute = new MockCharAttribute(c); String[] values = new String[] { "a=b", "c,a", ",", "=,", "=,,=" }; for(int i = 0; i < values.length; i++) { assertEquals( "" + c + '=' + values[i], new AbstractCharAttributeValue(mockCharAttribute, values[i]).toString() ); } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/util/CryptoUtilTest.java000066400000000000000000000045371311733162300311240ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import org.junit.Test; import java.security.SecureRandom; import java.util.Random; import static org.junit.Assert.fail; public class CryptoUtilTest { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @Test(expected = IllegalArgumentException.class) public void nonceInvalidSize1() { CryptoUtil.nonce(0, SECURE_RANDOM); } @Test(expected = IllegalArgumentException.class) public void nonceInvalidSize2() { CryptoUtil.nonce(-1, SECURE_RANDOM); } @Test public void nonceValid() { int nNonces = 1000; int nonceMaxSize = 100; Random random = new Random(); // Some more random testing for(int i = 0; i < nNonces; i++) { for(char c : CryptoUtil.nonce(random.nextInt(nonceMaxSize) + 1, SECURE_RANDOM).toCharArray()) { if(c == ',' || c < (char) 33 || c > (char) 126) { fail("Character c='" + c + "' is not allowed on a nonce"); } } } } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/util/StringWritableCsvTest.java000066400000000000000000000131641311733162300324160ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import com.ongres.scram.common.ScramAttributes; import com.ongres.scram.common.ScramAttributeValue; import com.ongres.scram.common.gssapi.Gs2AttributeValue; import com.ongres.scram.common.gssapi.Gs2Attributes; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class StringWritableCsvTest { private static final String[] ONE_ARG_VALUES = new String[] { "c=channel", "i=4096", "a=authzid", "n" }; private static final String SEVERAL_VALUES_STRING = "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL"; @Test public void writeToNullOrEmpty() { assertTrue(StringWritableCsv.writeTo(new StringBuffer()).length() == 0); assertTrue(StringWritableCsv.writeTo(new StringBuffer(), new CharAttributeValue[]{}).length() == 0); } @Test public void writeToOneArg() { CharAttributeValue[] pairs = new CharAttributeValue[] { new ScramAttributeValue(ScramAttributes.CHANNEL_BINDING, "channel"), new ScramAttributeValue(ScramAttributes.ITERATION, "" + 4096), new Gs2AttributeValue(Gs2Attributes.AUTHZID, "authzid"), new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null) }; for(int i = 0; i < pairs.length; i++) { assertEquals(ONE_ARG_VALUES[i], StringWritableCsv.writeTo(new StringBuffer(), pairs[i]).toString()); } } @Test public void writeToSeveralArgs() { assertEquals( SEVERAL_VALUES_STRING, StringWritableCsv.writeTo( new StringBuffer(), new Gs2AttributeValue(Gs2Attributes.CLIENT_NOT, null), null, new ScramAttributeValue(ScramAttributes.USERNAME, "user"), new ScramAttributeValue(ScramAttributes.NONCE, "fyko+d2lbbFgONRv9qkxdawL") ).toString() ); } @Test public void parseFromEmpty() { assertArrayEquals(new String[]{}, StringWritableCsv.parseFrom("")); } @Test public void parseFromOneArgWithLimitsOffsets() { for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s)); } int[] numberEntries = new int[] { 0, 1 }; for(int n : numberEntries) { for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n)); } } for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3)); } for(int n : numberEntries) { for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s}, StringWritableCsv.parseFrom(s, n, 0)); } } for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] {s, null, null}, StringWritableCsv.parseFrom(s, 3, 0)); } for(int n : numberEntries) { for(String s : ONE_ARG_VALUES) { assertArrayEquals(new String[] { null }, StringWritableCsv.parseFrom(s, n, 1)); } } } @Test public void parseFromSeveralArgsWithLimitsOffsets() { assertArrayEquals( new String[] { "n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL" }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING) ); assertArrayEquals( new String[] { "n", "" }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2) ); assertArrayEquals( new String[] { "", "n=user" }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 1) ); assertArrayEquals( new String[] { "r=fyko+d2lbbFgONRv9qkxdawL", null }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 3) ); assertArrayEquals( new String[] { null, null }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 2, 4) ); assertArrayEquals( new String[] { "n", "", "n=user", "r=fyko+d2lbbFgONRv9qkxdawL", null }, StringWritableCsv.parseFrom(SEVERAL_VALUES_STRING, 5) ); } } scram-1.0.0-beta.2/common/src/test/java/com/ongres/scram/common/util/UsAsciiUtilsTest.java000066400000000000000000000064101311733162300313570ustar00rootroot00000000000000/* * Copyright 2017, OnGres. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package com.ongres.scram.common.util; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; public class UsAsciiUtilsTest { @Test public void toPrintableNull() { try { UsAsciiUtils.toPrintable(null); } catch(IllegalArgumentException ex) { return; } fail("Calling with null value must throw IllegalArgumentException"); } @Test public void toPrintableNonASCII() { String[] nonASCIIStrings = new String[] { "abcdé", "ñ", "€", "Наташа", (char) 127 + "" }; int n = 0; for(String s : nonASCIIStrings) { try { UsAsciiUtils.toPrintable(s); } catch(IllegalArgumentException ex) { n++; } } assertTrue( "String(s) with non-ASCII characters not throwing IllegalArgumentException", n == nonASCIIStrings.length ); } @Test public void toPrintableNonPrintable() { String[] original = new String[] { " u ", "a" + (char) 12, (char) 0 + "ttt" + (char) 31 }; String[] expected = new String[] { "u", "a", "ttt" }; for(int i = 0; i < original.length; i++) { assertEquals("", expected[i], UsAsciiUtils.toPrintable(original[i])); } } @Test public void toPrintableAllPrintable() { List values = new ArrayList(); values.addAll(Arrays.asList( new String[] { (char) 33 + "", "user", "!", "-,.=?", (char) 126 + "" }) ); for(int c = 33; c < 127; c++) { values.add("---" + (char) c + "---"); } for(String s : values) { assertEquals( "All printable String '" + s + "' not returning the same value", s, UsAsciiUtils.toPrintable(s) ); } } } scram-1.0.0-beta.2/pom.xml000066400000000000000000000176421311733162300152150ustar00rootroot00000000000000 4.0.0 com.ongres.scram parent 1.0.0-beta.2 pom common client SCRAM Java Implementation of the Salted Challenge Response Authentication Mechanism (SCRAM) https://github.com/ongres/scram 2017 com.ongres.aht Álvaro Hernández Tortosa aht@ongres.com The 2-Clause BSD License https://opensource.org/licenses/BSD-2-Clause repo scm:git:https://github.com/ongres/scram.git scm:git:git@github.com:ongres/scram.git git@github.com:ongres/scram.git GitHub https://github.com/ongres/scram/issues ossrh-release OSSRH Release repository https://oss.sonatype.org/service/local/staging/deploy/maven2 ossrh-snapshot OSSRH Snapshot repository https://oss.sonatype.org/content/repositories/snapshots UTF-8 1.8 junit junit 4.12 org.apache.maven.plugins maven-compiler-plugin 3.6.1 ${java.version} ${java.version} -Xlint:all -Xlint:-options true true org.sonatype.plugins nexus-staging-maven-plugin 1.6.8 true ossrh https://oss.sonatype.org/ false org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.10.4 attach-javadocs jar safer org.codehaus.mojo findbugs-maven-plugin 3.0.4 Max Low true ${project.build.directory}/findbugs analyze-compile check master-branch org.apache.maven.plugins maven-enforcer-plugin 1.4.1 enforce-release enforce Artifacts on master branch must not be snapshot! sign org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign software@ongres.com