// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // 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 google.registry.keyring.api; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing; import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; /** * Collection of tools to serialize / deserialize PGP types. * *
There is no way to serialize / deserialize a PGPPrivateKey on its own, you have to pair it * with the public key. */ public final class KeySerializer { private KeySerializer() {} /** * Serialize a PGPPublicKey * *
The reason we're not using {@link PGPPublicKey#getEncoded()} is to use {@link * ArmoredOutputStream}. */ public static byte[] serializePublicKey(PGPPublicKey publicKey) throws IOException { try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) { // NOTE: We have to close the ArmoredOutputStream before calling the underlying OutputStream's // "toByteArray". Failing to do so would result in a truncated serialization as we took the // byte array before the ArmoredOutputStream wrote all the data. // // Even "flushing" the ArmoredOutputStream isn't enough - as there are parts that are only // written by the ArmoredOutputStream when it is closed: the "-----END PGP PRIVATE KEY // BLOCK-----" (or similar) footer. try (ArmoredOutputStream out = new ArmoredOutputStream(byteStream)) { publicKey.encode(out); } return byteStream.toByteArray(); } } /** Deserialize a PGPPublicKey */ public static PGPPublicKey deserializePublicKey(byte[] serialized) throws IOException { return new BcPGPPublicKeyRing( PGPUtil.getDecoderStream( new ByteArrayInputStream(serialized))).getPublicKey(); } /** Serializes a string */ public static byte[] serializeString(String key) { return key.getBytes(UTF_8); } /** Deserializes a string */ public static String deserializeString(byte[] serialized) { return new String(serialized, UTF_8); } /** * Serialize a PGPKeyPair * *
Use this to serialize a PGPPrivateKey as well (pairing it with the corresponding * PGPPublicKey), as private keys can't be serialized on their own. */ public static byte[] serializeKeyPair(PGPKeyPair keyPair) throws IOException, PGPException { try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) { // NOTE: We have to close the ArmoredOutputStream before calling the underlying OutputStream's // "toByteArray". Failing to do so would result in a truncated serialization as we took the // byte array before the ArmoredOutputStream wrote all the data. // // Even "flushing" the ArmoredOutputStream isn't enough - as there are parts that are only // written by the ArmoredOutputStream when it is closed: the "-----END PGP PRIVATE KEY // BLOCK-----" (or similar) footer. try (ArmoredOutputStream out = new ArmoredOutputStream(byteStream)) { new PGPSecretKey( keyPair.getPrivateKey(), keyPair.getPublicKey(), new JcaPGPDigestCalculatorProviderBuilder() .setProvider("BC") .build() .get(HashAlgorithmTags.SHA256), true, null).encode(out); } return byteStream.toByteArray(); } } /** Deserialize a PGPKeyPair */ public static PGPKeyPair deserializeKeyPair(byte[] serialized) throws IOException, PGPException { PGPSecretKey secretKey = new BcPGPSecretKeyRing( PGPUtil.getDecoderStream( new ByteArrayInputStream(serialized))).getSecretKey(); return new PGPKeyPair( secretKey.getPublicKey(), secretKey.extractPrivateKey(createSecretKeyDecryptor())); } private static PBESecretKeyDecryptor createSecretKeyDecryptor() { // There shouldn't be a passphrase on the key return new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) .build(new char[0]); } }