2020-10-25 19:54:03 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2020 Paul Schaub.
|
|
|
|
*
|
|
|
|
* 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 org.pgpainless.util;
|
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
2020-11-18 12:20:59 +01:00
|
|
|
import java.io.InputStream;
|
2021-07-15 16:55:13 +02:00
|
|
|
import java.io.OutputStream;
|
2021-05-15 15:56:51 +02:00
|
|
|
import java.util.ArrayList;
|
2021-05-08 14:02:44 +02:00
|
|
|
import java.util.Iterator;
|
2021-05-15 15:56:51 +02:00
|
|
|
import java.util.List;
|
2021-05-15 16:24:01 +02:00
|
|
|
import java.util.regex.Pattern;
|
2020-10-25 19:54:03 +01:00
|
|
|
|
2021-05-15 15:56:51 +02:00
|
|
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
2020-10-25 19:54:03 +01:00
|
|
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
2021-05-08 14:02:44 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
2020-10-25 19:54:03 +01:00
|
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
2021-05-29 13:52:29 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
2020-10-25 19:54:03 +01:00
|
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
2021-05-29 13:52:29 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
2021-05-15 15:56:51 +02:00
|
|
|
import org.pgpainless.algorithm.HashAlgorithm;
|
2021-05-08 14:02:44 +02:00
|
|
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
2020-10-25 19:54:03 +01:00
|
|
|
|
2020-11-20 12:01:39 +01:00
|
|
|
public class ArmorUtils {
|
2020-10-25 19:54:03 +01:00
|
|
|
|
2021-05-15 16:24:01 +02:00
|
|
|
// MessageIDs are 32 printable characters
|
|
|
|
private static final Pattern PATTERN_MESSAGE_ID = Pattern.compile("^\\S{32}$");
|
|
|
|
|
2021-05-15 15:56:51 +02:00
|
|
|
public static final String HEADER_COMMENT = "Comment";
|
|
|
|
public static final String HEADER_VERSION = "Version";
|
|
|
|
public static final String HEADER_MESSAGEID = "MessageID";
|
|
|
|
public static final String HEADER_HASH = "Hash";
|
|
|
|
public static final String HEADER_CHARSET = "Charset";
|
|
|
|
|
2020-10-25 19:54:03 +01:00
|
|
|
public static String toAsciiArmoredString(PGPSecretKeyRing secretKeys) throws IOException {
|
2021-05-08 14:02:44 +02:00
|
|
|
MultiMap<String, String> header = keyToHeader(secretKeys);
|
|
|
|
return toAsciiArmoredString(secretKeys.getEncoded(), header);
|
2020-11-18 12:20:59 +01:00
|
|
|
}
|
2020-10-25 19:54:03 +01:00
|
|
|
|
2020-11-18 12:20:59 +01:00
|
|
|
public static String toAsciiArmoredString(PGPPublicKeyRing publicKeys) throws IOException {
|
2021-05-08 14:02:44 +02:00
|
|
|
MultiMap<String, String> header = keyToHeader(publicKeys);
|
|
|
|
return toAsciiArmoredString(publicKeys.getEncoded(), header);
|
|
|
|
}
|
|
|
|
|
2021-05-29 13:52:29 +02:00
|
|
|
public static String toAsciiArmoredString(PGPSecretKeyRingCollection secretKeyRings) throws IOException {
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (Iterator<PGPSecretKeyRing> iterator = secretKeyRings.iterator(); iterator.hasNext(); ) {
|
|
|
|
PGPSecretKeyRing secretKeyRing = iterator.next();
|
|
|
|
sb.append(toAsciiArmoredString(secretKeyRing));
|
|
|
|
if (iterator.hasNext()) {
|
|
|
|
sb.append('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2021-07-15 16:55:13 +02:00
|
|
|
public static ArmoredOutputStream toAsciiArmoredStream(PGPKeyRing keyRing, OutputStream outputStream) {
|
|
|
|
MultiMap<String, String> header = keyToHeader(keyRing);
|
|
|
|
return toAsciiArmoredStream(outputStream, header);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static ArmoredOutputStream toAsciiArmoredStream(OutputStream outputStream, MultiMap<String, String> header) {
|
|
|
|
ArmoredOutputStream armoredOutputStream = ArmoredOutputStreamFactory.get(outputStream);
|
|
|
|
if (header != null) {
|
|
|
|
for (String headerKey : header.keySet()) {
|
|
|
|
for (String headerValue : header.get(headerKey)) {
|
|
|
|
armoredOutputStream.addHeader(headerKey, headerValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return armoredOutputStream;
|
|
|
|
}
|
|
|
|
|
2021-05-29 13:52:29 +02:00
|
|
|
public static String toAsciiArmoredString(PGPPublicKeyRingCollection publicKeyRings) throws IOException {
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (Iterator<PGPPublicKeyRing> iterator = publicKeyRings.iterator(); iterator.hasNext(); ) {
|
|
|
|
PGPPublicKeyRing publicKeyRing = iterator.next();
|
|
|
|
sb.append(toAsciiArmoredString(publicKeyRing));
|
|
|
|
if (iterator.hasNext()) {
|
|
|
|
sb.append('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2021-05-08 14:02:44 +02:00
|
|
|
private static MultiMap<String, String> keyToHeader(PGPKeyRing keyRing) {
|
|
|
|
MultiMap<String, String> header = new MultiMap<>();
|
|
|
|
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keyRing);
|
|
|
|
Iterator<String> userIds = keyRing.getPublicKey().getUserIDs();
|
|
|
|
|
2021-05-15 15:56:51 +02:00
|
|
|
header.put(HEADER_COMMENT, fingerprint.prettyPrint());
|
2021-05-08 14:02:44 +02:00
|
|
|
if (userIds.hasNext()) {
|
2021-05-15 15:56:51 +02:00
|
|
|
header.put(HEADER_COMMENT, userIds.next());
|
2021-05-08 14:02:44 +02:00
|
|
|
}
|
|
|
|
return header;
|
2020-11-18 12:20:59 +01:00
|
|
|
}
|
2020-10-25 19:54:03 +01:00
|
|
|
|
2020-11-18 12:20:59 +01:00
|
|
|
public static String toAsciiArmoredString(byte[] bytes) throws IOException {
|
2021-05-08 14:02:44 +02:00
|
|
|
return toAsciiArmoredString(bytes, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String toAsciiArmoredString(byte[] bytes, MultiMap<String, String> additionalHeaderValues) throws IOException {
|
|
|
|
return toAsciiArmoredString(new ByteArrayInputStream(bytes), additionalHeaderValues);
|
2020-10-25 19:54:03 +01:00
|
|
|
}
|
|
|
|
|
2020-11-18 12:20:59 +01:00
|
|
|
public static String toAsciiArmoredString(InputStream inputStream) throws IOException {
|
2021-05-08 14:02:44 +02:00
|
|
|
return toAsciiArmoredString(inputStream, null);
|
|
|
|
}
|
|
|
|
|
2021-05-15 16:24:01 +02:00
|
|
|
public static void addHashAlgorithmHeader(ArmoredOutputStream armor, HashAlgorithm hashAlgorithm) {
|
|
|
|
armor.addHeader(HEADER_HASH, hashAlgorithm.getAlgorithmName());
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void addCommentHeader(ArmoredOutputStream armor, String comment) {
|
|
|
|
armor.addHeader(HEADER_COMMENT, comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void addMessageIdHeader(ArmoredOutputStream armor, String messageId) {
|
|
|
|
if (messageId == null) {
|
|
|
|
throw new NullPointerException("MessageID cannot be null.");
|
|
|
|
}
|
|
|
|
if (!PATTERN_MESSAGE_ID.matcher(messageId).matches()) {
|
|
|
|
throw new IllegalArgumentException("MessageIDs MUST consist of 32 printable characters.");
|
|
|
|
}
|
|
|
|
armor.addHeader(HEADER_MESSAGEID, messageId);
|
|
|
|
}
|
|
|
|
|
2021-05-08 14:02:44 +02:00
|
|
|
public static String toAsciiArmoredString(InputStream inputStream, MultiMap<String, String> additionalHeaderValues) throws IOException {
|
2020-10-25 19:54:03 +01:00
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
2021-07-15 16:55:13 +02:00
|
|
|
ArmoredOutputStream armor = toAsciiArmoredStream(out, additionalHeaderValues);
|
2021-07-26 16:19:30 +02:00
|
|
|
StreamUtil.pipeAll(inputStream, armor);
|
2020-10-25 19:54:03 +01:00
|
|
|
armor.close();
|
|
|
|
|
|
|
|
return out.toString();
|
|
|
|
}
|
2021-05-15 15:56:51 +02:00
|
|
|
|
2021-07-15 16:55:13 +02:00
|
|
|
public static ArmoredOutputStream createArmoredOutputStreamFor(PGPKeyRing keyRing, OutputStream outputStream) {
|
|
|
|
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(outputStream);
|
|
|
|
MultiMap<String, String> headerMap = keyToHeader(keyRing);
|
|
|
|
for (String header : headerMap.keySet()) {
|
|
|
|
for (String value : headerMap.get(header)) {
|
|
|
|
armor.addHeader(header, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return armor;
|
|
|
|
}
|
|
|
|
|
2021-05-15 15:56:51 +02:00
|
|
|
public static List<String> getCommendHeaderValues(ArmoredInputStream armor) {
|
|
|
|
return getArmorHeaderValues(armor, HEADER_COMMENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<String> getMessageIdHeaderValues(ArmoredInputStream armor) {
|
|
|
|
return getArmorHeaderValues(armor, HEADER_MESSAGEID);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<String> getHashHeaderValues(ArmoredInputStream armor) {
|
|
|
|
return getArmorHeaderValues(armor, HEADER_HASH);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<HashAlgorithm> getHashAlgorithms(ArmoredInputStream armor) {
|
|
|
|
List<String> algorithmNames = getHashHeaderValues(armor);
|
2021-05-15 16:24:01 +02:00
|
|
|
List<HashAlgorithm> algorithms = new ArrayList<>();
|
2021-05-15 15:56:51 +02:00
|
|
|
for (String name : algorithmNames) {
|
2021-05-15 16:24:01 +02:00
|
|
|
HashAlgorithm algorithm = HashAlgorithm.fromName(name);
|
|
|
|
if (algorithm != null) {
|
|
|
|
algorithms.add(algorithm);
|
|
|
|
}
|
2021-05-15 15:56:51 +02:00
|
|
|
}
|
|
|
|
return algorithms;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<String> getVersionHeaderValues(ArmoredInputStream armor) {
|
|
|
|
return getArmorHeaderValues(armor, HEADER_VERSION);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<String> getCharsetHeaderValues(ArmoredInputStream armor) {
|
|
|
|
return getArmorHeaderValues(armor, HEADER_CHARSET);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static List<String> getArmorHeaderValues(ArmoredInputStream armor, String headerKey) {
|
|
|
|
String[] header = armor.getArmorHeaders();
|
|
|
|
String key = headerKey + ": ";
|
|
|
|
List<String> values = new ArrayList<>();
|
|
|
|
for (String line : header) {
|
|
|
|
if (line.startsWith(key)) {
|
|
|
|
values.add(line.substring(key.length()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return values;
|
|
|
|
}
|
2020-10-25 19:54:03 +01:00
|
|
|
}
|