import java.security.spec.ECPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
-import java.util.Base64;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.SshPublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private byte[] bytes = new byte[0];
private int pos = 0;
- public PublicKey decodePublicKey(final String keyLine) throws GeneralSecurityException {
-
- // look for the Base64 encoded part of the line to decode
- // both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
- bytes = Base64.getDecoder().decode(keyLine.getBytes(StandardCharsets.UTF_8));
- if (bytes.length == 0) {
- throw new IllegalArgumentException("No Base64 part to decode in " + keyLine);
- }
-
+ public PublicKey decodePublicKey(final SshPublicKey keyLine) throws GeneralSecurityException {
+ bytes = keyLine.getValue();
pos = 0;
final var type = decodeType();
out.write(bytes);
}
- public static String encodePublicKey(final PublicKey publicKey) throws IOException {
+ public static @NonNull SshPublicKey encodePublicKey(final PublicKey publicKey) throws IOException {
final var baos = new ByteArrayOutputStream();
try (var dout = new DataOutputStream(baos)) {
dout.write(coordX);
dout.write(coordY);
} else {
- throw new IllegalArgumentException("Unknown public key encoding: " + publicKey);
+ throw new IOException("Unknown public key encoding: " + publicKey);
}
}
- return Base64.getEncoder().encodeToString(baos.toByteArray());
+ return new SshPublicKey(baos.toByteArray());
}
}
import org.opendaylight.netconf.callhome.server.ssh.CallHomeSshAuthProvider;
import org.opendaylight.netconf.callhome.server.ssh.CallHomeSshAuthSettings;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.NetconfCallhomeServer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.SshPublicKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.credentials.Credentials;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.AllowedDevices;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.Global;
private void deleteDevice(final Device dataBefore) {
if (dataBefore != null) {
- final String publicKey = getHostPublicKey(dataBefore);
- if (publicKey != null) {
+ if (dataBefore.getTransport() instanceof Ssh ssh) {
LOG.debug("Removing device {}", dataBefore.getUniqueId());
- removeDevice(publicKey, dataBefore);
+ removeDevice(ssh.nonnullSshClientParams().requireHostKey(), dataBefore);
} else {
LOG.debug("Ignoring removal of device {}, no host key present", dataBefore.getUniqueId());
}
}
private void writeDevice(final Device dataAfter) {
- final String publicKey = getHostPublicKey(dataAfter);
- if (publicKey != null) {
+ if (dataAfter.getTransport() instanceof Ssh ssh) {
LOG.debug("Adding device {}", dataAfter.getUniqueId());
- addDevice(publicKey, dataAfter);
+ addDevice(ssh.nonnullSshClientParams().requireHostKey(), dataAfter);
} else {
LOG.debug("Ignoring addition of device {}, no host key present", dataAfter.getUniqueId());
}
}
- private static String getHostPublicKey(final Device device) {
- return device.getTransport() instanceof Ssh ssh ? ssh.nonnullSshClientParams().getHostKey() : null;
- }
-
- abstract void addDevice(String publicKey, Device device);
+ abstract void addDevice(SshPublicKey publicKey, Device device);
- abstract void removeDevice(String publicKey, Device device);
+ abstract void removeDevice(SshPublicKey publicKey, Device device);
}
private static final class DeviceConfig extends AbstractDeviceListener {
}
@Override
- void addDevice(final String publicKey, final Device device) {
+ void addDevice(final SshPublicKey publicKey, final Device device) {
final PublicKey key = publicKey(publicKey, device);
if (key != null) {
byPublicKey.put(key, device);
}
@Override
- void removeDevice(final String publicKey, final Device device) {
+ void removeDevice(final SshPublicKey publicKey, final Device device) {
final PublicKey key = publicKey(publicKey, device);
if (key != null) {
byPublicKey.remove(key);
}
}
- private PublicKey publicKey(final String hostKey, final Device device) {
+ private PublicKey publicKey(final SshPublicKey hostKey, final Device device) {
try {
return keyDecoder.decodePublicKey(hostKey);
} catch (GeneralSecurityException e) {
}
private static final class DeviceOp extends AbstractDeviceListener {
- private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
+ private final ConcurrentMap<SshPublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
Device get(final PublicKey serverKey) {
- final String skey;
+ final SshPublicKey skey;
try {
skey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
- } catch (IOException | IllegalArgumentException e) {
+ } catch (IOException e) {
LOG.error("Unable to encode server key: {}", serverKey, e);
return null;
}
}
@Override
- void removeDevice(final String publicKey, final Device device) {
+ void removeDevice(final SshPublicKey publicKey, final Device device) {
byPublicKey.remove(publicKey);
}
@Override
- void addDevice(final String publicKey, final Device device) {
+ void addDevice(final SshPublicKey publicKey, final Device device) {
byPublicKey.put(publicKey, device);
}
}
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.netconf.callhome.server.CallHomeStatusRecorder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.NetconfCallhomeServer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.SshPublicKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.AllowedDevices;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.allowed.devices.Device;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.allowed.devices.Device.DeviceStatus;
* @param status initial device status
*/
public void reportNewSshDevice(final String id, final PublicKey serverKey, final DeviceStatus status) {
- writeDevice(buildInstanceIdentifier(id), newSshDevice(id, serverKey, status));
+ final var device = newSshDevice(id, serverKey, status);
+ if (device != null) {
+ writeDevice(buildInstanceIdentifier(id), device);
+ }
}
private static Device newSshDevice(final String id, final PublicKey serverKey, final DeviceStatus status) {
// used only for netconf devices that are connected via SSH transport and global credentials
- String sshEncodedKey = serverKey.toString();
+ final SshPublicKey sshEncodedKey;
try {
sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
} catch (IOException e) {
- LOG.warn("Unable to encode public key to ssh format.", e);
+ LOG.warn("Unable to encode public key to ssh format, skipping update", e);
+ return null;
}
+
return new DeviceBuilder()
.setUniqueId(id)
.withKey(new DeviceKey(id))
"A number of improvements to the sematics of this model. In concrete terms:
- every device now has to have a transport
- previously-deprecated 'ssh-host-key' is now obsolete
- - 'credentials; is obsoleted as well";
+ - 'credentials; is obsoleted as well
+ - 'host-key' is now a dedicated typedef based on 'type binary'";
}
revision 2023-04-28 {
description "Initial version";
}
+ typedef ssh-public-key {
+ description "An SSH public key encoded in RFC4253 format";
+ reference "RFC4253 section 6.6";
+ type binary {
+ // Note: the format requires at least 8 bytes for length of the algo and its bytes
+ length "8..max";
+ }
+ }
+
grouping credentials {
container credentials {
presence "Credentials to device.";
container ssh-client-params {
leaf host-key {
mandatory true;
- description "BASE-64 encoded public key which device will use during connection.";
- type string;
+ description "Public key which device will use during connection.";
+ type ssh-public-key;
}
uses credentials;
}