diff --git a/files-sftp/src/main/java/module-info.java b/files-sftp/src/main/java/module-info.java index 1f8a7ca..293fbee 100644 --- a/files-sftp/src/main/java/module-info.java +++ b/files-sftp/src/main/java/module-info.java @@ -1,4 +1,6 @@ module org.xbib.files.sftp { + requires org.xbib.net.security; + requires java.logging; exports org.apache.sshd.client; exports org.apache.sshd.client.auth; exports org.apache.sshd.client.auth.password; @@ -79,7 +81,5 @@ module org.xbib.files.sftp { exports org.apache.sshd.common.util.security; exports org.apache.sshd.common.util.security.eddsa; exports org.apache.sshd.common.util.threads; - requires org.xbib.net.security; - requires java.logging; uses org.apache.sshd.common.io.IoServiceFactoryFactory; } \ No newline at end of file diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/Ed25519PublicKeyDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/Ed25519PublicKeyDecoder.java new file mode 100644 index 0000000..a57a174 --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/Ed25519PublicKeyDecoder.java @@ -0,0 +1,89 @@ +package org.apache.sshd.common.util.security.edec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPrivateKeySpec; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.NamedParameterSpec; +import java.util.Collections; +import java.util.Map; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; + +public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { + + public static final int MAX_ALLOWED_SEED_LEN = 1024; + + public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); + + private Ed25519PublicKeyDecoder() { + super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + } + + @Override + public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint edECPoint = key.getPoint(); + return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint)); + } + } + + @Override + public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded())); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return KeyPairGenerator.getInstance("Ed25519"); + } + + @Override + public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException { + // TODO + return "ssh-ed25519"; + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return KeyFactory.getInstance("EdDSA"); + } + + @Override + public EdECPublicKey decodePublicKey(SessionContext session, + String keyType, + InputStream keyData, + Map headers) + throws IOException, GeneralSecurityException { + byte[] pk = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); + boolean xisodd = false; + int lastbyteInt = pk[pk.length - 1]; + if ((lastbyteInt & 255) >> 7 == 1) { + xisodd = true; + } + pk[pk.length - 1] &= 127; + BigInteger y = new BigInteger(1, pk); + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint ep = new EdECPoint(xisodd, y); + EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, ep); + return EdECPublicKey.class.cast(generatePublicKey(spec)); + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java new file mode 100644 index 0000000..2e812c3 --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java @@ -0,0 +1,166 @@ +package org.apache.sshd.common.util.security.edec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPrivateKeySpec; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.NamedParameterSpec; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.NoCloseInputStream; +import org.apache.sshd.common.util.io.der.ASN1Object; +import org.apache.sshd.common.util.io.der.ASN1Type; +import org.apache.sshd.common.util.io.der.DERParser; + +public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { + + public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY"; + + public static final List BEGINNERS = Collections.singletonList(BEGIN_MARKER); + + public static final String END_MARKER = "END OPENSSH PRIVATE KEY"; + public static final List ENDERS = Collections.singletonList(END_MARKER); + + /** + * @see RFC8412 section 3 + */ + public static final String ED25519_OID = "1.3.101.112"; + + public static final EdECPEMResourceKeyParser INSTANCE = new EdECPEMResourceKeyParser(); + + public EdECPEMResourceKeyParser() { + super("Ed25519", ED25519_OID, BEGINNERS, ENDERS); + } + + @Override + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, String endMarker, + FilePasswordProvider passwordProvider, + InputStream stream, + Map headers) throws IOException, GeneralSecurityException { + KeyPair kp = parseEd25519KeyPair(stream, false); + return Collections.singletonList(kp); + } + + private static KeyPair parseEd25519KeyPair(InputStream inputStream, + boolean okToClose) throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { + return parseKeyPair(parser); + } + } + + /* + * See https://tools.ietf.org/html/rfc8410#section-7 + * + * SEQUENCE { + * INTEGER 0x00 (0 decimal) + * SEQUENCE { + * OBJECTIDENTIFIER 1.3.101.112 + * } + * OCTETSTRING keyData + * } + * + * NOTE: there is another variant that also has some extra parameters + * but it has the same "prefix" structure so we don't care + */ + private static KeyPair parseKeyPair(DERParser parser) throws IOException, GeneralSecurityException { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing version value"); + } + BigInteger version = obj.asInteger(); + if (!BigInteger.ZERO.equals(version)) { + throw new StreamCorruptedException("Invalid version: " + version); + } + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID container"); + } + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.SEQUENCE) { + throw new StreamCorruptedException("Unexpected OID object type: " + objType); + } + List curveOid; + try (DERParser oidParser = obj.createParser()) { + obj = oidParser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID value"); + } + curveOid = obj.asOID(); + } + String oid = GenericUtils.join(curveOid, '.'); + // TODO modify if more curves supported + if (!ED25519_OID.equals(oid)) { + throw new StreamCorruptedException("Unsupported curve OID: " + oid); + } + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data"); + } + return decodeKeyPair(obj.getValue()); + } + + private static KeyPair decodeKeyPair(byte[] keyData) throws IOException, GeneralSecurityException { + EdECPrivateKey privateKey = getPrivateKey(keyData); + EdECPublicKey publicKey = getPublicKey(privateKey); + return new KeyPair(publicKey, privateKey); + } + + private static EdECPrivateKey getPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(keyData)) { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data container"); + } + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.OCTET_STRING) { + throw new StreamCorruptedException("Mismatched key data container type: " + objType); + } + return generatePrivateKey(obj.getValue()); + } + } + + private static EdECPrivateKey generatePrivateKey(byte[] seed) throws GeneralSecurityException { + NamedParameterSpec spec = NamedParameterSpec.ED25519; + EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); + } + + private static EdECPublicKey getPublicKey(PrivateKey key) throws GeneralSecurityException { + if (!(key instanceof EdECPrivateKey)) { + throw new InvalidKeyException("Private key is not EdEC private key"); + } + byte[] pk = key.getEncoded(); + boolean xisodd = false; + int lastbyteInt = pk[pk.length - 1]; + if ((lastbyteInt & 255) >> 7 == 1) { + xisodd = true; + } + pk[pk.length - 1] &= 127; + BigInteger y = new BigInteger(1, pk); + NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519"); + EdECPoint ep = new EdECPoint(xisodd, y); + EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec)); + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java new file mode 100644 index 0000000..2bb32de --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java @@ -0,0 +1,45 @@ +package org.apache.sshd.common.util.security.edec; + +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.Security; +import java.security.Signature; +import java.util.Objects; +import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; + +public class EdECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { + + public EdECSecurityProviderRegistrar() { + super("Ed25519"); + } + + @Override + public boolean isEnabled() { + return super.isEnabled(); + } + + @Override + public Provider getSecurityProvider() { + return Security.getProvider("SunJCE"); + } + + @Override + public boolean isSecurityEntitySupported(Class entityType, String name) { + if (!isSupported()) { + return false; + } + if (KeyPairGenerator.class.isAssignableFrom(entityType) || KeyFactory.class.isAssignableFrom(entityType)) { + return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0; + } else if (Signature.class.isAssignableFrom(entityType)) { + return Objects.compare("NONEwithEdDSA", name, String.CASE_INSENSITIVE_ORDER) == 0; + } else { + return false; + } + } + + @Override + public boolean isSupported() { + return true; + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java new file mode 100644 index 0000000..9c69880 --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -0,0 +1,175 @@ +package org.apache.sshd.common.util.security.edec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPrivateKeySpec; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Objects; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.SecureByteArrayOutputStream; +import org.apache.sshd.common.util.io.der.ASN1Object; +import org.apache.sshd.common.util.io.der.ASN1Type; +import org.apache.sshd.common.util.io.der.DERParser; + + +public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { + + public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); + + private static final int PK_SIZE = 32; + + private static final int SK_SIZE = 32; + + private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; + + public OpenSSHEd25519PrivateKeyEntryDecoder() { + super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + } + + @Override + public EdECPrivateKey decodePrivateKey(SessionContext session, + String keyType, + FilePasswordProvider passwordProvider, + InputStream keyData) + throws IOException, GeneralSecurityException { + if (!"ssh-ed25519".equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + // ed25519 bernstein naming: pk .. public key, sk .. secret key + // we expect to find two byte arrays with the following structure (type:size): + // [pk:32], [sk:32,pk:32] + byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; + byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; + try { + pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); + keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); + if (pk.length != PK_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); + } + if (keypair.length != KEYPAIR_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, + KEYPAIR_SIZE)); + } + // verify that the keypair contains the expected pk + // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface + if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { + throw new InvalidKeyException("Keypair did not contain the public key."); + } + byte[] seed = Arrays.copyOf(keypair, SK_SIZE); + NamedParameterSpec spec = NamedParameterSpec.ED25519; + EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); + } finally { + // get rid of sensitive data a.s.a.p + Arrays.fill(pk, (byte) 0); + Arrays.fill(keypair, (byte) 0); + } + } + + @Override + public String encodePrivateKey(SecureByteArrayOutputStream s, + EdECPrivateKey key, + EdECPublicKey pubKey) throws IOException { + Objects.requireNonNull(key, "No private key provided"); + return "ssh-ed25519"; + } + + @Override + public boolean isPublicKeyRecoverySupported() { + return true; + } + + @Override + public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException { + byte[] pk = key.getEncoded(); + boolean xisodd = false; + int lastbyteInt = pk[pk.length - 1]; + if ((lastbyteInt & 255) >> 7 == 1) { + xisodd = true; + } + pk[pk.length - 1] &= 127; + BigInteger y = new BigInteger(1, pk); + NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519"); + EdECPoint ep = new EdECPoint(xisodd, y); + EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec)); + + } + + @Override + public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint edECPoint = key.getPoint(); + EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint); + return generatePublicKey(spec); + } + } + + @Override + public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return getPrivateKey(key.getEncoded()); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return KeyPairGenerator.getInstance("Ed25519"); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return KeyFactory.getInstance("EdDSA"); + } + + private static EdECPrivateKey getPrivateKey(byte[] keyData) throws GeneralSecurityException { + try (DERParser parser = new DERParser(keyData)) { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data container"); + } + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.OCTET_STRING) { + throw new StreamCorruptedException("Mismatched key data container type: " + objType); + } + return generatePrivateKey(obj.getValue()); + } catch (IOException e) { + throw new GeneralSecurityException(e); + } + } + + private static EdECPrivateKey generatePrivateKey(byte[] seed) throws GeneralSecurityException { + NamedParameterSpec spec = NamedParameterSpec.ED25519; + EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); + } + +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java new file mode 100644 index 0000000..22fa8ad --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java @@ -0,0 +1,26 @@ +package org.apache.sshd.common.util.security.edec; + +import java.util.Map; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.signature.AbstractSignature; +import org.apache.sshd.common.util.ValidateUtils; + +public class SignatureEd25519 extends AbstractSignature { + + public SignatureEd25519() { + super("NONEwithEdDSA"); + } + + @Override + public boolean verify(SessionContext session, byte[] sig) throws Exception { + byte[] data = sig; + Map.Entry encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); + if (encoding != null) { + String keyType = encoding.getKey(); + ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); + data = encoding.getValue(); + } + return doVerify(data); + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/xbib/XbibKeyPairResourceParser.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/xbib/XbibKeyPairResourceParser.java deleted file mode 100644 index 82f579e..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/xbib/XbibKeyPairResourceParser.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.util.security.xbib; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import org.apache.sshd.common.NamedResource; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; -import org.apache.sshd.common.session.SessionContext; -import org.xbib.net.security.PrivateKeyReader; - -/** - * @author Apache MINA SSHD Project - */ -public class XbibKeyPairResourceParser implements KeyPairResourceParser { - - public static final XbibKeyPairResourceParser INSTANCE = new XbibKeyPairResourceParser(); - - public XbibKeyPairResourceParser() { - } - - @Override - public boolean canExtractKeyPairs(NamedResource resourceKey, List lines) { - return true; - } - - @Override - public Collection loadKeyPairs(SessionContext session, - NamedResource resourceKey, - FilePasswordProvider passwordProvider, - List lines) throws IOException, GeneralSecurityException { - PrivateKeyReader privateKeyReader = new PrivateKeyReader(); - InputStream inputStream = new ByteArrayInputStream(String.join("\n", lines).getBytes(StandardCharsets.US_ASCII)); - String password = passwordProvider.getPassword(session, resourceKey, 0); - return Collections.singleton(privateKeyReader.generateFrom(inputStream, password)); - } -}