public Collection extractKeyPairs()

in sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java [105:188]


    public Collection<KeyPair> extractKeyPairs(
            SessionContext session, NamedResource resourceKey,
            String beginMarker, String endMarker,
            FilePasswordProvider passwordProvider,
            InputStream stream, Map<String, String> headers)
            throws IOException, GeneralSecurityException {
        boolean debugEnabled = log.isDebugEnabled();

        stream = validateStreamMagicMarker(session, resourceKey, stream);

        String cipher = KeyEntryResolver.decodeString(stream, MAX_CIPHER_NAME_LENGTH);
        OpenSSHKdfOptions kdfOptions = resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream, headers);
        OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfOptions);
        int numKeys = KeyEntryResolver.decodeInt(stream);
        if (numKeys <= 0) {
            if (debugEnabled) {
                log.debug("extractKeyPairs({}) no encoded keys for context={}", resourceKey, context);
            }
            return Collections.emptyList();
        }

        if (debugEnabled) {
            log.debug("extractKeyPairs({}) decode {} keys using context={}", resourceKey, numKeys, context);
        }

        List<PublicKey> publicKeys = new ArrayList<>(numKeys);
        boolean traceEnabled = log.isTraceEnabled();
        for (int index = 1; index <= numKeys; index++) {
            PublicKey pubKey = readPublicKey(session, resourceKey, context, stream, headers);
            ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey);
            if (traceEnabled) {
                log.trace("extractKeyPairs({}) read public key #{}: {} {}",
                        resourceKey, index, KeyUtils.getKeyType(pubKey), KeyUtils.getFingerPrint(pubKey));
            }
            publicKeys.add(pubKey);
        }

        if (!context.isEncrypted()) {
            byte[] privateData = KeyEntryResolver.readRLEBytes(stream, MAX_PRIVATE_KEY_DATA_SIZE);
            try (InputStream bais = new ByteArrayInputStream(privateData)) {
                return readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais);
            } finally {
                Arrays.fill(privateData, (byte) 0);
            }
        }

        if (passwordProvider == null) {
            throw new FailedLoginException("No password provider for encrypted key in " + resourceKey);
        }

        CipherFactory cipherSpec = BuiltinCiphers.resolveFactory(cipher);
        if (cipherSpec == null || !cipherSpec.isSupported()) {
            throw new NoSuchAlgorithmException("Unsupported cipher: " + cipher + " for encrypted key in " + resourceKey);
        }

        byte[] encryptedData;
        if (cipherSpec.getAuthenticationTagSize() > 0) {
            // If an AEAD algorithm is used, openSSH simply puts the AT after the RLE-encoded encrypted data. It is not
            // included in the RLE (i.e., the length does not include the AT size). The ciphers do expect the AT (MAC)
            // to follow the payload data.
            int authTokenLength = cipherSpec.getAuthenticationTagSize();
            int encryptedLength = KeyEntryResolver.decodeInt(stream);
            if (encryptedLength < 0) {
                throw new StreamCorruptedException(
                        "Key length " + encryptedLength + " negative for encrypted key in " + resourceKey);
            } else if (encryptedLength > MAX_PRIVATE_KEY_DATA_SIZE) {
                throw new StreamCorruptedException("Key length " + encryptedLength + " > allowed maximum "
                                                   + MAX_PRIVATE_KEY_DATA_SIZE + " for encrypted key in " + resourceKey);
            }
            encryptedData = new byte[encryptedLength + authTokenLength];
            IoUtils.readFully(stream, encryptedData);
        } else {
            encryptedData = KeyEntryResolver.readRLEBytes(stream, MAX_PRIVATE_KEY_DATA_SIZE);
        }
        Collection<KeyPair> keys = passwordProvider.decode(session, resourceKey, pwd -> {
            byte[] decryptedData = kdfOptions.decodePrivateKeyBytes(session, resourceKey, cipherSpec, encryptedData, pwd);
            try (InputStream bais = new ByteArrayInputStream(decryptedData)) {
                return readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais);
            } finally {
                Arrays.fill(decryptedData, (byte) 0);
            }
        });
        return keys == null ? Collections.emptyList() : keys;
    }