/* * 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.loader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.Pair; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.logging.AbstractLoggingBean; /** * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean implements KeyPairResourceParser { private final List<String> beginners; private final List<String> enders; private final List<List<String>> endingMarkers; /** * @param beginners The markers that indicate the beginning of a parsing block * @param enders The <U>matching</U> (by position) markers that indicate the end of a parsing block */ protected AbstractKeyPairResourceParser(List<String> beginners, List<String> enders) { this.beginners = ValidateUtils.checkNotNullAndNotEmpty(beginners, "No begin markers"); this.enders = ValidateUtils.checkNotNullAndNotEmpty(enders, "No end markers"); ValidateUtils.checkTrue( beginners.size() == enders.size(), "Mismatched begin(%d)/end(%d) markers sizes", beginners.size(), enders.size()); endingMarkers = new ArrayList<>(enders.size()); enders.forEach(m -> endingMarkers.add(Collections.singletonList(m))); } public List<String> getBeginners() { return beginners; } public List<String> getEnders() { return enders; } /** * @return A {@link List} of same size as the ending markers, where * each ending marker is encapsulated inside a singleton list and * resides as the <U>same index</U> as the marker it encapsulates */ public List<List<String>> getEndingMarkers() { return endingMarkers; } @Override public boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException { return KeyPairResourceParser.containsMarkerLine(lines, getBeginners()); } @Override public Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, List<String> lines) throws IOException, GeneralSecurityException { Collection<KeyPair> keyPairs = Collections.emptyList(); List<String> beginMarkers = getBeginners(); List<List<String>> endMarkers = getEndingMarkers(); for (Pair<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers); markerPos != null;) { int startIndex = markerPos.getKey(); String startLine = lines.get(startIndex); startIndex++; int markerIndex = markerPos.getValue(); List<String> ender = endMarkers.get(markerIndex); markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender); if (markerPos == null) { throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex); } int endIndex = markerPos.getKey(); String endLine = lines.get(endIndex); Collection<KeyPair> kps = extractKeyPairs(resourceKey, startLine, endLine, passwordProvider, lines.subList(startIndex, endIndex)); if (GenericUtils.isNotEmpty(kps)) { if (GenericUtils.isEmpty(keyPairs)) { keyPairs = new LinkedList<>(kps); } else { keyPairs.addAll(kps); } } // see if there are more markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers); } return keyPairs; } /** * Extracts the key pairs within a <U>single</U> delimited by markers block of lines. By * default cleans up the empty lines, joins them and converts them from BASE64 * * @param resourceKey A hint as to the origin of the text lines * @param beginMarker The line containing the begin marker * @param endMarker The line containing the end marker * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param lines The block of lines between the markers * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys * @see #extractKeyPairs(String, String, String, FilePasswordProvider, byte[]) */ public Collection<KeyPair> extractKeyPairs( String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, List<String> lines) throws IOException, GeneralSecurityException { return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, KeyPairResourceParser.extractDataBytes(lines)); } /** * @param resourceKey A hint as to the origin of the text lines * @param beginMarker The line containing the begin marker * @param endMarker The line containing the end marker * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param bytes The decoded bytes from the lines containing the data * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys * @see #extractKeyPairs(String, String, String, FilePasswordProvider, InputStream) */ public Collection<KeyPair> extractKeyPairs( String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, byte[] bytes) throws IOException, GeneralSecurityException { if (log.isTraceEnabled()) { BufferUtils.dumpHex(getSimplifiedLogger(), Level.FINER, beginMarker, ':', 16, bytes); } try (InputStream bais = new ByteArrayInputStream(bytes)) { return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, bais); } } /** * @param resourceKey A hint as to the origin of the text lines * @param beginMarker The line containing the begin marker * @param endMarker The line containing the end marker * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param stream The decoded data {@link InputStream} * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys */ public abstract Collection<KeyPair> extractKeyPairs( String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream) throws IOException, GeneralSecurityException; }