org.apache.sshd.common.cipher.ECCurves Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.cipher;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.security.interfaces.ECKey;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.OptionalFeature;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.digest.DigestFactory;
import org.apache.sshd.common.keyprovider.KeySizeIndicator;
import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* Utilities for working with elliptic curves.
*
* @author Apache MINA SSHD Project
*/
public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResource, OptionalFeature {
nistp256(Constants.NISTP256, new int[] { 1, 2, 840, 10045, 3, 1, 7 },
new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(
new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)),
new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16),
new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)),
new ECPoint(
new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16),
new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)),
new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16),
1),
32,
BuiltinDigests.sha256),
nistp384(Constants.NISTP384, new int[] { 1, 3, 132, 0, 34 },
new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(
new BigInteger(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
16)),
new BigInteger(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
16),
new BigInteger(
"B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
16)),
new ECPoint(
new BigInteger(
"AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
16),
new BigInteger(
"3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
16)),
new BigInteger(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
16),
1),
48,
BuiltinDigests.sha384),
nistp521(Constants.NISTP521, new int[] { 1, 3, 132, 0, 35 },
new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(
new BigInteger(
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
16)),
new BigInteger(
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
16),
new BigInteger(
"0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951"
+ "EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
16)),
new ECPoint(
new BigInteger(
"00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77"
+ "EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
16),
new BigInteger(
"011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE7299"
+ "5EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
16)),
new BigInteger(
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B"
+ "7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
16),
1),
66,
BuiltinDigests.sha512);
/**
* A {@link Set} of all the known curves
*/
public static final Set VALUES = Collections.unmodifiableSet(EnumSet.allOf(ECCurves.class));
/**
* A {@link Set} of all the known curves names
*/
public static final NavigableSet NAMES = Collections.unmodifiableNavigableSet(
GenericUtils.mapSort(VALUES, ECCurves::getName, String.CASE_INSENSITIVE_ORDER));
/**
* A {@link Set} of all the known curves key types
*/
public static final NavigableSet KEY_TYPES = Collections.unmodifiableNavigableSet(
GenericUtils.mapSort(VALUES, ECCurves::getKeyType, String.CASE_INSENSITIVE_ORDER));
public static final Comparator BY_KEY_SIZE = (o1, o2) -> {
int k1 = (o1 == null) ? Integer.MAX_VALUE : o1.getKeySize();
int k2 = (o2 == null) ? Integer.MAX_VALUE : o2.getKeySize();
return Integer.compare(k1, k2);
};
public static final List SORTED_KEY_SIZE = Collections.unmodifiableList(VALUES.stream()
.sorted(BY_KEY_SIZE)
.collect(Collectors.toList()));
private final String name;
private final String keyType;
private final String oidString;
private final List oidValue;
private final ECParameterSpec params;
private final int keySize;
private final int numOctets;
private final DigestFactory digestFactory;
ECCurves(String name, int[] oid, ECParameterSpec params, int numOctets, DigestFactory digestFactory) {
this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No curve name");
this.oidString = NumberUtils.join('.', ValidateUtils.checkNotNullAndNotEmpty(oid, "No OID"));
this.oidValue = Collections.unmodifiableList(NumberUtils.asList(oid));
this.keyType = Constants.ECDSA_SHA2_PREFIX + name;
this.params = ValidateUtils.checkNotNull(params, "No EC params for %s", name);
this.keySize = getCurveSize(params);
this.numOctets = numOctets;
this.digestFactory = Objects.requireNonNull(digestFactory, "No digestFactory");
}
@Override // The curve's standard name
public final String getName() {
return name;
}
public final String getOID() {
return oidString;
}
public final List getOIDValue() {
return oidValue;
}
@Override
public final String getKeyType() {
return keyType;
}
@Override
public final boolean isSupported() {
return SecurityUtils.isECCSupported() && digestFactory.isSupported();
}
public final ECParameterSpec getParameters() {
return params;
}
@Override
public final int getKeySize() {
return keySize;
}
/**
* @return The number of octets used to represent the point(s) for the curve
*/
public final int getNumPointOctets() {
return numOctets;
}
/**
* @return The {@link Digest} to use when hashing the curve's parameters
*/
public final Digest getDigestForParams() {
return digestFactory.create();
}
/**
* @param type The key type value - ignored if {@code null}/empty
* @return The matching {@link ECCurves} constant - {@code null} if no match found case insensitive
*/
public static ECCurves fromKeyType(String type) {
if (GenericUtils.isEmpty(type)) {
return null;
}
for (ECCurves c : VALUES) {
if (type.equalsIgnoreCase(c.getKeyType())) {
return c;
}
}
return null;
}
/**
* @param name The curve name (case insensitive - ignored if {@code null}/empty
* @return The matching {@link ECCurves} instance - {@code null} if no match found
*/
public static ECCurves fromCurveName(String name) {
return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
}
/**
* @param key The {@link ECKey} - ignored if {@code null}
* @return The matching {@link ECCurves} instance - {@code null} if no match found
*/
public static ECCurves fromECKey(ECKey key) {
return fromCurveParameters((key == null) ? null : key.getParams());
}
/**
* @param params The curve's {@link ECParameterSpec} - ignored if {@code null}
* @return The matching {@link ECCurves} value - {@code null} if no match found
* @see #getCurveSize(ECParameterSpec)
* @see #fromCurveSize(int)
*/
public static ECCurves fromCurveParameters(ECParameterSpec params) {
if (params == null) {
return null;
} else {
return fromCurveSize(getCurveSize(params));
}
}
/**
* @param keySize The key size (in bits)
* @return The matching {@link ECCurves} value - {@code null} if no match found
*/
public static ECCurves fromCurveSize(int keySize) {
if (keySize <= 0) {
return null;
}
for (ECCurves c : VALUES) {
if (keySize == c.getKeySize()) {
return c;
}
}
return null;
}
public static ECCurves fromOIDValue(List extends Number> oid) {
if (GenericUtils.isEmpty(oid)) {
return null;
}
for (ECCurves c : VALUES) {
List extends Number> v = c.getOIDValue();
if (oid.size() != v.size()) {
continue;
}
boolean matches = true;
for (int index = 0; index < v.size(); index++) {
Number exp = v.get(index);
Number act = oid.get(index);
if (exp.intValue() != act.intValue()) {
matches = false;
break;
}
}
if (matches) {
return c;
}
}
return null;
}
public static ECCurves fromOID(String oid) {
if (GenericUtils.isEmpty(oid)) {
return null;
}
for (ECCurves c : VALUES) {
if (oid.equalsIgnoreCase(c.getOID())) {
return c;
}
}
return null;
}
/**
* @param params The curve's {@link ECParameterSpec}
* @return The curve's key size in bits
* @throws IllegalArgumentException if invalid parameters provided
*/
public static int getCurveSize(ECParameterSpec params) {
EllipticCurve curve = Objects.requireNonNull(params, "No EC params").getCurve();
ECField field = Objects.requireNonNull(curve, "No EC curve").getField();
return Objects.requireNonNull(field, "No EC field").getFieldSize();
}
public static byte[] encodeECPoint(ECPoint group, ECParameterSpec params) {
return encodeECPoint(group, params.getCurve());
}
public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) {
// M has len 2 ceil(log_2(q)/8) + 1 ?
int elementSize = (curve.getField().getFieldSize() + 7) / 8;
byte[] m = new byte[2 * elementSize + 1];
// Uncompressed format
m[0] = 0x04;
byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray());
System.arraycopy(affineX, 0, m, 1 + elementSize - affineX.length, affineX.length);
byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray());
System.arraycopy(affineY, 0, m, 1 + elementSize + elementSize - affineY.length, affineY.length);
return m;
}
private static byte[] removeLeadingZeroes(byte[] input) {
if (input[0] != 0x00) {
return input;
}
int pos = 1;
while (pos < input.length - 1 && input[pos] == 0x00) {
pos++;
}
byte[] output = new byte[input.length - pos];
System.arraycopy(input, pos, output, 0, output.length);
return output;
}
/**
* Converts the given octet string (defined by ASN.1 specifications) to a {@link BigInteger} As octet strings always
* represent positive integers, a zero-byte is prepended to the given array if necessary (if is MSB equal to 1),
* then this is converted to BigInteger The conversion is defined in the Section 2.3.8
*
* @param octets - octet string bytes to be converted
* @return The {@link BigInteger} representation of the octet string
*/
public static BigInteger octetStringToInteger(byte... octets) {
if (octets == null) {
return null;
} else if (octets.length == 0) {
return BigInteger.ZERO;
} else {
return new BigInteger(1, octets);
}
}
public static ECPoint octetStringToEcPoint(byte... octets) {
if (NumberUtils.isEmpty(octets)) {
return null;
}
int startIndex = findFirstNonZeroIndex(octets);
if (startIndex < 0) {
throw new IllegalArgumentException("All zeroes ECPoint N/A");
}
byte indicator = octets[startIndex];
ECCurves.ECPointCompression compression = ECCurves.ECPointCompression.fromIndicatorValue(indicator);
if (compression == null) {
throw new UnsupportedOperationException(
"Unknown compression indicator value: 0x" + Integer.toHexString(indicator & 0xFF));
}
// The coordinates actually start after the compression indicator
return compression.octetStringToEcPoint(octets, startIndex + 1, octets.length - startIndex - 1);
}
private static int findFirstNonZeroIndex(byte... octets) {
if (NumberUtils.isEmpty(octets)) {
return -1;
}
for (int index = 0; index < octets.length; index++) {
if (octets[index] != 0) {
return index;
}
}
return -1; // all zeroes
}
public static final class Constants {
/**
* Standard prefix of NISTP key types when encoded
*/
public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-";
public static final String NISTP256 = "nistp256";
public static final String NISTP384 = "nistp384";
public static final String NISTP521 = "nistp521";
private Constants() {
throw new UnsupportedOperationException("No instance allowed");
}
}
/**
* The various {@link ECPoint} representation compression indicators
*
* @author Apache MINA SSHD Project
* @see RFC-5480 - section 2.2
*/
public enum ECPointCompression {
// see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00
// see
// http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point
VARIANT2((byte) 0x02) {
@Override
public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
byte[] xp = new byte[len];
System.arraycopy(octets, startIndex, xp, 0, len);
BigInteger x = octetStringToInteger(xp);
// TODO derive even Y...
throw new UnsupportedOperationException(
"octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A");
}
},
VARIANT3((byte) 0x03) {
@Override
public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
byte[] xp = new byte[len];
System.arraycopy(octets, startIndex, xp, 0, len);
BigInteger x = octetStringToInteger(xp);
// TODO derive odd Y...
throw new UnsupportedOperationException(
"octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A");
}
},
UNCOMPRESSED((byte) 0x04) {
@Override
public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
int numElements = len / 2; /* x, y */
if (len != (numElements * 2)) { // make sure length is not odd
throw new IllegalArgumentException("octetStringToEcPoint(" + name() + ") "
+ " invalid remainder octets representation: "
+ " expected=" + (2 * numElements) + ", actual=" + len);
}
byte[] xp = new byte[numElements];
byte[] yp = new byte[numElements];
System.arraycopy(octets, startIndex, xp, 0, numElements);
System.arraycopy(octets, startIndex + numElements, yp, 0, numElements);
BigInteger x = octetStringToInteger(xp);
BigInteger y = octetStringToInteger(yp);
return new ECPoint(x, y);
}
@Override
public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
ECCurves curve = fromCurveName(curveName);
if (curve == null) {
throw new StreamCorruptedException(
"writeECPoint(" + name() + ")[" + curveName + "] cannot determine octets count");
}
int numElements = curve.getNumPointOctets();
KeyEntryResolver.encodeInt(s, 1 /* the indicator */ + 2 * numElements);
s.write(getIndicatorValue());
writeCoordinate(s, "X", p.getAffineX(), numElements);
writeCoordinate(s, "Y", p.getAffineY(), numElements);
}
};
public static final Set VALUES
= Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
private final byte indicatorValue;
ECPointCompression(byte indicator) {
indicatorValue = indicator;
}
public final byte getIndicatorValue() {
return indicatorValue;
}
public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len);
public byte[] ecPointToOctetString(String curveName, ECPoint p) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 66) + Long.SIZE)) {
writeECPoint(baos, curveName, p);
return baos.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException("ecPointToOctetString(" + curveName + ")"
+ " failed (" + e.getClass().getSimpleName() + ")"
+ " to write data: " + e.getMessage(),
e);
}
}
public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
if (s == null) {
throw new EOFException("No output stream");
}
throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + p + "] N/A");
}
protected void writeCoordinate(OutputStream s, String n, BigInteger v, int numElements) throws IOException {
byte[] vp = v.toByteArray();
int startIndex = 0;
int vLen = vp.length;
if (vLen > numElements) {
if (vp[0] == 0) { // skip artificial positive sign
startIndex++;
vLen--;
}
}
if (vLen > numElements) {
throw new StreamCorruptedException("writeCoordinate(" + name() + ")[" + n + "]"
+ " value length (" + vLen + ") exceeds max. (" + numElements + ")"
+ " for " + v);
}
if (vLen < numElements) {
byte[] tmp = new byte[numElements];
System.arraycopy(vp, startIndex, tmp, numElements - vLen, vLen);
vp = tmp;
startIndex = 0;
vLen = vp.length;
}
s.write(vp, startIndex, vLen);
}
public static ECPointCompression fromIndicatorValue(int value) {
if ((value < 0) || (value > 0xFF)) {
return null; // must be a byte value
}
for (ECPointCompression c : VALUES) {
if (value == c.getIndicatorValue()) {
return c;
}
}
return null;
}
}
}