blob: 3dd5e7b6f80b4ac6c796828f5c32dbcaae689480 [file] [log] [blame]
/*
* 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.config.keys;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.io.IoUtils;
/**
* @param <PUB> Type of {@link PublicKey}
* @param <PRV> Type of {@link PrivateKey}
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey>
extends IdentityResourceLoader<PUB, PRV> {
/**
* @param keySize Key size in bits
* @return A {@link KeyPair} with the specified key size
* @throws GeneralSecurityException if unable to generate the pair
*/
default KeyPair generateKeyPair(int keySize) throws GeneralSecurityException {
KeyPairGenerator gen = getKeyPairGenerator();
gen.initialize(keySize);
return gen.generateKeyPair();
}
/**
* @param kp The {@link KeyPair} to be cloned - ignored if {@code null}
* @return A cloned pair (or {@code null} if no original pair)
* @throws GeneralSecurityException If failed to clone - e.g., provided key pair does not contain keys of the
* expected type
* @see #getPublicKeyType()
* @see #getPrivateKeyType()
*/
default KeyPair cloneKeyPair(KeyPair kp) throws GeneralSecurityException {
if (kp == null) {
return null;
}
PUB pubCloned = null;
PublicKey pubOriginal = kp.getPublic();
Class<PUB> pubExpected = getPublicKeyType();
if (pubOriginal != null) {
Class<?> orgType = pubOriginal.getClass();
if (!pubExpected.isAssignableFrom(orgType)) {
throw new InvalidKeyException(
"Mismatched public key types: expected=" + pubExpected.getSimpleName() + ", actual="
+ orgType.getSimpleName());
}
PUB castPub = pubExpected.cast(pubOriginal);
pubCloned = clonePublicKey(castPub);
}
PRV prvCloned = null;
PrivateKey prvOriginal = kp.getPrivate();
Class<PRV> prvExpected = getPrivateKeyType();
if (prvOriginal != null) {
Class<?> orgType = prvOriginal.getClass();
if (!prvExpected.isAssignableFrom(orgType)) {
throw new InvalidKeyException(
"Mismatched private key types: expected=" + prvExpected.getSimpleName() + ", actual="
+ orgType.getSimpleName());
}
PRV castPrv = prvExpected.cast(prvOriginal);
prvCloned = clonePrivateKey(castPrv);
}
return new KeyPair(pubCloned, prvCloned);
}
/**
* @param key The {@link PublicKey} to clone - ignored if {@code null}
* @return The cloned key (or {@code null} if no original key)
* @throws GeneralSecurityException If failed to clone the key
*/
PUB clonePublicKey(PUB key) throws GeneralSecurityException;
/**
* @param key The {@link PrivateKey} to clone - ignored if {@code null}
* @return The cloned key (or {@code null} if no original key)
* @throws GeneralSecurityException If failed to clone the key
*/
PRV clonePrivateKey(PRV key) throws GeneralSecurityException;
/**
* @return A {@link KeyPairGenerator} suitable for this decoder
* @throws GeneralSecurityException If failed to create the generator
*/
KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException;
/**
* @return A {@link KeyFactory} suitable for the specific decoder type
* @throws GeneralSecurityException If failed to create one
*/
KeyFactory getKeyFactoryInstance() throws GeneralSecurityException;
static int encodeString(OutputStream s, String v) throws IOException {
return encodeString(s, v, StandardCharsets.UTF_8);
}
static int encodeString(OutputStream s, String v, String charset) throws IOException {
return encodeString(s, v, Charset.forName(charset));
}
static int encodeString(OutputStream s, String v, Charset cs) throws IOException {
return writeRLEBytes(s, v.getBytes(cs));
}
static int encodeBigInt(OutputStream s, BigInteger v) throws IOException {
return writeRLEBytes(s, v.toByteArray());
}
static int writeRLEBytes(OutputStream s, byte... bytes) throws IOException {
return writeRLEBytes(s, bytes, 0, bytes.length);
}
static int writeRLEBytes(OutputStream s, byte[] bytes, int off, int len) throws IOException {
byte[] lenBytes = encodeInt(s, len);
s.write(bytes, off, len);
return lenBytes.length + len;
}
static byte[] encodeInt(OutputStream s, int v) throws IOException {
byte[] bytes = {
(byte) ((v >> 24) & 0xFF),
(byte) ((v >> 16) & 0xFF),
(byte) ((v >> 8) & 0xFF),
(byte) (v & 0xFF)
};
s.write(bytes);
return bytes;
}
static String decodeString(InputStream s, int maxChars) throws IOException {
return decodeString(s, StandardCharsets.UTF_8, maxChars);
}
static String decodeString(InputStream s, String charset, int maxChars) throws IOException {
return decodeString(s, Charset.forName(charset), maxChars);
}
static String decodeString(InputStream s, Charset cs, int maxChars) throws IOException {
byte[] bytes = readRLEBytes(s, maxChars * 4 /* in case UTF-8 with weird characters */);
return new String(bytes, cs);
}
static BigInteger decodeBigInt(InputStream s) throws IOException {
return new BigInteger(readRLEBytes(s, IdentityResourceLoader.MAX_BIGINT_OCTETS_COUNT));
}
static byte[] readRLEBytes(InputStream s, int maxAllowed) throws IOException {
int len = decodeInt(s);
if (len > maxAllowed) {
throw new StreamCorruptedException(
"Requested block length (" + len + ") exceeds max. allowed (" + maxAllowed + ")");
}
if (len < 0) {
throw new StreamCorruptedException("Negative block length requested: " + len);
}
byte[] bytes = new byte[len];
IoUtils.readFully(s, bytes);
return bytes;
}
static int decodeInt(InputStream s) throws IOException {
byte[] bytes = { 0, 0, 0, 0 };
IoUtils.readFully(s, bytes);
return ((bytes[0] & 0xFF) << 24)
| ((bytes[1] & 0xFF) << 16)
| ((bytes[2] & 0xFF) << 8)
| (bytes[3] & 0xFF);
}
static Map.Entry<String, Integer> decodeString(byte[] buf, int maxChars) {
return decodeString(buf, 0, NumberUtils.length(buf), maxChars);
}
static Map.Entry<String, Integer> decodeString(byte[] buf, int offset, int available, int maxChars) {
return decodeString(buf, offset, available, StandardCharsets.UTF_8, maxChars);
}
static Map.Entry<String, Integer> decodeString(byte[] buf, Charset cs, int maxChars) {
return decodeString(buf, 0, NumberUtils.length(buf), cs, maxChars);
}
/**
* Decodes a run-length encoded string
*
* @param buf The buffer with the data bytes
* @param offset The offset in the buffer to decode the string
* @param available The max. available data starting from the offset
* @param cs The {@link Charset} to use to decode the string
* @param maxChars Max. allowed characters in string - if more than that is encoded then an
* {@link IndexOutOfBoundsException} will be thrown
* @return The decoded string + the offset of the next byte after it
* @see #readRLEBytes(byte[], int, int, int)
*/
static Map.Entry<String, Integer> decodeString(
byte[] buf, int offset, int available, Charset cs, int maxChars) {
Map.Entry<byte[], Integer> result = readRLEBytes(buf, offset, available, maxChars * 4 /*
* in case UTF-8 with
* weird characters
*/);
byte[] bytes = result.getKey();
Integer nextOffset = result.getValue();
return new SimpleImmutableEntry<>(new String(bytes, cs), nextOffset);
}
static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int maxAllowed) {
return readRLEBytes(buf, 0, NumberUtils.length(buf), maxAllowed);
}
/**
* Decodes a run-length encoded byte array
*
* @param buf The buffer with the data bytes
* @param offset The offset in the buffer to decode the array
* @param available The max. available data starting from the offset
* @param maxAllowed Max. allowed data in decoded buffer - if more than that is encoded then an
* {@link IndexOutOfBoundsException} will be thrown
* @return The decoded data buffer + the offset of the next byte after it
*/
static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int offset, int available, int maxAllowed) {
int len = decodeInt(buf, offset, available);
if (len > maxAllowed) {
throw new IndexOutOfBoundsException(
"Requested block length (" + len + ") exceeds max. allowed (" + maxAllowed + ")");
}
if (len < 0) {
throw new IndexOutOfBoundsException("Negative block length requested: " + len);
}
available -= Integer.BYTES;
if (len > available) {
throw new IndexOutOfBoundsException("Requested block length (" + len + ") exceeds remaing (" + available + ")");
}
byte[] bytes = new byte[len];
offset += Integer.BYTES;
System.arraycopy(buf, offset, bytes, 0, len);
return new SimpleImmutableEntry<>(bytes, Integer.valueOf(offset + len));
}
static int decodeInt(byte[] buf) {
return decodeInt(buf, 0, NumberUtils.length(buf));
}
static int decodeInt(byte[] buf, int offset, int available) {
if (available < Integer.BYTES) {
throw new IndexOutOfBoundsException(
"Available data length (" + available + ") cannot accommodate integer encoding");
}
return ((buf[offset] & 0xFF) << 24)
| ((buf[offset + 1] & 0xFF) << 16)
| ((buf[offset + 2] & 0xFF) << 8)
| (buf[offset + 3] & 0xFF);
}
}