/* * Copyright (c) 2013 Google Inc. * * Licensed 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 com.google.api.client.util; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * {@link Beta} <br/> * PEM file reader. * * <p> * Supports reading any PEM stream that contains Base64 encoded content stored inside * {@code "-----BEGIN ...-----"} and {@code "-----END ...-----"} tags. Each call to * {@link #readNextSection()} parses the next section in the PEM file. If you need a section of a * certain title use {@link #readNextSection(String)}, for example * {@code readNextSection("PRIVATE KEY")}. To ensure that the stream is closed properly, call * {@link #close()} in a finally block. * </p> * * <p> * As a convenience, use {@link #readFirstSectionAndClose(Reader)} or * {@link #readFirstSectionAndClose(Reader, String)} for the common case of only a single section in * a PEM file (or only a single section of a given title). * </p> * * <p> * Limitations: * <p> * * <ul> * <li>Assumes the PEM file section content is not encrypted and cannot handle the case of any * headers inside the BEGIN and END tag.</li> * <li>It also ignores any attributes associated with any PEM file section.</li> * </ul> * * @since 1.14 * @author Yaniv Inbar */ @Beta public final class PemReader { private static final Pattern BEGIN_PATTERN = Pattern.compile("-----BEGIN ([A-Z ]+)-----"); private static final Pattern END_PATTERN = Pattern.compile("-----END ([A-Z ]+)-----"); /** Reader. */ private BufferedReader reader; /** * @param reader reader */ public PemReader(Reader reader) { this.reader = new BufferedReader(reader); } /** Reads the next section in the PEM file or {@code null} for end of file. */ public Section readNextSection() throws IOException { return readNextSection(null); } /** * Reads the next section in the PEM file, optionally based on a title to look for. * * @param titleToLookFor title to look for or {@code null} for any title * @return next section or {@code null} for end of file */ public Section readNextSection(String titleToLookFor) throws IOException { String title = null; StringBuilder keyBuilder = null; while (true) { String line = reader.readLine(); if (line == null) { Preconditions.checkArgument(title == null, "missing end tag (%s)", title); return null; } if (keyBuilder == null) { Matcher m = BEGIN_PATTERN.matcher(line); if (m.matches()) { String curTitle = m.group(1); if (titleToLookFor == null || curTitle.equals(titleToLookFor)) { keyBuilder = new StringBuilder(); title = curTitle; } } } else { Matcher m = END_PATTERN.matcher(line); if (m.matches()) { String endTitle = m.group(1); Preconditions.checkArgument(endTitle.equals(title), "end tag (%s) doesn't match begin tag (%s)", endTitle, title); return new Section(title, Base64.decodeBase64(keyBuilder.toString())); } keyBuilder.append(line); } } } /** * Reads the first section in the PEM file, and then closes the reader. * * @param reader reader * @return first section found or {@code null} for none found */ public static Section readFirstSectionAndClose(Reader reader) throws IOException { return readFirstSectionAndClose(reader, null); } /** * Reads the first section in the PEM file, optionally based on a title to look for, and then * closes the reader. * * @param titleToLookFor title to look for or {@code null} for any title * @param reader reader * @return first section found or {@code null} for none found */ public static Section readFirstSectionAndClose(Reader reader, String titleToLookFor) throws IOException { PemReader pemReader = new PemReader(reader); try { return pemReader.readNextSection(titleToLookFor); } finally { pemReader.close(); } } /** * Closes the reader. * * <p> * To ensure that the stream is closed properly, call {@link #close()} in a finally block. * </p> */ public void close() throws IOException { reader.close(); } /** Section in the PEM file. */ public static final class Section { /** Title. */ private final String title; /** Base64-decoded bytes. */ private final byte[] base64decodedBytes; /** * @param title title * @param base64decodedBytes base64-decoded bytes */ Section(String title, byte[] base64decodedBytes) { this.title = Preconditions.checkNotNull(title); this.base64decodedBytes = Preconditions.checkNotNull(base64decodedBytes); } /** Returns the title. */ public String getTitle() { return title; } /** Returns the base64-decoded bytes (modifiable array). */ public byte[] getBase64DecodedBytes() { return base64decodedBytes; } } }