mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 20:17:51 +02:00
174 lines
6.3 KiB
Java
174 lines
6.3 KiB
Java
// Copyright 2016 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 com.google.common.base.Preconditions.checkNotNull;
|
|
import static com.google.common.base.Verify.verify;
|
|
import static com.google.common.base.Verify.verifyNotNull;
|
|
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.DSA;
|
|
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.ELGAMAL_GENERAL;
|
|
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL;
|
|
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_SIGN;
|
|
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.base.VerifyException;
|
|
import java.io.IOException;
|
|
import java.util.Iterator;
|
|
import org.bouncycastle.openpgp.PGPException;
|
|
import org.bouncycastle.openpgp.PGPKeyPair;
|
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing;
|
|
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
|
|
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
|
|
|
/** Helper functions for extracting PGP keys from their keyrings. */
|
|
public final class PgpHelper {
|
|
|
|
/**
|
|
* Narrowed key search requirements.
|
|
* @see PgpHelper#lookupPublicKey
|
|
*/
|
|
public static enum KeyRequirement { ENCRYPT, SIGN, ENCRYPT_SIGN }
|
|
|
|
/** Converts {@code publicKey} to bytes. */
|
|
public static byte[] convertPublicKeyToBytes(PGPPublicKey publicKey) {
|
|
try {
|
|
return publicKey.getEncoded();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/** Returns raw key bytes as a Bouncy Castle PGP public key. */
|
|
public static PGPPublicKey loadPublicKeyBytes(byte[] data) {
|
|
try {
|
|
return lookupPublicSubkey(new BcPGPPublicKeyRing(data), KeyRequirement.ENCRYPT).get();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search for public key on keyring based on a substring (like an email address).
|
|
*
|
|
* @throws VerifyException if the key couldn't be found.
|
|
* @see #lookupKeyPair
|
|
*/
|
|
public static PGPPublicKey lookupPublicKey(
|
|
PGPPublicKeyRingCollection keyring, String query, KeyRequirement want) {
|
|
try {
|
|
// Safe by specification.
|
|
@SuppressWarnings("unchecked")
|
|
Iterator<PGPPublicKeyRing> results =
|
|
keyring.getKeyRings(checkNotNull(query, "query"), true, true);
|
|
verify(results.hasNext(), "No public key found matching substring: %s", query);
|
|
while (results.hasNext()) {
|
|
Optional<PGPPublicKey> result = lookupPublicSubkey(results.next(), want);
|
|
if (result.isPresent()) {
|
|
return result.get();
|
|
}
|
|
}
|
|
throw new VerifyException(String.format(
|
|
"No public key (%s) found matching substring: %s", want, query));
|
|
} catch (PGPException e) {
|
|
throw new VerifyException(String.format(
|
|
"Public key lookup with query %s failed: %s", query, e.getMessage()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as {@link #lookupPublicKey} but also retrieves the associated private key.
|
|
*
|
|
* @throws VerifyException if either keys couldn't be found.
|
|
* @see #lookupPublicKey
|
|
*/
|
|
@SuppressWarnings("deprecation")
|
|
public static PGPKeyPair lookupKeyPair(
|
|
PGPPublicKeyRingCollection publics,
|
|
PGPSecretKeyRingCollection privates,
|
|
String query,
|
|
KeyRequirement want) {
|
|
PGPPublicKey publicKey = lookupPublicKey(publics, query, want);
|
|
PGPPrivateKey privateKey;
|
|
try {
|
|
PGPSecretKey secret = verifyNotNull(privates.getSecretKey(publicKey.getKeyID()),
|
|
"Keyring missing private key associated with public key id: %x (query '%s')",
|
|
publicKey.getKeyID(), query);
|
|
// We do not support putting a password on the private key so we're just going to
|
|
// put char[0] here.
|
|
privateKey = secret.extractPrivateKey(
|
|
new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
|
|
.build(new char[0]));
|
|
} catch (PGPException e) {
|
|
throw new VerifyException(e.getMessage());
|
|
}
|
|
return new PGPKeyPair(publicKey, privateKey);
|
|
}
|
|
|
|
/**
|
|
* Return appropriate key or subkey for given task from public key.
|
|
*
|
|
* <p>Weirder older PGP public keys will actually have multiple keys. The main key will usually
|
|
* be sign-only in such situations. So you've gotta go digging in through the key packets and
|
|
* make sure you get the one that's valid for encryption, or whatever you want to do.
|
|
*/
|
|
public static Optional<PGPPublicKey> lookupPublicSubkey(
|
|
PGPPublicKeyRing ring, KeyRequirement want) {
|
|
// Safe by specification.
|
|
@SuppressWarnings("unchecked")
|
|
Iterator<PGPPublicKey> keys = ring.getPublicKeys();
|
|
while (keys.hasNext()) {
|
|
PGPPublicKey key = keys.next();
|
|
switch (want) {
|
|
case ENCRYPT:
|
|
if (key.isEncryptionKey()) {
|
|
return Optional.of(key);
|
|
}
|
|
break;
|
|
case SIGN:
|
|
if (isSigningKey(key)) {
|
|
return Optional.of(key);
|
|
}
|
|
break;
|
|
case ENCRYPT_SIGN:
|
|
if (key.isEncryptionKey() && isSigningKey(key)) {
|
|
return Optional.of(key);
|
|
}
|
|
break;
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
return Optional.absent();
|
|
}
|
|
|
|
/** Returns {@code true} if this key can be used for signing. */
|
|
public static boolean isSigningKey(PGPPublicKey key) {
|
|
switch (key.getAlgorithm()) {
|
|
case RSA_GENERAL:
|
|
case RSA_SIGN:
|
|
case DSA:
|
|
case ELGAMAL_GENERAL:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|