se.swedenconnect.signservice.signature.signer.crypto.EcdsaSigValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of signservice-signhandler Show documentation
Show all versions of signservice-signhandler Show documentation
SignService Signature Handler
/*
* Copyright 2022 Sweden Connect
*
* 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 se.swedenconnect.signservice.signature.signer.crypto;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERSequence;
import lombok.Getter;
import lombok.Setter;
/**
* ECDSA Signature value
*/
public class EcdsaSigValue implements ASN1Encodable {
/**
* Supported key lengths for ECDSA keys
*
* @param supportedKeyLengths array of integers providing supported key lengths
* @return array of integers providing supported key lengths
*/
@Getter
@Setter
private static int[] supportedKeyLengths = new int[] { 160, 224, 256, 384, 521 };
/**
* @return the R component of the ECDSA Signature */
@Getter
private final BigInteger r;
/**
* @return the S component of the ECDSA Signature */
@Getter
private final BigInteger s;
/**
* Creates an instance of ECDSA signature value
* @param obj signature value object as {@link ASN1TaggedObject}
* @param explicit indicate if the tagging is explicit
* @return ECDSA signature value
* @throws IOException invalid input
*/
public static EcdsaSigValue getInstance(@Nonnull final ASN1TaggedObject obj, final boolean explicit)
throws IOException {
Objects.requireNonNull(obj, "The input object to the ASN.1 constructor must not be null");
return getInstance(ASN1Sequence.getInstance(obj, explicit));
}
/**
* Creates an instance of ECDSA signature value
* @param obj signature value object as {@link EcdsaSigValue}, {@link ASN1Sequence} or
* {@link ASN1InputStream}
* @return ECDSA signature value
* @throws IOException invalid input
*/
public static EcdsaSigValue getInstance(@Nonnull final Object obj) throws IOException {
Objects.requireNonNull(obj, "The input object to the general constructor must not be null");
if (obj instanceof EcdsaSigValue) {
return (EcdsaSigValue) obj;
}
if (obj instanceof ASN1Sequence) {
return new EcdsaSigValue((ASN1Sequence) obj);
}
if (obj instanceof ASN1InputStream) {
return new EcdsaSigValue(ASN1Sequence.getInstance(((ASN1InputStream) obj).readObject()));
}
throw new IOException("unknown object in factory: " + obj.getClass().getName());
}
/**
* Creates an instance of ECDSA signature value
*
* @param concatenatedRS concatenated bytes of the R and S signature value integers
* @return ECDSA signature value
* @throws IOException invalid input
*/
public static EcdsaSigValue getInstance(@Nonnull final byte[] concatenatedRS) throws IOException {
Objects.requireNonNull(concatenatedRS, "Concatenated RS value must not be null");
try {
final BigInteger[] rsVals = getRSFromConcatenatedBytes(concatenatedRS);
return new EcdsaSigValue(rsVals[0], rsVals[1]);
}
catch (Exception ex) {
throw new IOException("Unable to parse concatenated RS data", ex);
}
}
/**
* Creates an instance of ECDSA signature value
*
* @param r R component of the ECDSA signature
* @param s S component of the ECDSA signature
* @return ECDSA signature value
*/
public static EcdsaSigValue getInstance(@Nonnull final BigInteger r, @Nonnull final BigInteger s) {
Objects.requireNonNull(r, "Integer r of the signature value must not be null");
Objects.requireNonNull(s, "Integer s of the signature value must not be null");
return new EcdsaSigValue(r, s);
}
private EcdsaSigValue(final BigInteger r, final BigInteger s) {
this.r = r;
this.s = s;
}
private EcdsaSigValue(final ASN1Sequence obj) throws IOException {
try {
final Enumeration> e = obj.getObjects();
r = ASN1Integer.getInstance(e.nextElement()).getValue();
s = ASN1Integer.getInstance(e.nextElement()).getValue();
}
catch (Exception ex) {
throw new IOException("Error creating ECDSA signature value from provided ASN1 sequence", ex);
}
}
private static BigInteger[] getRSFromConcatenatedBytes(final byte[] concatenatedRS) {
final int rLen, sLen;
final int len = concatenatedRS.length;
rLen = len / 2;
sLen = rLen;
final byte[] rBytes = new byte[rLen];
final byte[] sBytes = new byte[sLen];
System.arraycopy(concatenatedRS, 0, rBytes, 0, rLen);
System.arraycopy(concatenatedRS, rLen, sBytes, 0, sLen);
final BigInteger[] srArray = new BigInteger[2];
srArray[0] = getBigInt(rBytes);
srArray[1] = getBigInt(sBytes);
return srArray;
}
/**
* @return the ASN.1 object of the signature value
*/
public DERSequence toASN1Object() {
final ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(r));
v.add(new ASN1Integer(s));
return new DERSequence(v);
}
/**
* Gets the bytes to be carried in an OCTET STRING to form the CMS signature value
* @return DER encoded bytes of the signature value ASN.1 SEQUENCE
* @throws IOException illegal signature value
*/
public byte[] getDEREncodedSigValue() throws IOException {
return toASN1Object().getEncoded("DER");
}
/**
* Returns the concatenation of the bytes of r and s
* @return byte array representation of signature value
* @throws IOException illegal signature value
*/
public byte[] toByteArray() throws IOException {
try {
final int blockSize = getSigValueBlockSize();
final byte[] rBytes = trimByteArray(r.toByteArray(), blockSize);
final byte[] sBytes = trimByteArray(s.toByteArray(), blockSize);
final byte[] rsBytes = new byte[rBytes.length + sBytes.length];
System.arraycopy(rBytes, 0, rsBytes, 0, rBytes.length);
System.arraycopy(sBytes, 0, rsBytes, rBytes.length, sBytes.length);
return rsBytes;
}
catch (Exception ex) {
throw new IOException("Illegal signature value", ex);
}
}
/**
* Return the key length that represents the signature value block size. For EC 256
* bit key the block size is 256. For EC 521 bit key the block size is 528 (the bits
* of the full byte count that can contain the key).
*
*
* Calculation of the block size take into account that the actual integer byte
* representation may be different than the block size/8. This is the case when the
* bytes representation of the integer is padded to avoid negative representation as
* well as the situation when the byte count is shorter since the actual integer is
* smaller than the allowed max size
*
*
*
* For this reason this implementation lists allowed key sizes supported by this
* implementation and present integers must fit these key sizes. A match is when the
* size without padding is an exact match or when the sum size difference between both
* integer byte count and 2 x the block size byte count is not grater than 4 bytes.
*
* @return The block size of this signature value in bytes
* @throws IOException if the integer values does not fit the preset key lengths
*/
private int getSigValueBlockSize() throws IOException {
final int rByteLen = getDataLength(r);
final int sByteLen = getDataLength(s);
return Arrays.stream(supportedKeyLengths).map(bitLength -> (int) Math.ceil((double) bitLength / 8))
.filter(byteLength -> byteLength >= rByteLen && byteLength >= sByteLen)
.filter(byteLength -> (2 * byteLength - (rByteLen + sByteLen)) < 4).findFirst()
.orElseThrow(IOException::new);
}
/**
* Return the actual number of bytes representing the value of an integer not counting
* any padding bytes Since the bytes are derived form a BigInteger, the maximum number
* of present padding bytes is 1
* @param integer integer value
* @return number of data bytes representing the positive integer value
*/
private int getDataLength(final BigInteger integer) {
final byte[] integerBytes = integer.toByteArray();
if (integerBytes.length == 0) {
return 0;
}
return integerBytes[0] == 0x00 ? integerBytes.length - 1 : integerBytes.length;
}
/**
* Trim the bytes of an integer to fit a predefined block length
* @param inpBytes the data bytes of a positive integer value
* @param blockSize block length in bytes
* @return trimmed bytes
*/
private static byte[] trimByteArray(final byte[] inpBytes, final int blockSize) {
final int len = inpBytes.length;
if (len == blockSize) {
return inpBytes;
}
final byte[] trimmed = new byte[blockSize];
if (len < blockSize) {
int padCnt = blockSize - len;
for (int i = 0; i < padCnt; i++) {
trimmed[i] = 0x00;
System.arraycopy(inpBytes, 0, trimmed, padCnt, len);
}
}
if (len > blockSize) {
int truncCnt = len - blockSize;
System.arraycopy(inpBytes, truncCnt, trimmed, 0, len - truncCnt);
}
return trimmed;
}
/**
* Get the BigInteger value of a byte source representing a positive integer
* @param source byte data of positive integer that may or may not have a leading
* padding byte
* @return positive BigInteger value
*/
private static BigInteger getBigInt(final byte[] source) {
final byte[] padded = new byte[source.length + 1];
padded[0] = 0x00;
System.arraycopy(source, 0, padded, 1, source.length);
return new BigInteger(padded);
}
/**
* Returns the ASN1 object representation of this ECDSA signature value
* @return ASN1 object representation of this ECDSA signature value
*/
@Override
public ASN1Primitive toASN1Primitive() {
return toASN1Object();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy