Skip to content

Commit

Permalink
Fix #451: Allow overriding length of decimalized signatures (#452) (#553
Browse files Browse the repository at this point in the history
)

* Fix #451: Allow overriding length of decimalized singatures
- Change code based on code review
- Fix test vector
- Fix JavaDoc
- Add invalid format to exception message
(cherry picked from commit 6d81bf1)
Co-authored-by: Petr Dvořák <petr@wultra.com>
  • Loading branch information
jandusil committed Dec 7, 2023
1 parent 08bc740 commit 0a1cdca
Show file tree
Hide file tree
Showing 12 changed files with 3,137 additions and 47 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<packaging>pom</packaging>

<inceptionYear>2016</inceptionYear>
<url>http://powerauth.com/</url>
<url>https://powerauth.com/</url>

<organization>
<name>Wultra s.r.o.</name>
Expand All @@ -39,7 +39,7 @@
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>

Expand Down
2,604 changes: 2,604 additions & 0 deletions powerauth-docs/test-vectors/signatures-offline.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
package io.getlime.security.powerauth.crypto.client.signature;

import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;
import io.getlime.security.powerauth.crypto.lib.config.SignatureConfiguration;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import io.getlime.security.powerauth.crypto.lib.util.SignatureUtils;
Expand Down Expand Up @@ -44,13 +44,13 @@ public class PowerAuthClientSignature {
* @param data Data to be signed.
* @param signatureKeys A signature keys.
* @param ctrData Hash based counter / index of the derived key KEY_DERIVED.
* @param signatureFormat Format of signature to calculate.
* @param signatureConfiguration Format and parameters of signature to calculate.
* @return PowerAuth signature for given data.
* @throws GenericCryptoException In case signature computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
public String signatureForData(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData, PowerAuthSignatureFormat signatureFormat) throws GenericCryptoException, CryptoProviderException {
return signatureUtils.computePowerAuthSignature(data, signatureKeys, ctrData, signatureFormat);
public String signatureForData(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData, SignatureConfiguration signatureConfiguration) throws GenericCryptoException, CryptoProviderException {
return signatureUtils.computePowerAuthSignature(data, signatureKeys, ctrData, signatureConfiguration);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* PowerAuth Crypto Library
* Copyright 2023 Wultra s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getlime.security.powerauth.crypto.lib.config;

import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;

/**
* Configuration for Base64 signatures.
*
* @author Petr Dvorak, petr@wultra.com
*/
public class Base64SignatureConfiguration extends SignatureConfiguration {

/**
* Constructor with the base64 signature. Package scoped.
*/
Base64SignatureConfiguration() {
super(PowerAuthSignatureFormat.BASE64);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* PowerAuth Crypto Library
* Copyright 2023 Wultra s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getlime.security.powerauth.crypto.lib.config;

import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;

/**
* Configuration for decimal signatures.
*
* @author Petr Dvorak, petr@wultra.com
*/
public class DecimalSignatureConfiguration extends SignatureConfiguration {

private Integer length;

/**
* Constructor with the decimal signature. Package scoped.
*/
DecimalSignatureConfiguration() {
super(PowerAuthSignatureFormat.DECIMAL);
}

/**
* Constructor with signature length. Package scoped.
*
* @param length Length.
*/
DecimalSignatureConfiguration(Integer length) {
super(PowerAuthSignatureFormat.DECIMAL);
this.length = length;
}

/**
* Get length of signature.
*
* @return Length of signature.
*/
public Integer getLength() {
return length;
}

/**
* Set length of the signature.
*
* @param length Length of signature.
*/
public void setLength(Integer length) {
this.length = length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* PowerAuth Crypto Library
* Copyright 2023 Wultra s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getlime.security.powerauth.crypto.lib.config;

import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;

/**
* Class that holds information about the signature configuration.
*
* @author Petr Dvorak, petr@wultra.com
*/
public abstract class SignatureConfiguration {

private final PowerAuthSignatureFormat signatureFormat;

/**
* Constructor with the signature format.
*
* @param signatureFormat Signature format.
*/
public SignatureConfiguration(PowerAuthSignatureFormat signatureFormat) {
this.signatureFormat = signatureFormat;
}

/**
* Get signature format.
* @return Signature format.
*/
public PowerAuthSignatureFormat getSignatureFormat() {
return signatureFormat;
}

public static SignatureConfiguration forFormat(PowerAuthSignatureFormat format) throws CryptoProviderException {
switch (format) {
case BASE64: {
return new Base64SignatureConfiguration();
}
case DECIMAL: {
return new DecimalSignatureConfiguration();
}
}
throw new CryptoProviderException("Invalid or null format provided: " + format);
}

/**
* Construct new decimal signature of default length.
*
* @return Decimal signature with default length.
*/
public static DecimalSignatureConfiguration decimal() {
return new DecimalSignatureConfiguration();
}

/**
* Construct new decimal signature of given length.
*
* @param length Length.
* @return Decimal signature with given length.
*/
public static DecimalSignatureConfiguration decimal(Integer length) {
return new DecimalSignatureConfiguration(length);
}

/**
* Construct new Base64 signature.
*
* @return Base64 signature.
*/
public static Base64SignatureConfiguration base64() {
return new Base64SignatureConfiguration();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import javax.crypto.SecretKey;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.util.Arrays;

/**
* The {@code HashBasedCounterUtils} class provides additional functionality that allows secure transfer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

import com.google.common.base.Joiner;
import com.google.common.io.BaseEncoding;
import io.getlime.security.powerauth.crypto.lib.config.DecimalSignatureConfiguration;
import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration;
import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;
import io.getlime.security.powerauth.crypto.lib.config.SignatureConfiguration;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import org.slf4j.Logger;
Expand Down Expand Up @@ -98,11 +99,27 @@ public boolean validateECDSASignature(byte[] signedBytes, byte[] signature, Publ
* @param data Data to be signed.
* @param signatureKeys Keys for computing the signature.
* @param ctrData Counter byte array / derived key index.
* @param length Required length of the factor related signature component (i.e, if length is 4, then 2FA will
* have 8 digits). Minimal allowed non-null value is 4. Maximum allowed value is 8. If the value
* is null, the default system value (8) is used.
* @return Decimal formatted PowerAuth signature for given data.
* @throws GenericCryptoException In case signature computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
private String computePowerAuthDecimalSignature(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
private String computePowerAuthDecimalSignature(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData, Integer length) throws GenericCryptoException, CryptoProviderException {
// Determine the length of the signature component, validate length
final int signatureDecimalLength;
if (length != null) {
if (length < 4) {
throw new CryptoProviderException("Length must be at least 4, provided: " + length);
}
if (length > 8) {
throw new CryptoProviderException("Length must be less or equal to 8, provided: " + length);
}
signatureDecimalLength = length;
} else {
signatureDecimalLength = PowerAuthConfiguration.SIGNATURE_DECIMAL_LENGTH;
}
// Prepare holder for signature components
final String[] signatureStringComponents = new String[signatureKeys.size()];
// Compute signature components
Expand All @@ -111,8 +128,8 @@ private String computePowerAuthDecimalSignature(byte[] data, List<SecretKey> sig
for (int i = 0; i < signatureComponents.size(); i++) {
final byte[] signatureComponent = signatureComponents.get(i);
int index = signatureComponent.length - 4;
int number = (ByteBuffer.wrap(signatureComponent).getInt(index) & 0x7FFFFFFF) % (int) (Math.pow(10, PowerAuthConfiguration.SIGNATURE_DECIMAL_LENGTH));
signatureStringComponents[i] = String.format("%0" + PowerAuthConfiguration.SIGNATURE_DECIMAL_LENGTH + "d", number);
int number = (ByteBuffer.wrap(signatureComponent).getInt(index) & 0x7FFFFFFF) % (int) (Math.pow(10, signatureDecimalLength));
signatureStringComponents[i] = String.format("%0" + signatureDecimalLength + "d", number);
}
// Join components with dash.
return Joiner.on("-").join(signatureStringComponents);
Expand Down Expand Up @@ -192,12 +209,12 @@ private List<byte[]> computePowerAuthSignatureComponents(byte[] data, List<Secre
* @param data Data to be signed.
* @param signatureKeys Keys for computing the signature.
* @param ctrData Counter byte array / derived key index.
* @param format Format of signature to produce.
* @param configuration Format of signature to produce and parameters for the signature.
* @return PowerAuth signature for given data.
* @throws GenericCryptoException In case signature computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
public String computePowerAuthSignature(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData, PowerAuthSignatureFormat format) throws GenericCryptoException, CryptoProviderException {
public String computePowerAuthSignature(byte[] data, List<SecretKey> signatureKeys, byte[] ctrData, SignatureConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
if (signatureKeys == null) {
throw new GenericCryptoException("Missing signatureKeys parameter");
}
Expand All @@ -210,13 +227,17 @@ public String computePowerAuthSignature(byte[] data, List<SecretKey> signatureKe
if (ctrData.length != PowerAuthConfiguration.SIGNATURE_COUNTER_LENGTH) {
throw new GenericCryptoException("Invalid length of signature counter");
}
switch (format) {
case BASE64:
switch (configuration.getSignatureFormat()) {
case BASE64: {
return computePowerAuthBase64Signature(data, signatureKeys, ctrData);
case DECIMAL:
return computePowerAuthDecimalSignature(data, signatureKeys, ctrData);
default:
}
case DECIMAL: {
final Integer len = ((DecimalSignatureConfiguration)configuration).getLength();
return computePowerAuthDecimalSignature(data, signatureKeys, ctrData, len);
}
default: {
throw new GenericCryptoException("Unsupported format of PowerAuth signature.");
}
}
}

Expand All @@ -227,13 +248,13 @@ public String computePowerAuthSignature(byte[] data, List<SecretKey> signatureKe
* @param signature Data signature.
* @param signatureKeys Keys for signature validation.
* @param ctrData Counter data.
* @param format Format in which signature will be validated.
* @param configuration Format in which signature will be validated and parameters for the validation.
* @return Return "true" if signature matches, "false" otherwise.
* @throws GenericCryptoException In case signature computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
public boolean validatePowerAuthSignature(byte[] data, String signature, List<SecretKey> signatureKeys, byte[] ctrData, PowerAuthSignatureFormat format) throws GenericCryptoException, CryptoProviderException {
return signature.equals(computePowerAuthSignature(data, signatureKeys, ctrData, format));
public boolean validatePowerAuthSignature(byte[] data, String signature, List<SecretKey> signatureKeys, byte[] ctrData, SignatureConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
return signature.equals(computePowerAuthSignature(data, signatureKeys, ctrData, configuration));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.UUID;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
package io.getlime.security.powerauth.crypto.server.signature;

import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;
import io.getlime.security.powerauth.crypto.lib.config.SignatureConfiguration;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import io.getlime.security.powerauth.crypto.lib.util.SignatureUtils;
Expand All @@ -43,13 +43,13 @@ public class PowerAuthServerSignature {
* @param signature Signature for the data.
* @param signatureKeys Keys used for signature.
* @param ctrData Hash based counter / derived signing key index.
* @param signatureFormat Format of signature to verify.
* @param signatureConfiguration Format and parameters of signature to verify.
* @return Returns "true" if the signature matches, "false" otherwise.
* @throws GenericCryptoException In case signature computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
public boolean verifySignatureForData(byte[] data, String signature, List<SecretKey> signatureKeys, byte[] ctrData, PowerAuthSignatureFormat signatureFormat) throws GenericCryptoException, CryptoProviderException {
return signatureUtils.validatePowerAuthSignature(data, signature, signatureKeys, ctrData, signatureFormat);
public boolean verifySignatureForData(byte[] data, String signature, List<SecretKey> signatureKeys, byte[] ctrData, SignatureConfiguration signatureConfiguration) throws GenericCryptoException, CryptoProviderException {
return signatureUtils.validatePowerAuthSignature(data, signature, signatureKeys, ctrData, signatureConfiguration);
}

}
Loading

0 comments on commit 0a1cdca

Please sign in to comment.