mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-10 18:59:39 +02:00
Rename module painless-core -> pgpainless-core
Fix build.gradle
This commit is contained in:
parent
a80bb043eb
commit
39c5464c37
102 changed files with 1 additions and 14 deletions
94
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
94
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.DecryptionBuilder;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.encryption_signing.EncryptionBuilder;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.key.generation.KeyRingBuilder;
|
||||
import org.pgpainless.key.parsing.KeyRingReader;
|
||||
import org.pgpainless.symmetric_encryption.SymmetricEncryptorDecryptor;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PGPainless {
|
||||
|
||||
/**
|
||||
* Generate a new OpenPGP key ring.
|
||||
* @return builder
|
||||
*/
|
||||
public static KeyRingBuilder generateKeyRing() {
|
||||
return new KeyRingBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an existing OpenPGP key ring.
|
||||
* @return builder
|
||||
*/
|
||||
public static KeyRingReader readKeyRing() {
|
||||
return new KeyRingReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link EncryptionStream}, which can be used to encrypt and/or sign data using OpenPGP.
|
||||
* @return builder
|
||||
*/
|
||||
public static EncryptionBuilder createEncryptor() {
|
||||
return new EncryptionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link DecryptionStream}, which can be used to decrypt and/or verify data using OpenPGP.
|
||||
* @return builder
|
||||
*/
|
||||
public static DecryptionBuilder createDecryptor() {
|
||||
return new DecryptionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt some data symmetrically using OpenPGP and a password.
|
||||
* The resulting data will be uncompressed and integrity protected.
|
||||
*
|
||||
* @param data input data.
|
||||
* @param password password.
|
||||
* @return symmetrically OpenPGP encrypted data.
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws PGPException PGP is brittle.
|
||||
*/
|
||||
public static byte[] encryptWithPassword(byte[] data, Passphrase password, SymmetricKeyAlgorithm algorithm) throws IOException, PGPException {
|
||||
return SymmetricEncryptorDecryptor.symmetricallyEncrypt(data, password,
|
||||
algorithm, CompressionAlgorithm.UNCOMPRESSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt some symmetrically encrypted data using a password.
|
||||
* The data is suspected to be integrity protected.
|
||||
*
|
||||
* @param data symmetrically OpenPGP encrypted data.
|
||||
* @param password password.
|
||||
* @return decrypted data.
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws PGPException PGP is brittle.
|
||||
*/
|
||||
public static byte[] decryptWithPassword(byte[] data, Passphrase password) throws IOException, PGPException {
|
||||
return SymmetricEncryptorDecryptor.symmetricallyDecrypt(data, password);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AlgorithmSuite {
|
||||
|
||||
private static AlgorithmSuite defaultAlgorithmSuite = new AlgorithmSuite(
|
||||
Arrays.asList(
|
||||
SymmetricKeyAlgorithm.AES_256,
|
||||
SymmetricKeyAlgorithm.AES_192,
|
||||
SymmetricKeyAlgorithm.AES_128),
|
||||
Arrays.asList(
|
||||
HashAlgorithm.SHA512,
|
||||
HashAlgorithm.SHA384,
|
||||
HashAlgorithm.SHA256,
|
||||
HashAlgorithm.SHA224),
|
||||
Arrays.asList(
|
||||
CompressionAlgorithm.ZLIB,
|
||||
CompressionAlgorithm.BZIP2,
|
||||
CompressionAlgorithm.ZIP,
|
||||
CompressionAlgorithm.UNCOMPRESSED)
|
||||
);
|
||||
|
||||
private List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms;
|
||||
private List<HashAlgorithm> hashAlgorithms;
|
||||
private List<CompressionAlgorithm> compressionAlgorithms;
|
||||
|
||||
public AlgorithmSuite(List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms,
|
||||
List<HashAlgorithm> hashAlgorithms,
|
||||
List<CompressionAlgorithm> compressionAlgorithms) {
|
||||
this.symmetricKeyAlgorithms = Collections.unmodifiableList(symmetricKeyAlgorithms);
|
||||
this.hashAlgorithms = Collections.unmodifiableList(hashAlgorithms);
|
||||
this.compressionAlgorithms = Collections.unmodifiableList(compressionAlgorithms);
|
||||
}
|
||||
|
||||
public void setSymmetricKeyAlgorithms(List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms) {
|
||||
this.symmetricKeyAlgorithms = symmetricKeyAlgorithms;
|
||||
}
|
||||
|
||||
public List<SymmetricKeyAlgorithm> getSymmetricKeyAlgorithms() {
|
||||
return new ArrayList<>(symmetricKeyAlgorithms);
|
||||
}
|
||||
|
||||
public int[] getSymmetricKeyAlgorithmIds() {
|
||||
int[] array = new int[symmetricKeyAlgorithms.size()];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = symmetricKeyAlgorithms.get(i).getAlgorithmId();
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setHashAlgorithms(List<HashAlgorithm> hashAlgorithms) {
|
||||
this.hashAlgorithms = hashAlgorithms;
|
||||
}
|
||||
|
||||
public List<HashAlgorithm> getHashAlgorithms() {
|
||||
return hashAlgorithms;
|
||||
}
|
||||
|
||||
public int[] getHashAlgorithmIds() {
|
||||
int[] array = new int[hashAlgorithms.size()];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = hashAlgorithms.get(i).getAlgorithmId();
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setCompressionAlgorithms(List<CompressionAlgorithm> compressionAlgorithms) {
|
||||
this.compressionAlgorithms = compressionAlgorithms;
|
||||
}
|
||||
|
||||
public List<CompressionAlgorithm> getCompressionAlgorithms() {
|
||||
return compressionAlgorithms;
|
||||
}
|
||||
|
||||
public int[] getCompressionAlgorithmIds() {
|
||||
int[] array = new int[compressionAlgorithms.size()];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = compressionAlgorithms.get(i).getAlgorithmId();
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static AlgorithmSuite getDefaultAlgorithmSuite() {
|
||||
return defaultAlgorithmSuite;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
|
||||
public enum CompressionAlgorithm {
|
||||
|
||||
UNCOMPRESSED (CompressionAlgorithmTags.UNCOMPRESSED),
|
||||
ZIP (CompressionAlgorithmTags.ZIP),
|
||||
ZLIB (CompressionAlgorithmTags.ZLIB),
|
||||
BZIP2 (CompressionAlgorithmTags.BZIP2),
|
||||
;
|
||||
|
||||
private static final Map<Integer, CompressionAlgorithm> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (CompressionAlgorithm c : CompressionAlgorithm.values()) {
|
||||
MAP.put(c.algorithmId, c);
|
||||
}
|
||||
}
|
||||
|
||||
public static CompressionAlgorithm fromId(int id) {
|
||||
return MAP.get(id);
|
||||
}
|
||||
|
||||
private final int algorithmId;
|
||||
|
||||
CompressionAlgorithm(int id) {
|
||||
this.algorithmId = id;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return algorithmId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.Features;
|
||||
|
||||
public enum Feature {
|
||||
|
||||
/**
|
||||
* Add modification detection package.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.14">
|
||||
* RFC-4880 §5.14: Modification Detection Code Packet</a>
|
||||
*/
|
||||
MODIFICATION_DETECTION(Features.FEATURE_MODIFICATION_DETECTION),
|
||||
;
|
||||
|
||||
private static final Map<Byte, Feature> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Feature f : Feature.values()) {
|
||||
MAP.put(f.featureId, f);
|
||||
}
|
||||
}
|
||||
|
||||
public static Feature fromId(byte id) {
|
||||
return MAP.get(id);
|
||||
}
|
||||
|
||||
private final byte featureId;
|
||||
|
||||
Feature(byte featureId) {
|
||||
this.featureId = featureId;
|
||||
}
|
||||
|
||||
public byte getFeatureId() {
|
||||
return featureId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
|
||||
public enum HashAlgorithm {
|
||||
|
||||
MD5 (HashAlgorithmTags.MD5),
|
||||
SHA1 (HashAlgorithmTags.SHA1),
|
||||
RIPEMD160 (HashAlgorithmTags.RIPEMD160),
|
||||
DOUBLE_SHA (HashAlgorithmTags.DOUBLE_SHA),
|
||||
MD2 (HashAlgorithmTags.MD2),
|
||||
TIGER_192 (HashAlgorithmTags.TIGER_192),
|
||||
HAVAL_5_160(HashAlgorithmTags.HAVAL_5_160),
|
||||
SHA256 (HashAlgorithmTags.SHA256),
|
||||
SHA384 (HashAlgorithmTags.SHA384),
|
||||
SHA512 (HashAlgorithmTags.SHA512),
|
||||
SHA224 (HashAlgorithmTags.SHA224),
|
||||
;
|
||||
// Coincidence? I don't this so...
|
||||
private static final Map<Integer, HashAlgorithm> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (HashAlgorithm h : HashAlgorithm.values()) {
|
||||
MAP.put(h.algorithmId, h);
|
||||
}
|
||||
}
|
||||
|
||||
public static HashAlgorithm fromId(int id) {
|
||||
return MAP.get(id);
|
||||
}
|
||||
|
||||
private final int algorithmId;
|
||||
|
||||
HashAlgorithm(int id) {
|
||||
this.algorithmId = id;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return algorithmId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
|
||||
public enum KeyFlag {
|
||||
|
||||
CERTIFY_OTHER (KeyFlags.CERTIFY_OTHER),
|
||||
SIGN_DATA (KeyFlags.SIGN_DATA),
|
||||
ENCRYPT_COMMS (KeyFlags.ENCRYPT_COMMS),
|
||||
ENCRYPT_STORAGE(KeyFlags.ENCRYPT_STORAGE),
|
||||
SPLIT (KeyFlags.SPLIT),
|
||||
AUTHENTICATION (KeyFlags.AUTHENTICATION),
|
||||
SHARED (KeyFlags.SHARED),
|
||||
;
|
||||
|
||||
private final int flag;
|
||||
|
||||
KeyFlag(int flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public int getFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public static List<KeyFlag> fromInteger(int bitmask) {
|
||||
List<KeyFlag> flags = new ArrayList<>();
|
||||
for (KeyFlag f : KeyFlag.values()) {
|
||||
if ((bitmask & f.flag) != 0) {
|
||||
flags.add(f);
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
|
||||
public enum PublicKeyAlgorithm {
|
||||
|
||||
RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL),
|
||||
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT),
|
||||
RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN),
|
||||
ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT),
|
||||
DSA (PublicKeyAlgorithmTags.DSA),
|
||||
/**
|
||||
* EC is deprecated.
|
||||
* @deprecated use {@link #ECDH} instead.
|
||||
*/
|
||||
EC (PublicKeyAlgorithmTags.EC),
|
||||
ECDH (PublicKeyAlgorithmTags.ECDH),
|
||||
ECDSA (PublicKeyAlgorithmTags.ECDSA),
|
||||
ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL),
|
||||
DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN),
|
||||
;
|
||||
|
||||
private static final Map<Integer, PublicKeyAlgorithm> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (PublicKeyAlgorithm p : PublicKeyAlgorithm.values()) {
|
||||
MAP.put(p.algorithmId, p);
|
||||
}
|
||||
}
|
||||
|
||||
public static PublicKeyAlgorithm fromId(int id) {
|
||||
return MAP.get(id);
|
||||
}
|
||||
|
||||
private final int algorithmId;
|
||||
|
||||
PublicKeyAlgorithm(int algorithmId) {
|
||||
this.algorithmId = algorithmId;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return algorithmId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
|
||||
public enum SymmetricKeyAlgorithm {
|
||||
|
||||
NULL (SymmetricKeyAlgorithmTags.NULL),
|
||||
/**
|
||||
* IDEA is deprecated.
|
||||
* @deprecated use a different algorithm.
|
||||
*/
|
||||
IDEA (SymmetricKeyAlgorithmTags.IDEA),
|
||||
TRIPLE_DES (SymmetricKeyAlgorithmTags.TRIPLE_DES),
|
||||
CAST5 (SymmetricKeyAlgorithmTags.CAST5),
|
||||
BLOWFISH (SymmetricKeyAlgorithmTags.BLOWFISH),
|
||||
SAFER (SymmetricKeyAlgorithmTags.SAFER),
|
||||
DES (SymmetricKeyAlgorithmTags.DES),
|
||||
AES_128 (SymmetricKeyAlgorithmTags.AES_128),
|
||||
AES_192 (SymmetricKeyAlgorithmTags.AES_192),
|
||||
AES_256 (SymmetricKeyAlgorithmTags.AES_256),
|
||||
TWOFISH (SymmetricKeyAlgorithmTags.TWOFISH),
|
||||
CAMELLIA_128 (SymmetricKeyAlgorithmTags.CAMELLIA_128),
|
||||
CAMELLIA_192 (SymmetricKeyAlgorithmTags.CAMELLIA_192),
|
||||
CAMELLIA_256 (SymmetricKeyAlgorithmTags.CAMELLIA_256),
|
||||
;
|
||||
|
||||
private static final Map<Integer, SymmetricKeyAlgorithm> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (SymmetricKeyAlgorithm s : SymmetricKeyAlgorithm.values()) {
|
||||
MAP.put(s.algorithmId, s);
|
||||
}
|
||||
}
|
||||
|
||||
public static SymmetricKeyAlgorithm fromId(int id) {
|
||||
return MAP.get(id);
|
||||
}
|
||||
|
||||
private final int algorithmId;
|
||||
|
||||
SymmetricKeyAlgorithm(int algorithmId) {
|
||||
this.algorithmId = algorithmId;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return algorithmId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Enums which map to OpenPGP's algorithm IDs.
|
||||
*/
|
||||
package org.pgpainless.algorithm;
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
||||
public class DecryptionBuilder implements DecryptionBuilderInterface {
|
||||
|
||||
private InputStream inputStream;
|
||||
private PGPSecretKeyRingCollection decryptionKeys;
|
||||
private SecretKeyRingProtector decryptionKeyDecryptor;
|
||||
private Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
|
||||
private MissingPublicKeyCallback missingPublicKeyCallback = null;
|
||||
|
||||
@Override
|
||||
public DecryptWith onInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
return new DecryptWithImpl();
|
||||
}
|
||||
|
||||
class DecryptWithImpl implements DecryptWith {
|
||||
|
||||
@Override
|
||||
public VerifyWith decryptWith(SecretKeyRingProtector decryptor, PGPSecretKeyRingCollection secretKeyRings) {
|
||||
DecryptionBuilder.this.decryptionKeys = secretKeyRings;
|
||||
DecryptionBuilder.this.decryptionKeyDecryptor = decryptor;
|
||||
return new VerifyWithImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerifyWith doNotDecrypt() {
|
||||
DecryptionBuilder.this.decryptionKeys = null;
|
||||
DecryptionBuilder.this.decryptionKeyDecryptor = null;
|
||||
return new VerifyWithImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyWithImpl implements VerifyWith {
|
||||
|
||||
@Override
|
||||
public HandleMissingPublicKeys verifyWith(PGPPublicKeyRingCollection publicKeyRingCollection) {
|
||||
Set<PGPPublicKeyRing> publicKeyRings = new HashSet<>();
|
||||
for (Iterator<PGPPublicKeyRing> i = publicKeyRingCollection.getKeyRings(); i.hasNext(); ) {
|
||||
publicKeyRings.add(i.next());
|
||||
}
|
||||
return verifyWith(publicKeyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandleMissingPublicKeys verifyWith(Set<OpenPgpV4Fingerprint> trustedKeyIds,
|
||||
PGPPublicKeyRingCollection publicKeyRingCollection) {
|
||||
Set<PGPPublicKeyRing> publicKeyRings = new HashSet<>();
|
||||
for (Iterator<PGPPublicKeyRing> i = publicKeyRingCollection.getKeyRings(); i.hasNext(); ) {
|
||||
PGPPublicKeyRing p = i.next();
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(p);
|
||||
if (trustedKeyIds.contains(fingerprint)) {
|
||||
publicKeyRings.add(p);
|
||||
}
|
||||
}
|
||||
return verifyWith(publicKeyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandleMissingPublicKeys verifyWith(Set<PGPPublicKeyRing> publicKeyRings) {
|
||||
DecryptionBuilder.this.verificationKeys = publicKeyRings;
|
||||
return new HandleMissingPublicKeysImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Build doNotVerify() {
|
||||
DecryptionBuilder.this.verificationKeys = null;
|
||||
return new BuildImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class HandleMissingPublicKeysImpl implements HandleMissingPublicKeys {
|
||||
|
||||
@Override
|
||||
public Build handleMissingPublicKeysWith(MissingPublicKeyCallback callback) {
|
||||
DecryptionBuilder.this.missingPublicKeyCallback = callback;
|
||||
return new BuildImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Build ignoreMissingPublicKeys() {
|
||||
DecryptionBuilder.this.missingPublicKeyCallback = null;
|
||||
return new BuildImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class BuildImpl implements Build {
|
||||
|
||||
@Override
|
||||
public DecryptionStream build() throws IOException, PGPException {
|
||||
return DecryptionStreamFactory.create(inputStream,
|
||||
decryptionKeys, decryptionKeyDecryptor, verificationKeys, missingPublicKeyCallback);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
||||
public interface DecryptionBuilderInterface {
|
||||
|
||||
DecryptWith onInputStream(InputStream inputStream);
|
||||
|
||||
interface DecryptWith {
|
||||
|
||||
VerifyWith decryptWith(SecretKeyRingProtector decryptor, PGPSecretKeyRingCollection secretKeyRings);
|
||||
|
||||
VerifyWith doNotDecrypt();
|
||||
|
||||
}
|
||||
|
||||
interface VerifyWith {
|
||||
|
||||
HandleMissingPublicKeys verifyWith(PGPPublicKeyRingCollection publicKeyRings);
|
||||
|
||||
HandleMissingPublicKeys verifyWith(Set<OpenPgpV4Fingerprint> trustedFingerprints, PGPPublicKeyRingCollection publicKeyRings);
|
||||
|
||||
HandleMissingPublicKeys verifyWith(Set<PGPPublicKeyRing> publicKeyRings);
|
||||
|
||||
Build doNotVerify();
|
||||
|
||||
}
|
||||
|
||||
interface HandleMissingPublicKeys {
|
||||
|
||||
Build handleMissingPublicKeysWith(MissingPublicKeyCallback callback);
|
||||
|
||||
Build ignoreMissingPublicKeys();
|
||||
}
|
||||
|
||||
interface Build {
|
||||
|
||||
DecryptionStream build() throws IOException, PGPException;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class DecryptionStream extends InputStream {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final PainlessResult.Builder resultBuilder;
|
||||
private boolean isClosed = false;
|
||||
|
||||
DecryptionStream(InputStream wrapped, PainlessResult.Builder resultBuilder) {
|
||||
|
||||
if (wrapped == null) {
|
||||
throw new NullPointerException("Wrapped InputStream MUST NOT be null!");
|
||||
}
|
||||
|
||||
this.inputStream = wrapped;
|
||||
this.resultBuilder = resultBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
this.isClosed = true;
|
||||
}
|
||||
|
||||
public PainlessResult getResult() {
|
||||
if (!isClosed) {
|
||||
throw new IllegalStateException("DecryptionStream MUST be closed before the result can be accessed.");
|
||||
}
|
||||
return resultBuilder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
||||
public final class DecryptionStreamFactory {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DecryptionStreamFactory.class.getName());
|
||||
private static final Level LEVEL = Level.FINE;
|
||||
|
||||
private final PGPSecretKeyRingCollection decryptionKeys;
|
||||
private final SecretKeyRingProtector decryptionKeyDecryptor;
|
||||
private final Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
|
||||
private final MissingPublicKeyCallback missingPublicKeyCallback;
|
||||
|
||||
private final PainlessResult.Builder resultBuilder = PainlessResult.getBuilder();
|
||||
private final PGPContentVerifierBuilderProvider verifierBuilderProvider = new BcPGPContentVerifierBuilderProvider();
|
||||
private final KeyFingerPrintCalculator fingerCalc = new BcKeyFingerprintCalculator();
|
||||
private final Map<OpenPgpV4Fingerprint, PGPOnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
||||
|
||||
private DecryptionStreamFactory(PGPSecretKeyRingCollection decryptionKeys,
|
||||
SecretKeyRingProtector decryptor,
|
||||
Set<PGPPublicKeyRing> verificationKeys,
|
||||
MissingPublicKeyCallback missingPublicKeyCallback) {
|
||||
this.decryptionKeys = decryptionKeys;
|
||||
this.decryptionKeyDecryptor = decryptor;
|
||||
this.verificationKeys.addAll(verificationKeys != null ? verificationKeys : Collections.emptyList());
|
||||
this.missingPublicKeyCallback = missingPublicKeyCallback;
|
||||
}
|
||||
|
||||
public static DecryptionStream create(InputStream inputStream,
|
||||
PGPSecretKeyRingCollection decryptionKeys,
|
||||
SecretKeyRingProtector decryptor,
|
||||
Set<PGPPublicKeyRing> verificationKeys,
|
||||
MissingPublicKeyCallback missingPublicKeyCallback)
|
||||
throws IOException, PGPException {
|
||||
|
||||
DecryptionStreamFactory factory = new DecryptionStreamFactory(decryptionKeys,
|
||||
decryptor,
|
||||
verificationKeys,
|
||||
missingPublicKeyCallback);
|
||||
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(
|
||||
PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator());
|
||||
|
||||
return new DecryptionStream(factory.wrap(objectFactory), factory.resultBuilder);
|
||||
}
|
||||
|
||||
private InputStream wrap(PGPObjectFactory objectFactory) throws IOException, PGPException {
|
||||
|
||||
Object pgpObj;
|
||||
while ((pgpObj = objectFactory.nextObject()) != null) {
|
||||
|
||||
if (pgpObj instanceof PGPEncryptedDataList) {
|
||||
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
|
||||
PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpObj;
|
||||
InputStream nextStream = decrypt(encDataList);
|
||||
objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
|
||||
return wrap(objectFactory);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPCompressedData) {
|
||||
PGPCompressedData compressedData = (PGPCompressedData) pgpObj;
|
||||
InputStream nextStream = compressedData.getDataStream();
|
||||
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
|
||||
objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
|
||||
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " +
|
||||
CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
|
||||
return wrap(objectFactory);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPOnePassSignatureList) {
|
||||
PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) pgpObj;
|
||||
LOGGER.log(LEVEL, "Encountered PGPOnePassSignatureList of size " + onePassSignatures.size());
|
||||
initOnePassSignatures(onePassSignatures);
|
||||
return wrap(objectFactory);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPLiteralData) {
|
||||
LOGGER.log(LEVEL, "Found PGPLiteralData");
|
||||
PGPLiteralData literalData = (PGPLiteralData) pgpObj;
|
||||
InputStream literalDataInputStream = literalData.getInputStream();
|
||||
|
||||
if (verifiableOnePassSignatures.isEmpty()) {
|
||||
LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
|
||||
return literalDataInputStream;
|
||||
}
|
||||
|
||||
return new SignatureVerifyingInputStream(literalDataInputStream,
|
||||
objectFactory, verifiableOnePassSignatures, resultBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PGPException("No Literal Data Packet found");
|
||||
}
|
||||
|
||||
private InputStream decrypt(PGPEncryptedDataList encryptedDataList)
|
||||
throws PGPException {
|
||||
Iterator<?> iterator = encryptedDataList.getEncryptedDataObjects();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Decryption failed - EncryptedDataList has no items");
|
||||
}
|
||||
|
||||
PGPPrivateKey decryptionKey = null;
|
||||
PGPPublicKeyEncryptedData encryptedSessionKey = null;
|
||||
while (iterator.hasNext()) {
|
||||
PGPPublicKeyEncryptedData encryptedData = (PGPPublicKeyEncryptedData) iterator.next();
|
||||
long keyId = encryptedData.getKeyID();
|
||||
|
||||
resultBuilder.addRecipientKeyId(keyId);
|
||||
LOGGER.log(LEVEL, "PGPEncryptedData is encrypted for key " + Long.toHexString(keyId));
|
||||
|
||||
PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId);
|
||||
if (secretKey != null) {
|
||||
LOGGER.log(LEVEL, "Found respective secret key " + Long.toHexString(keyId));
|
||||
encryptedSessionKey = encryptedData;
|
||||
decryptionKey = secretKey.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(keyId));
|
||||
resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(secretKey));
|
||||
}
|
||||
}
|
||||
|
||||
if (decryptionKey == null) {
|
||||
throw new PGPException("Decryption failed - No suitable decryption key found");
|
||||
}
|
||||
|
||||
PublicKeyDataDecryptorFactory keyDecryptor = new BcPublicKeyDataDecryptorFactory(decryptionKey);
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm
|
||||
.fromId(encryptedSessionKey.getSymmetricAlgorithm(keyDecryptor));
|
||||
|
||||
LOGGER.log(LEVEL, "Message is encrypted using " + symmetricKeyAlgorithm);
|
||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
||||
|
||||
if (encryptedSessionKey.isIntegrityProtected()) {
|
||||
LOGGER.log(LEVEL, "Message is integrity protected");
|
||||
resultBuilder.setIntegrityProtected(true);
|
||||
} else {
|
||||
LOGGER.log(LEVEL, "Message is not integrity protected");
|
||||
resultBuilder.setIntegrityProtected(false);
|
||||
}
|
||||
InputStream decryptionStream = encryptedSessionKey.getDataStream(keyDecryptor);
|
||||
|
||||
return decryptionStream;
|
||||
}
|
||||
|
||||
private void initOnePassSignatures(PGPOnePassSignatureList onePassSignatureList) throws PGPException {
|
||||
Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Verification failed - No OnePassSignatures found");
|
||||
}
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
PGPOnePassSignature signature = iterator.next();
|
||||
final long keyId = signature.getKeyID();
|
||||
resultBuilder.addUnverifiedSignatureKeyId(keyId);
|
||||
|
||||
LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
|
||||
|
||||
// Find public key
|
||||
PGPPublicKey verificationKey = null;
|
||||
for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
|
||||
verificationKey = publicKeyRing.getPublicKey(keyId);
|
||||
if (verificationKey != null) {
|
||||
LOGGER.log(LEVEL, "Found respective public key " + Long.toHexString(keyId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (verificationKey == null) {
|
||||
LOGGER.log(Level.INFO, "No public key for signature of " + Long.toHexString(keyId) + " found.");
|
||||
|
||||
if (missingPublicKeyCallback == null) {
|
||||
LOGGER.log(Level.INFO, "Skip signature of " + Long.toHexString(keyId));
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPPublicKey missingPublicKey = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId);
|
||||
if (missingPublicKey == null) {
|
||||
LOGGER.log(Level.INFO, "Skip signature of " + Long.toHexString(keyId));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (missingPublicKey.getKeyID() != keyId) {
|
||||
throw new IllegalArgumentException("KeyID of the provided public key differs from the signatures keyId. " +
|
||||
"The signature was created from " + Long.toHexString(keyId) + " while the provided key has ID " +
|
||||
Long.toHexString(missingPublicKey.getKeyID()));
|
||||
}
|
||||
|
||||
verificationKey = missingPublicKey;
|
||||
}
|
||||
|
||||
signature.init(verifierBuilderProvider, verificationKey);
|
||||
verifiableOnePassSignatures.put(new OpenPgpV4Fingerprint(verificationKey), signature);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
public interface MissingPublicKeyCallback {
|
||||
|
||||
PGPPublicKey onMissingPublicKeyEncountered(Long keyId);
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public class PainlessResult {
|
||||
|
||||
private final Set<Long> recipientKeyIds;
|
||||
private final OpenPgpV4Fingerprint decryptionFingerprint;
|
||||
private final Set<Long> unverifiedSignatureKeyIds;
|
||||
private final Set<OpenPgpV4Fingerprint> verifiedSignaturesFingerprints;
|
||||
|
||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||
private final CompressionAlgorithm compressionAlgorithm;
|
||||
private final boolean integrityProtected;
|
||||
|
||||
public PainlessResult(Set<Long> recipientKeyIds,
|
||||
OpenPgpV4Fingerprint decryptionFingerprint,
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
CompressionAlgorithm algorithm,
|
||||
boolean integrityProtected,
|
||||
Set<Long> unverifiedSignatureKeyIds,
|
||||
Set<OpenPgpV4Fingerprint> verifiedSignaturesFingerprints) {
|
||||
|
||||
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
||||
this.decryptionFingerprint = decryptionFingerprint;
|
||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
this.compressionAlgorithm = algorithm;
|
||||
this.integrityProtected = integrityProtected;
|
||||
this.unverifiedSignatureKeyIds = Collections.unmodifiableSet(unverifiedSignatureKeyIds);
|
||||
this.verifiedSignaturesFingerprints = Collections.unmodifiableSet(verifiedSignaturesFingerprints);
|
||||
}
|
||||
|
||||
public Set<Long> getRecipientKeyIds() {
|
||||
return recipientKeyIds;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
return !getRecipientKeyIds().isEmpty();
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint getDecryptionFingerprint() {
|
||||
return decryptionFingerprint;
|
||||
}
|
||||
|
||||
public SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
|
||||
return symmetricKeyAlgorithm;
|
||||
}
|
||||
|
||||
public CompressionAlgorithm getCompressionAlgorithm() {
|
||||
return compressionAlgorithm;
|
||||
}
|
||||
|
||||
public boolean isIntegrityProtected() {
|
||||
return integrityProtected;
|
||||
}
|
||||
|
||||
public Set<Long> getAllSignatureKeyFingerprints() {
|
||||
return unverifiedSignatureKeyIds;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return !unverifiedSignatureKeyIds.isEmpty();
|
||||
}
|
||||
|
||||
public Set<OpenPgpV4Fingerprint> getVerifiedSignaturesFingerprints() {
|
||||
return verifiedSignaturesFingerprints;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return !verifiedSignaturesFingerprints.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing publicKeys) {
|
||||
for (PGPPublicKey key : publicKeys) {
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(key);
|
||||
if (verifiedSignaturesFingerprints.contains(fingerprint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Builder getBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
|
||||
private final Set<Long> recipientFingerprints = new HashSet<>();
|
||||
private OpenPgpV4Fingerprint decryptionFingerprint;
|
||||
private final Set<Long> unverifiedSignatureKeyIds = new HashSet<>();
|
||||
private final Set<OpenPgpV4Fingerprint> verifiedSignatureFingerprints = new HashSet<>();
|
||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
private boolean integrityProtected = false;
|
||||
|
||||
public Builder addRecipientKeyId(Long keyId) {
|
||||
this.recipientFingerprints.add(keyId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDecryptionFingerprint(OpenPgpV4Fingerprint fingerprint) {
|
||||
this.decryptionFingerprint = fingerprint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
|
||||
this.compressionAlgorithm = algorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addUnverifiedSignatureKeyId(Long keyId) {
|
||||
this.unverifiedSignatureKeyIds.add(keyId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addVerifiedSignatureFingerprint(OpenPgpV4Fingerprint fingerprint) {
|
||||
this.verifiedSignatureFingerprints.add(fingerprint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIntegrityProtected(boolean integrityProtected) {
|
||||
this.integrityProtected = integrityProtected;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PainlessResult build() {
|
||||
return new PainlessResult(recipientFingerprints, decryptionFingerprint, symmetricKeyAlgorithm, compressionAlgorithm, integrityProtected, unverifiedSignatureKeyIds, verifiedSignatureFingerprints);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright 2018 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.decryption_verification;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public class SignatureVerifyingInputStream extends FilterInputStream {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SignatureVerifyingInputStream.class.getName());
|
||||
private static final Level LEVEL = Level.INFO;
|
||||
|
||||
private final PGPObjectFactory objectFactory;
|
||||
private final Map<OpenPgpV4Fingerprint, PGPOnePassSignature> onePassSignatures;
|
||||
private final PainlessResult.Builder resultBuilder;
|
||||
|
||||
private boolean validated = false;
|
||||
|
||||
protected SignatureVerifyingInputStream(InputStream inputStream,
|
||||
PGPObjectFactory objectFactory,
|
||||
Map<OpenPgpV4Fingerprint, PGPOnePassSignature> onePassSignatures,
|
||||
PainlessResult.Builder resultBuilder) {
|
||||
super(inputStream);
|
||||
this.objectFactory = objectFactory;
|
||||
this.resultBuilder = resultBuilder;
|
||||
this.onePassSignatures = onePassSignatures;
|
||||
|
||||
LOGGER.log(LEVEL, "Begin verifying OnePassSignatures");
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte data) {
|
||||
for (PGPOnePassSignature signature : onePassSignatures.values()) {
|
||||
signature.update(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte[] b, int off, int len) {
|
||||
for (PGPOnePassSignature signature : onePassSignatures.values()) {
|
||||
signature.update(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOnePassSignatures() throws IOException {
|
||||
|
||||
if (validated) {
|
||||
LOGGER.log(LEVEL, "Validated signatures already. Skip");
|
||||
return;
|
||||
}
|
||||
|
||||
validated = true;
|
||||
|
||||
if (onePassSignatures.isEmpty()) {
|
||||
LOGGER.log(LEVEL, "No One-Pass-Signatures found -> No validation");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PGPSignatureList signatureList = null;
|
||||
Object obj = objectFactory.nextObject();
|
||||
while (obj != null && signatureList == null) {
|
||||
if (obj instanceof PGPSignatureList) {
|
||||
signatureList = (PGPSignatureList) obj;
|
||||
} else {
|
||||
obj = objectFactory.nextObject();
|
||||
}
|
||||
}
|
||||
|
||||
if (signatureList == null || signatureList.isEmpty()) {
|
||||
throw new IOException("Verification failed - No Signatures found");
|
||||
}
|
||||
|
||||
for (PGPSignature signature : signatureList) {
|
||||
OpenPgpV4Fingerprint fingerprint = null;
|
||||
for (OpenPgpV4Fingerprint f : onePassSignatures.keySet()) {
|
||||
if (f.getKeyId() == signature.getKeyID()) {
|
||||
fingerprint = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PGPOnePassSignature onePassSignature;
|
||||
if (fingerprint == null || (onePassSignature = onePassSignatures.get(fingerprint)) == null) {
|
||||
LOGGER.log(LEVEL, "Found Signature without respective OnePassSignature packet -> skip");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!onePassSignature.verify(signature)) {
|
||||
throw new SignatureException("Bad Signature of key " + signature.getKeyID());
|
||||
} else {
|
||||
LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID()));
|
||||
resultBuilder.addVerifiedSignatureFingerprint(fingerprint);
|
||||
}
|
||||
}
|
||||
} catch (PGPException | SignatureException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
final int data = super.read();
|
||||
final boolean endOfStream = data == -1;
|
||||
if (endOfStream) {
|
||||
validateOnePassSignatures();
|
||||
} else {
|
||||
updateOnePassSignatures((byte) data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
|
||||
final boolean endOfStream = read == -1;
|
||||
if (endOfStream) {
|
||||
validateOnePassSignatures();
|
||||
} else {
|
||||
updateOnePassSignatures(b, off, read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) {
|
||||
throw new UnsupportedOperationException("skip() is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark() not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() {
|
||||
throw new UnsupportedOperationException("reset() is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes used to decryption and verification of OpenPGP encrypted / signed data.
|
||||
*/
|
||||
package org.pgpainless.decryption_verification;
|
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* Copyright 2018 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.encryption_signing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
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.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.impl.And;
|
||||
import org.pgpainless.key.selection.key.impl.EncryptionKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.impl.NoRevocation;
|
||||
import org.pgpainless.key.selection.key.impl.SignatureKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||
|
||||
private OutputStream outputStream;
|
||||
private final Set<PGPPublicKey> encryptionKeys = new HashSet<>();
|
||||
private final Set<PGPSecretKey> signingKeys = new HashSet<>();
|
||||
private SecretKeyRingProtector signingKeysDecryptor;
|
||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
|
||||
private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256;
|
||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
private boolean asciiArmor = false;
|
||||
|
||||
@Override
|
||||
public ToRecipients onOutputStream(OutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
return new ToRecipientsImpl();
|
||||
}
|
||||
|
||||
class ToRecipientsImpl implements ToRecipients {
|
||||
|
||||
@Override
|
||||
public WithAlgorithms toRecipients(PGPPublicKey... keys) {
|
||||
for (PGPPublicKey k : keys) {
|
||||
if (encryptionKeySelector().accept(null, k)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key.");
|
||||
}
|
||||
}
|
||||
|
||||
if (EncryptionBuilder.this.encryptionKeys.isEmpty()) {
|
||||
throw new IllegalStateException("No valid encryption keys found!");
|
||||
}
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms toRecipients(PGPPublicKeyRing... keys) {
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
for (PGPPublicKey k : ring) {
|
||||
if (encryptionKeySelector().accept(null, k)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EncryptionBuilder.this.encryptionKeys.isEmpty()) {
|
||||
throw new IllegalStateException("No valid encryption keys found!");
|
||||
}
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms toRecipients(PGPPublicKeyRingCollection... keys) {
|
||||
for (PGPPublicKeyRingCollection collection : keys) {
|
||||
for (PGPPublicKeyRing ring : collection) {
|
||||
for (PGPPublicKey k : ring) {
|
||||
if (encryptionKeySelector().accept(null, k)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EncryptionBuilder.this.encryptionKeys.isEmpty()) {
|
||||
throw new IllegalStateException("No valid encryption keys found!");
|
||||
}
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> WithAlgorithms toRecipients(PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
||||
MultiMap<O, PGPPublicKeyRingCollection> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
throw new IllegalArgumentException("Recipient map MUST NOT be empty.");
|
||||
}
|
||||
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
||||
for (O identifier : acceptedKeyRings.keySet()) {
|
||||
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
||||
for (PGPPublicKeyRing ring : acceptedSet) {
|
||||
for (PGPPublicKey k : ring) {
|
||||
if (encryptionKeySelector().accept(null, k)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EncryptionBuilder.this.encryptionKeys.isEmpty()) {
|
||||
throw new IllegalStateException("No valid encryption keys found!");
|
||||
}
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignWith doNotEncrypt() {
|
||||
return new SignWithImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class WithAlgorithmsImpl implements WithAlgorithms {
|
||||
|
||||
@Override
|
||||
public WithAlgorithms andToSelf(PGPPublicKey... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
for (PGPPublicKey k : keys) {
|
||||
if (encryptionKeySelector().accept(null, k)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key.");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms andToSelf(PGPPublicKeyRing... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (encryptionKeySelector().accept(null, key)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms andToSelf(PGPPublicKeyRingCollection keys) {
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (encryptionKeySelector().accept(null, key)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <O> WithAlgorithms andToSelf(PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
||||
MultiMap<O, PGPPublicKeyRingCollection> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings =
|
||||
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
||||
for (O identifier : acceptedKeyRings.keySet()) {
|
||||
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
||||
for (PGPPublicKeyRing k : acceptedSet) {
|
||||
for (Iterator<PGPPublicKey> i = k.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (encryptionKeySelector().accept(null, key)) {
|
||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignWith usingAlgorithms(SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
HashAlgorithm hashAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm) {
|
||||
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
EncryptionBuilder.this.hashAlgorithm = hashAlgorithm;
|
||||
EncryptionBuilder.this.compressionAlgorithm = compressionAlgorithm;
|
||||
|
||||
return new SignWithImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignWith usingSecureAlgorithms() {
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_256;
|
||||
EncryptionBuilder.this.hashAlgorithm = HashAlgorithm.SHA512;
|
||||
EncryptionBuilder.this.compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
|
||||
return new SignWithImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class SignWithImpl implements SignWith {
|
||||
|
||||
@Override
|
||||
public <O> Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKey... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
for (PGPSecretKey s : keys) {
|
||||
if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) {
|
||||
signingKeys.add(s);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key " + s.getKeyID() + " is not a valid signing key.");
|
||||
}
|
||||
}
|
||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKeyRing... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
for (PGPSecretKeyRing key : keys) {
|
||||
for (Iterator<PGPSecretKey> i = key.getSecretKeys(); i.hasNext(); ) {
|
||||
PGPSecretKey s = i.next();
|
||||
if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) {
|
||||
EncryptionBuilder.this.signingKeys.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> Armor signWith(SecretKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
||||
SecretKeyRingProtector decryptor,
|
||||
MultiMap<O, PGPSecretKeyRingCollection> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
}
|
||||
MultiMap<O, PGPSecretKeyRing> acceptedKeyRings =
|
||||
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
||||
for (O identifier : acceptedKeyRings.keySet()) {
|
||||
Set<PGPSecretKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
||||
for (PGPSecretKeyRing k : acceptedSet) {
|
||||
for (Iterator<PGPSecretKey> i = k.getSecretKeys(); i.hasNext(); ) {
|
||||
PGPSecretKey s = i.next();
|
||||
if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) {
|
||||
EncryptionBuilder.this.signingKeys.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor doNotSign() {
|
||||
return new ArmorImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class ArmorImpl implements Armor {
|
||||
|
||||
@Override
|
||||
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
||||
EncryptionBuilder.this.asciiArmor = true;
|
||||
return build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionStream noArmor() throws IOException, PGPException {
|
||||
EncryptionBuilder.this.asciiArmor = false;
|
||||
return build();
|
||||
}
|
||||
|
||||
private EncryptionStream build() throws IOException, PGPException {
|
||||
|
||||
Set<PGPPrivateKey> privateKeys = new HashSet<>();
|
||||
for (PGPSecretKey secretKey : signingKeys) {
|
||||
privateKeys.add(secretKey.extractPrivateKey(signingKeysDecryptor.getDecryptor(secretKey.getKeyID())));
|
||||
}
|
||||
|
||||
return EncryptionStream.create(
|
||||
EncryptionBuilder.this.outputStream,
|
||||
EncryptionBuilder.this.encryptionKeys,
|
||||
privateKeys,
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm,
|
||||
EncryptionBuilder.this.hashAlgorithm,
|
||||
EncryptionBuilder.this.compressionAlgorithm,
|
||||
EncryptionBuilder.this.asciiArmor);
|
||||
}
|
||||
}
|
||||
|
||||
<O> PublicKeySelectionStrategy<O> encryptionKeySelector() {
|
||||
return new And.PubKeySelectionStrategy<>(
|
||||
new NoRevocation.PubKeySelectionStrategy<>(),
|
||||
new EncryptionKeySelectionStrategy<>());
|
||||
}
|
||||
|
||||
<O> SecretKeySelectionStrategy<O> signingKeySelector() {
|
||||
return new And.SecKeySelectionStrategy<>(
|
||||
new NoRevocation.SecKeySelectionStrategy<>(),
|
||||
new SignatureKeySelectionStrategy<>());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2018 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.encryption_signing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.exception.SecretKeyNotFoundException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public interface EncryptionBuilderInterface {
|
||||
|
||||
ToRecipients onOutputStream(OutputStream outputStream);
|
||||
|
||||
interface ToRecipients {
|
||||
|
||||
WithAlgorithms toRecipients(PGPPublicKey... keys);
|
||||
|
||||
WithAlgorithms toRecipients(PGPPublicKeyRing... keys);
|
||||
|
||||
WithAlgorithms toRecipients(PGPPublicKeyRingCollection... keys);
|
||||
|
||||
<O> WithAlgorithms toRecipients(PublicKeyRingSelectionStrategy<O> selectionStrategy,
|
||||
MultiMap<O, PGPPublicKeyRingCollection> keys);
|
||||
|
||||
SignWith doNotEncrypt();
|
||||
|
||||
}
|
||||
|
||||
interface WithAlgorithms {
|
||||
|
||||
WithAlgorithms andToSelf(PGPPublicKey... keys);
|
||||
|
||||
WithAlgorithms andToSelf(PGPPublicKeyRing... keys);
|
||||
|
||||
WithAlgorithms andToSelf(PGPPublicKeyRingCollection keys);
|
||||
|
||||
<O> WithAlgorithms andToSelf(PublicKeyRingSelectionStrategy<O> selectionStrategy,
|
||||
MultiMap<O, PGPPublicKeyRingCollection> keys);
|
||||
|
||||
SignWith usingAlgorithms(SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
HashAlgorithm hashAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm);
|
||||
|
||||
SignWith usingSecureAlgorithms();
|
||||
|
||||
}
|
||||
|
||||
interface SignWith {
|
||||
|
||||
<O> Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKey... keys);
|
||||
|
||||
<O> Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKeyRing... keyRings);
|
||||
|
||||
<O> Armor signWith(SecretKeyRingSelectionStrategy<O> selectionStrategy,
|
||||
SecretKeyRingProtector decryptor,
|
||||
MultiMap<O, PGPSecretKeyRingCollection> keys)
|
||||
throws SecretKeyNotFoundException;
|
||||
|
||||
Armor doNotSign();
|
||||
|
||||
}
|
||||
|
||||
interface Armor {
|
||||
|
||||
EncryptionStream asciiArmor() throws IOException, PGPException;
|
||||
|
||||
EncryptionStream noArmor() throws IOException, PGPException;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Copyright 2018 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.encryption_signing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.PainlessResult;
|
||||
|
||||
/**
|
||||
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
||||
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
|
||||
*/
|
||||
public final class EncryptionStream extends OutputStream {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(EncryptionStream.class.getName());
|
||||
private static final Level LEVEL = Level.FINE;
|
||||
|
||||
private static final int BUFFER_SIZE = 1 << 8;
|
||||
|
||||
private final PainlessResult result;
|
||||
|
||||
private List<PGPSignatureGenerator> signatureGenerators = new ArrayList<>();
|
||||
private boolean closed = false;
|
||||
|
||||
// ASCII Armor
|
||||
private ArmoredOutputStream armorOutputStream = null;
|
||||
|
||||
// Public Key Encryption of Symmetric Session Key
|
||||
private OutputStream publicKeyEncryptedStream = null;
|
||||
|
||||
// Data Compression
|
||||
private PGPCompressedDataGenerator compressedDataGenerator;
|
||||
private BCPGOutputStream basicCompressionStream;
|
||||
|
||||
// Literal Data
|
||||
private PGPLiteralDataGenerator literalDataGenerator;
|
||||
private OutputStream literalDataStream;
|
||||
|
||||
private EncryptionStream(OutputStream targetOutputStream,
|
||||
Set<PGPPublicKey> encryptionKeys,
|
||||
Set<PGPPrivateKey> signingKeys,
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
HashAlgorithm hashAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm,
|
||||
boolean asciiArmor)
|
||||
throws IOException, PGPException {
|
||||
|
||||
// Currently outermost Stream
|
||||
OutputStream outerMostStream;
|
||||
if (asciiArmor) {
|
||||
LOGGER.log(LEVEL, "Wrap encryption output in ASCII armor");
|
||||
armorOutputStream = new ArmoredOutputStream(targetOutputStream);
|
||||
outerMostStream = armorOutputStream;
|
||||
} else {
|
||||
LOGGER.log(LEVEL, "Encryption output will be binary");
|
||||
outerMostStream = targetOutputStream;
|
||||
}
|
||||
|
||||
// If we want to encrypt
|
||||
if (!encryptionKeys.isEmpty()) {
|
||||
LOGGER.log(LEVEL, "At least one encryption key is available -> encrypt using " + symmetricKeyAlgorithm);
|
||||
BcPGPDataEncryptorBuilder dataEncryptorBuilder =
|
||||
new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId());
|
||||
|
||||
LOGGER.log(LEVEL, "Integrity protection enabled");
|
||||
dataEncryptorBuilder.setWithIntegrityPacket(true);
|
||||
|
||||
PGPEncryptedDataGenerator encryptedDataGenerator =
|
||||
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
||||
|
||||
for (PGPPublicKey key : encryptionKeys) {
|
||||
LOGGER.log(LEVEL, "Encrypt for key " + Long.toHexString(key.getKeyID()));
|
||||
encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(key));
|
||||
}
|
||||
|
||||
publicKeyEncryptedStream = encryptedDataGenerator.open(outerMostStream, new byte[BUFFER_SIZE]);
|
||||
outerMostStream = publicKeyEncryptedStream;
|
||||
}
|
||||
|
||||
// If we want to sign, prepare for signing
|
||||
if (!signingKeys.isEmpty()) {
|
||||
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
|
||||
for (PGPPrivateKey privateKey : signingKeys) {
|
||||
LOGGER.log(LEVEL, "Sign using key " + Long.toHexString(privateKey.getKeyID()));
|
||||
BcPGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder(
|
||||
privateKey.getPublicKeyPacket().getAlgorithm(), hashAlgorithm.getAlgorithmId());
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
|
||||
signatureGenerators.add(signatureGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.log(LEVEL, "Compress using " + compressionAlgorithm);
|
||||
// Compression
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(
|
||||
compressionAlgorithm.getAlgorithmId());
|
||||
basicCompressionStream = new BCPGOutputStream(compressedDataGenerator.open(outerMostStream));
|
||||
|
||||
// If we want to sign, sign!
|
||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
|
||||
signatureGenerator.generateOnePassVersion(false).encode(basicCompressionStream);
|
||||
}
|
||||
|
||||
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
literalDataStream = literalDataGenerator.open(basicCompressionStream,
|
||||
PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, new Date(), new byte[BUFFER_SIZE]);
|
||||
|
||||
// Prepare result
|
||||
Set<Long> recipientKeyIds = new HashSet<>();
|
||||
for (PGPPublicKey recipient : encryptionKeys) {
|
||||
recipientKeyIds.add(recipient.getKeyID());
|
||||
}
|
||||
|
||||
Set<Long> signingKeyIds = new HashSet<>();
|
||||
for (PGPPrivateKey signer : signingKeys) {
|
||||
signingKeyIds.add(signer.getKeyID());
|
||||
}
|
||||
|
||||
|
||||
this.result = new PainlessResult(recipientKeyIds,
|
||||
null, symmetricKeyAlgorithm,
|
||||
compressionAlgorithm, true,
|
||||
signingKeyIds, Collections.emptySet());
|
||||
}
|
||||
|
||||
static EncryptionStream create(OutputStream outputStream,
|
||||
Set<PGPPublicKey> encryptionKeys,
|
||||
Set<PGPPrivateKey> signingKeys,
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
HashAlgorithm hashAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm,
|
||||
boolean asciiArmor)
|
||||
throws IOException, PGPException {
|
||||
|
||||
requireNonNull(outputStream, "targetOutputStream");
|
||||
requireNonNull(encryptionKeys, "encryptionKeys");
|
||||
requireNonNull(signingKeys, "signingKeys");
|
||||
requireNonNull(symmetricKeyAlgorithm, "symmetricKeyAlgorithm");
|
||||
requireNonNull(hashAlgorithm, "hashAlgorithm");
|
||||
requireNonNull(compressionAlgorithm, "compressionAlgorithm");
|
||||
|
||||
return new EncryptionStream(outputStream,
|
||||
encryptionKeys,
|
||||
signingKeys,
|
||||
symmetricKeyAlgorithm,
|
||||
hashAlgorithm,
|
||||
compressionAlgorithm,
|
||||
asciiArmor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int data) throws IOException {
|
||||
literalDataStream.write(data);
|
||||
|
||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
|
||||
byte asByte = (byte) (data & 0xff);
|
||||
signatureGenerator.update(asByte);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int off, int len) throws IOException {
|
||||
literalDataStream.write(buffer, 0, len);
|
||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
|
||||
signatureGenerator.update(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
literalDataStream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!closed) {
|
||||
|
||||
// Literal Data
|
||||
literalDataStream.flush();
|
||||
literalDataStream.close();
|
||||
literalDataGenerator.close();
|
||||
|
||||
// Signing
|
||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
|
||||
try {
|
||||
signatureGenerator.generate().encode(basicCompressionStream);
|
||||
} catch (PGPException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Compressed Data
|
||||
compressedDataGenerator.close();
|
||||
|
||||
// Public Key Encryption
|
||||
if (publicKeyEncryptedStream != null) {
|
||||
publicKeyEncryptedStream.flush();
|
||||
publicKeyEncryptedStream.close();
|
||||
}
|
||||
|
||||
// Armor
|
||||
if (armorOutputStream != null) {
|
||||
armorOutputStream.flush();
|
||||
armorOutputStream.close();
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireNonNull(Object o, String name) {
|
||||
if (o == null) {
|
||||
throw new IllegalArgumentException("Argument '" + name + "' MUST NOT be null.");
|
||||
}
|
||||
}
|
||||
|
||||
public PainlessResult getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes used to encrypt or sign data using OpenPGP.
|
||||
*/
|
||||
package org.pgpainless.encryption_signing;
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2018 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.exception;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
||||
public class PublicKeyNotFoundException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long keyId;
|
||||
|
||||
public PublicKeyNotFoundException(long keyId) {
|
||||
super("No PGPPublicKey with id " + Long.toHexString(keyId) + " (" + keyId + ") found.");
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public PublicKeyNotFoundException(PGPException e) {
|
||||
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2018 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.exception;
|
||||
|
||||
public class SecretKeyNotFoundException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long keyId;
|
||||
|
||||
public SecretKeyNotFoundException(long keyId) {
|
||||
super("No PGPSecretKey with id " + Long.toHexString(keyId) + " (" + keyId + ") found.");
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Exceptions.
|
||||
*/
|
||||
package org.pgpainless.exception;
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright 2018 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.key;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* This class represents an hex encoded, uppercase OpenPGP v4 fingerprint.
|
||||
*/
|
||||
public class OpenPgpV4Fingerprint implements CharSequence, Comparable<OpenPgpV4Fingerprint> {
|
||||
|
||||
private final String fingerprint;
|
||||
|
||||
/**
|
||||
* Create an {@link OpenPgpV4Fingerprint}.
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#annoucning-pubkey">
|
||||
* XEP-0373 §4.1: The OpenPGP Public-Key Data Node about how to obtain the fingerprint</a>
|
||||
* @param fingerprint hexadecimal representation of the fingerprint.
|
||||
*/
|
||||
public OpenPgpV4Fingerprint(String fingerprint) {
|
||||
if (fingerprint == null) {
|
||||
throw new NullPointerException("Fingerprint MUST NOT be null.");
|
||||
}
|
||||
String fp = fingerprint.trim().toUpperCase();
|
||||
if (!isValid(fp)) {
|
||||
throw new IllegalArgumentException("Fingerprint " + fingerprint +
|
||||
" does not appear to be a valid OpenPGP v4 fingerprint.");
|
||||
}
|
||||
this.fingerprint = fp;
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint(byte[] bytes) {
|
||||
this(new String(bytes, Charset.forName("UTF-8")));
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint(PGPPublicKey key) {
|
||||
this(Hex.encode(key.getFingerprint()));
|
||||
if (key.getVersion() != 4) {
|
||||
throw new IllegalArgumentException("Key is not a v4 OpenPgp key.");
|
||||
}
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint(PGPSecretKey key) {
|
||||
this(key.getPublicKey());
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint(PGPPublicKeyRing ring) {
|
||||
this(ring.getPublicKey());
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint(PGPSecretKeyRing ring) {
|
||||
this(ring.getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
||||
* @param fp fingerprint to check.
|
||||
* @return true if fingerprint is valid.
|
||||
*/
|
||||
private boolean isValid(String fp) {
|
||||
return fp.matches("[0-9A-F]{40}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key id of the OpenPGP public key this {@link OpenPgpV4Fingerprint} belongs to.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
||||
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
||||
* @return key id
|
||||
*/
|
||||
public long getKeyId() {
|
||||
|
||||
byte[] bytes = new BigInteger(toString(), 16).toByteArray();
|
||||
if (bytes.length != toString().length() / 2) {
|
||||
bytes = Arrays.copyOfRange(bytes, 1, bytes.length);
|
||||
}
|
||||
|
||||
byte[] lower8Bytes = Arrays.copyOfRange(bytes, 12, 20);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
|
||||
byteBuffer.put(lower8Bytes);
|
||||
byteBuffer.flip();
|
||||
return byteBuffer.getLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(other instanceof CharSequence)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.toString().equals(other.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fingerprint.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return fingerprint.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return fingerprint.charAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int i, int i1) {
|
||||
return fingerprint.subSequence(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OpenPgpV4Fingerprint openPgpV4Fingerprint) {
|
||||
return fingerprint.compareTo(openPgpV4Fingerprint.fingerprint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.collection;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.PGPainless;
|
||||
|
||||
public class KeyRingCollection {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(KeyRingCollection.class.getName());
|
||||
|
||||
private PGPPublicKeyRingCollection publicKeys;
|
||||
private PGPSecretKeyRingCollection secretKeys;
|
||||
|
||||
public KeyRingCollection(PGPPublicKeyRingCollection publicKeyRings, PGPSecretKeyRingCollection secretKeyRings) {
|
||||
this.publicKeys = publicKeyRings;
|
||||
this.secretKeys = secretKeyRings;
|
||||
}
|
||||
|
||||
public KeyRingCollection(File pubRingFile, File secRingFile) throws IOException, PGPException {
|
||||
if (pubRingFile != null) {
|
||||
InputStream pubRingIn = new FileInputStream(pubRingFile);
|
||||
this.publicKeys = PGPainless.readKeyRing().publicKeyRingCollection(pubRingIn);
|
||||
pubRingIn.close();
|
||||
}
|
||||
|
||||
if (secRingFile != null) {
|
||||
InputStream secRingIn = new FileInputStream(secRingFile);
|
||||
this.secretKeys = PGPainless.readKeyRing().secretKeyRingCollection(secRingIn);
|
||||
secRingIn.close();
|
||||
}
|
||||
}
|
||||
|
||||
public KeyRingCollection(PGPPublicKeyRingCollection publicKeyRings) {
|
||||
this(publicKeyRings, null);
|
||||
}
|
||||
|
||||
public KeyRingCollection(PGPSecretKeyRingCollection secretKeyRings) {
|
||||
this(null, secretKeyRings);
|
||||
}
|
||||
|
||||
public void importPublicKeys(PGPPublicKeyRingCollection publicKeyRings) {
|
||||
if (this.publicKeys == null) {
|
||||
this.publicKeys = publicKeyRings;
|
||||
return;
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRing keyRing : publicKeyRings) {
|
||||
try {
|
||||
this.publicKeys = PGPPublicKeyRingCollection.addPublicKeyRing(this.publicKeys, keyRing);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// TODO: merge key rings.
|
||||
LOGGER.log(Level.FINE, "Keyring " + Long.toHexString(keyRing.getPublicKey().getKeyID()) +
|
||||
" is already included in the collection. Skip!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void importSecretKeys(PGPSecretKeyRingCollection secretKeyRings) {
|
||||
if (this.secretKeys == null) {
|
||||
this.secretKeys = secretKeyRings;
|
||||
return;
|
||||
}
|
||||
|
||||
for (PGPSecretKeyRing keyRing : secretKeyRings) {
|
||||
try {
|
||||
this.secretKeys = PGPSecretKeyRingCollection.addSecretKeyRing(this.secretKeys, keyRing);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// TODO: merge key rings.
|
||||
LOGGER.log(Level.FINE, "Keyring " + Long.toHexString(keyRing.getPublicKey().getKeyID()) +
|
||||
" is already included in the collection. Skip!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.collection;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public class PGPKeyRing {
|
||||
|
||||
private PGPPublicKeyRing publicKeys;
|
||||
private PGPSecretKeyRing secretKeys;
|
||||
|
||||
public PGPKeyRing(PGPPublicKeyRing publicKeys, PGPSecretKeyRing secretKeys) {
|
||||
if (secretKeys == null && publicKeys == null) {
|
||||
throw new IllegalArgumentException("publicKeys and secretKeys MUST NOT be both null.");
|
||||
}
|
||||
|
||||
if (publicKeys != null && secretKeys != null) {
|
||||
if (publicKeys.getPublicKey().getKeyID() != secretKeys.getPublicKey().getKeyID()) {
|
||||
throw new IllegalArgumentException("publicKeys and secretKeys must have the same master key.");
|
||||
}
|
||||
}
|
||||
|
||||
this.publicKeys = publicKeys;
|
||||
this.secretKeys = secretKeys;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return getMasterKey().getKeyID();
|
||||
}
|
||||
|
||||
public PGPPublicKey getMasterKey() {
|
||||
PGPPublicKey publicKey = hasSecretKeys() ? secretKeys.getPublicKey() : publicKeys.getPublicKey();
|
||||
if (!publicKey.isMasterKey()) {
|
||||
throw new IllegalStateException("Expected master key is not a master key");
|
||||
}
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint getV4Fingerprint() {
|
||||
return new OpenPgpV4Fingerprint(getMasterKey());
|
||||
}
|
||||
|
||||
public boolean hasSecretKeys() {
|
||||
return secretKeys != null;
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing getPublicKeys() {
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing getSecretKeys() {
|
||||
return secretKeys;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* OpenPGP key collections.
|
||||
*/
|
||||
package org.pgpainless.key.collection;
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation;
|
||||
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.generation.type.ECDH;
|
||||
import org.pgpainless.key.generation.type.ECDSA;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.RSA_GENERAL;
|
||||
import org.pgpainless.key.generation.type.curve.EllipticCurve;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.pgpainless.util.KeyRingSubKeyFix;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public class KeyRingBuilder implements KeyRingBuilderInterface {
|
||||
|
||||
private final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
private List<KeySpec> keySpecs = new ArrayList<>();
|
||||
private String userId;
|
||||
private Passphrase passphrase;
|
||||
|
||||
/**
|
||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
|
||||
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
|
||||
*
|
||||
* @param userId user id.
|
||||
* @param length length in bits.
|
||||
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
||||
* @throws PGPException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
*/
|
||||
public PGPKeyRing simpleRsaKeyRing(String userId, RsaLength length)
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
return withMasterKey(
|
||||
KeySpec.getBuilder(RSA_GENERAL.withLength(length))
|
||||
.withDefaultKeyFlags()
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryUserId(userId)
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key ring consisting of an ECDSA master key and an ECDH sub-key.
|
||||
* The ECDSA master key is used for signing messages and certifying the sub key.
|
||||
* The ECDH sub-key is used for encryption of messages.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
||||
* @throws PGPException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
*/
|
||||
public PGPKeyRing simpleEcKeyRing(String userId)
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
return withSubKey(
|
||||
KeySpec.getBuilder(ECDH.fromCurve(EllipticCurve._P256))
|
||||
.withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
|
||||
.withDefaultAlgorithms())
|
||||
.withMasterKey(
|
||||
KeySpec.getBuilder(ECDSA.fromCurve(EllipticCurve._P256))
|
||||
.withKeyFlags(KeyFlag.AUTHENTICATION, KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryUserId(userId)
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyRingBuilderInterface withSubKey(KeySpec type) {
|
||||
KeyRingBuilder.this.keySpecs.add(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithPrimaryUserId withMasterKey(KeySpec spec) {
|
||||
if ((spec.getSubpackets().getKeyFlags() & KeyFlags.CERTIFY_OTHER) == 0) {
|
||||
throw new IllegalArgumentException("Certification Key MUST have KeyFlag CERTIFY_OTHER");
|
||||
}
|
||||
KeyRingBuilder.this.keySpecs.add(0, spec);
|
||||
return new WithPrimaryUserIdImpl();
|
||||
}
|
||||
|
||||
class WithPrimaryUserIdImpl implements WithPrimaryUserId {
|
||||
|
||||
@Override
|
||||
public WithPassphrase withPrimaryUserId(String userId) {
|
||||
KeyRingBuilder.this.userId = userId;
|
||||
return new WithPassphraseImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithPassphrase withPrimaryUserId(byte[] userId) {
|
||||
return withPrimaryUserId(new String(userId, UTF8));
|
||||
}
|
||||
}
|
||||
|
||||
class WithPassphraseImpl implements WithPassphrase {
|
||||
|
||||
@Override
|
||||
public Build withPassphrase(Passphrase passphrase) {
|
||||
KeyRingBuilder.this.passphrase = passphrase;
|
||||
return new BuildImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Build withoutPassphrase() {
|
||||
KeyRingBuilder.this.passphrase = null;
|
||||
return new BuildImpl();
|
||||
}
|
||||
|
||||
class BuildImpl implements Build {
|
||||
|
||||
@Override
|
||||
public PGPKeyRing build() throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||
InvalidAlgorithmParameterException {
|
||||
|
||||
// Hash Calculator
|
||||
PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build()
|
||||
.get(HashAlgorithm.SHA1.getAlgorithmId());
|
||||
|
||||
// Encryptor for encrypting secret keys
|
||||
PBESecretKeyEncryptor encryptor = passphrase == null ?
|
||||
null : // unencrypted key pair, otherwise AES-256 encrypted
|
||||
new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, calculator)
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build(passphrase != null ? passphrase.getChars() : null);
|
||||
|
||||
if (passphrase != null) {
|
||||
passphrase.clear();
|
||||
}
|
||||
|
||||
// First key is the Master Key
|
||||
KeySpec certKeySpec = keySpecs.get(0);
|
||||
// Remove master key, so that we later only add sub keys.
|
||||
keySpecs.remove(0);
|
||||
|
||||
// Generate Master Key
|
||||
PGPKeyPair certKey = generateKeyPair(certKeySpec);
|
||||
|
||||
// Signer for creating self-signature
|
||||
PGPContentSignerBuilder signer = new JcaPGPContentSignerBuilder(
|
||||
certKey.getPublicKey().getAlgorithm(), HashAlgorithm.SHA512.getAlgorithmId())
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
|
||||
PGPSignatureSubpacketVector hashedSubPackets = certKeySpec.getSubpackets();
|
||||
|
||||
// Generator which the user can get the key pair from
|
||||
PGPKeyRingGenerator ringGenerator = new PGPKeyRingGenerator(
|
||||
PGPSignature.POSITIVE_CERTIFICATION, certKey,
|
||||
userId, calculator,
|
||||
hashedSubPackets, null, signer, encryptor);
|
||||
|
||||
for (KeySpec subKeySpec : keySpecs) {
|
||||
PGPKeyPair subKey = generateKeyPair(subKeySpec);
|
||||
if (subKeySpec.isInheritedSubPackets()) {
|
||||
ringGenerator.addSubKey(subKey);
|
||||
} else {
|
||||
ringGenerator.addSubKey(subKey, subKeySpec.getSubpackets(), null);
|
||||
}
|
||||
}
|
||||
|
||||
PGPPublicKeyRing publicKeys = ringGenerator.generatePublicKeyRing();
|
||||
PGPSecretKeyRing secretKeys = ringGenerator.generateSecretKeyRing();
|
||||
|
||||
// TODO: Remove once BC 1.61 is released
|
||||
secretKeys = KeyRingSubKeyFix.repairSubkeyPackets(secretKeys, null, null);
|
||||
|
||||
return new PGPKeyRing(publicKeys, secretKeys);
|
||||
}
|
||||
|
||||
private PGPKeyPair generateKeyPair(KeySpec spec)
|
||||
throws NoSuchProviderException, NoSuchAlgorithmException, PGPException,
|
||||
InvalidAlgorithmParameterException {
|
||||
KeyType type = spec.getKeyType();
|
||||
KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(
|
||||
type.getName(), BouncyCastleProvider.PROVIDER_NAME);
|
||||
certKeyGenerator.initialize(type.getAlgorithmSpec());
|
||||
|
||||
// Create raw Key Pair
|
||||
KeyPair keyPair = certKeyGenerator.generateKeyPair();
|
||||
|
||||
// Form PGP key pair
|
||||
PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(type.getAlgorithm().getAlgorithmId(),
|
||||
keyPair, new Date());
|
||||
|
||||
return pgpKeyPair;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public interface KeyRingBuilderInterface {
|
||||
|
||||
KeyRingBuilderInterface withSubKey(KeySpec keySpec);
|
||||
|
||||
WithPrimaryUserId withMasterKey(KeySpec keySpec);
|
||||
|
||||
interface WithPrimaryUserId {
|
||||
|
||||
WithPassphrase withPrimaryUserId(String userId);
|
||||
|
||||
WithPassphrase withPrimaryUserId(byte[] userId);
|
||||
|
||||
}
|
||||
|
||||
interface WithPassphrase {
|
||||
|
||||
Build withPassphrase(Passphrase passphrase);
|
||||
|
||||
Build withoutPassphrase();
|
||||
}
|
||||
|
||||
interface Build {
|
||||
|
||||
PGPKeyRing build() throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||
InvalidAlgorithmParameterException;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
|
||||
public class KeySpec {
|
||||
|
||||
private final KeyType keyType;
|
||||
private final PGPSignatureSubpacketGenerator subpacketGenerator;
|
||||
private final boolean inheritedSubPackets;
|
||||
|
||||
KeySpec(KeyType type,
|
||||
PGPSignatureSubpacketGenerator subpacketGenerator,
|
||||
boolean inheritedSubPackets) {
|
||||
this.keyType = type;
|
||||
this.subpacketGenerator = subpacketGenerator;
|
||||
this.inheritedSubPackets = inheritedSubPackets;
|
||||
}
|
||||
|
||||
KeyType getKeyType() {
|
||||
return keyType;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector getSubpackets() {
|
||||
return subpacketGenerator.generate();
|
||||
}
|
||||
|
||||
boolean isInheritedSubPackets() {
|
||||
return inheritedSubPackets;
|
||||
}
|
||||
|
||||
public static KeySpecBuilder getBuilder(KeyType type) {
|
||||
return new KeySpecBuilder(type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.Features;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.pgpainless.algorithm.AlgorithmSuite;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.Feature;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
|
||||
public class KeySpecBuilder implements KeySpecBuilderInterface {
|
||||
|
||||
private KeyType type;
|
||||
private PGPSignatureSubpacketGenerator hashedSubPackets = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
KeySpecBuilder(KeyType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithDetailedConfiguration withKeyFlags(KeyFlag... flags) {
|
||||
int val = 0;
|
||||
for (KeyFlag f : flags) {
|
||||
val |= f.getFlag();
|
||||
}
|
||||
this.hashedSubPackets.setKeyFlags(false, val);
|
||||
return new WithDetailedConfigurationImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithDetailedConfiguration withDefaultKeyFlags() {
|
||||
return withKeyFlags(
|
||||
KeyFlag.CERTIFY_OTHER,
|
||||
KeyFlag.SIGN_DATA,
|
||||
KeyFlag.ENCRYPT_COMMS,
|
||||
KeyFlag.ENCRYPT_STORAGE,
|
||||
KeyFlag.AUTHENTICATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySpec withInheritedSubPackets() {
|
||||
return new KeySpec(type, null, true);
|
||||
}
|
||||
|
||||
class WithDetailedConfigurationImpl implements WithDetailedConfiguration {
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public WithPreferredSymmetricAlgorithms withDetailedConfiguration() {
|
||||
return new WithPreferredSymmetricAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySpec withDefaultAlgorithms() {
|
||||
AlgorithmSuite defaultSuite = AlgorithmSuite.getDefaultAlgorithmSuite();
|
||||
hashedSubPackets.setPreferredCompressionAlgorithms(false, defaultSuite.getCompressionAlgorithmIds());
|
||||
hashedSubPackets.setPreferredSymmetricAlgorithms(false, defaultSuite.getSymmetricKeyAlgorithmIds());
|
||||
hashedSubPackets.setPreferredHashAlgorithms(false, defaultSuite.getHashAlgorithmIds());
|
||||
hashedSubPackets.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
|
||||
return new KeySpec(
|
||||
KeySpecBuilder.this.type,
|
||||
KeySpecBuilder.this.hashedSubPackets,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
class WithPreferredSymmetricAlgorithmsImpl implements WithPreferredSymmetricAlgorithms {
|
||||
|
||||
@Override
|
||||
public WithPreferredHashAlgorithms withPreferredSymmetricAlgorithms(SymmetricKeyAlgorithm... algorithms) {
|
||||
int[] ids = new int[algorithms.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
ids[i] = algorithms[i].getAlgorithmId();
|
||||
}
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredSymmetricAlgorithms(false, ids);
|
||||
return new WithPreferredHashAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithPreferredHashAlgorithms withDefaultSymmetricAlgorithms() {
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredSymmetricAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getSymmetricKeyAlgorithmIds());
|
||||
return new WithPreferredHashAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithFeatures withDefaultAlgorithms() {
|
||||
hashedSubPackets.setPreferredSymmetricAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getSymmetricKeyAlgorithmIds());
|
||||
hashedSubPackets.setPreferredCompressionAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getCompressionAlgorithmIds());
|
||||
hashedSubPackets.setPreferredHashAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getHashAlgorithmIds());
|
||||
return new WithFeaturesImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class WithPreferredHashAlgorithmsImpl implements WithPreferredHashAlgorithms {
|
||||
|
||||
@Override
|
||||
public WithPreferredCompressionAlgorithms withPreferredHashAlgorithms(HashAlgorithm... algorithms) {
|
||||
int[] ids = new int[algorithms.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
ids[i] = algorithms[i].getAlgorithmId();
|
||||
}
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredHashAlgorithms(false, ids);
|
||||
return new WithPreferredCompressionAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithPreferredCompressionAlgorithms withDefaultHashAlgorithms() {
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredHashAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getHashAlgorithmIds());
|
||||
return new WithPreferredCompressionAlgorithmsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class WithPreferredCompressionAlgorithmsImpl implements WithPreferredCompressionAlgorithms {
|
||||
|
||||
@Override
|
||||
public WithFeatures withPreferredCompressionAlgorithms(CompressionAlgorithm... algorithms) {
|
||||
int[] ids = new int[algorithms.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
ids[i] = algorithms[i].getAlgorithmId();
|
||||
}
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredCompressionAlgorithms(false, ids);
|
||||
return new WithFeaturesImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithFeatures withDefaultCompressionAlgorithms() {
|
||||
KeySpecBuilder.this.hashedSubPackets.setPreferredCompressionAlgorithms(false,
|
||||
AlgorithmSuite.getDefaultAlgorithmSuite().getCompressionAlgorithmIds());
|
||||
return new WithFeaturesImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class WithFeaturesImpl implements WithFeatures {
|
||||
|
||||
@Override
|
||||
public WithFeatures withFeature(Feature feature) {
|
||||
KeySpecBuilder.this.hashedSubPackets.setFeature(false, feature.getFeatureId());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySpec done() {
|
||||
return new KeySpec(
|
||||
KeySpecBuilder.this.type,
|
||||
hashedSubPackets,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation;
|
||||
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.Feature;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
|
||||
public interface KeySpecBuilderInterface {
|
||||
|
||||
WithDetailedConfiguration withKeyFlags(KeyFlag... flags);
|
||||
|
||||
WithDetailedConfiguration withDefaultKeyFlags();
|
||||
|
||||
KeySpec withInheritedSubPackets();
|
||||
|
||||
interface WithDetailedConfiguration {
|
||||
|
||||
WithPreferredSymmetricAlgorithms withDetailedConfiguration();
|
||||
|
||||
KeySpec withDefaultAlgorithms();
|
||||
}
|
||||
|
||||
interface WithPreferredSymmetricAlgorithms {
|
||||
|
||||
WithPreferredHashAlgorithms withPreferredSymmetricAlgorithms(SymmetricKeyAlgorithm... algorithms);
|
||||
|
||||
WithPreferredHashAlgorithms withDefaultSymmetricAlgorithms();
|
||||
|
||||
WithFeatures withDefaultAlgorithms();
|
||||
|
||||
}
|
||||
|
||||
interface WithPreferredHashAlgorithms {
|
||||
|
||||
WithPreferredCompressionAlgorithms withPreferredHashAlgorithms(HashAlgorithm... algorithms);
|
||||
|
||||
WithPreferredCompressionAlgorithms withDefaultHashAlgorithms();
|
||||
|
||||
}
|
||||
|
||||
interface WithPreferredCompressionAlgorithms {
|
||||
|
||||
WithFeatures withPreferredCompressionAlgorithms(CompressionAlgorithm... algorithms);
|
||||
|
||||
WithFeatures withDefaultCompressionAlgorithms();
|
||||
|
||||
}
|
||||
|
||||
interface WithFeatures {
|
||||
|
||||
WithFeatures withFeature(Feature feature);
|
||||
|
||||
KeySpec done();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to OpenPGP key generation.
|
||||
*/
|
||||
package org.pgpainless.key.generation;
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.curve.EllipticCurve;
|
||||
|
||||
public class ECDH implements KeyType {
|
||||
|
||||
private final EllipticCurve curve;
|
||||
|
||||
ECDH(EllipticCurve curve) {
|
||||
this.curve = curve;
|
||||
}
|
||||
|
||||
public static ECDH fromCurve(EllipticCurve curve) {
|
||||
return new ECDH(curve);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ECDH";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.ECDH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlgorithmParameterSpec getAlgorithmSpec() {
|
||||
return new ECNamedCurveGenParameterSpec(curve.getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.curve.EllipticCurve;
|
||||
|
||||
public class ECDSA extends ECDH {
|
||||
|
||||
ECDSA(EllipticCurve curve) {
|
||||
super(curve);
|
||||
}
|
||||
|
||||
public static ECDSA fromCurve(EllipticCurve curve) {
|
||||
return new ECDSA(curve);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ECDSA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.ECDSA;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.length.ElGamalLength;
|
||||
|
||||
public class ElGamal_ENCRYPT extends ElGamal_GENERAL {
|
||||
|
||||
ElGamal_ENCRYPT(ElGamalLength length) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.ELGAMAL_ENCRYPT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.length.ElGamalLength;
|
||||
|
||||
public class ElGamal_GENERAL implements KeyType {
|
||||
|
||||
private final ElGamalLength length;
|
||||
|
||||
ElGamal_GENERAL(ElGamalLength length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static ElGamal_GENERAL withLength(ElGamalLength length) {
|
||||
return new ElGamal_GENERAL(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ElGamal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.ELGAMAL_GENERAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlgorithmParameterSpec getAlgorithmSpec() {
|
||||
return new ElGamalParameterSpec(length.getP(), length.getG());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
|
||||
public interface KeyType {
|
||||
|
||||
String getName();
|
||||
|
||||
PublicKeyAlgorithm getAlgorithm();
|
||||
|
||||
AlgorithmParameterSpec getAlgorithmSpec();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
|
||||
public class RSA_ENCRYPT extends RSA_GENERAL {
|
||||
|
||||
RSA_ENCRYPT(RsaLength length) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.RSA_ENCRYPT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
|
||||
public class RSA_GENERAL implements KeyType {
|
||||
|
||||
private final RsaLength length;
|
||||
|
||||
RSA_GENERAL(RsaLength length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static RSA_GENERAL withLength(RsaLength length) {
|
||||
return new RSA_GENERAL(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RSA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.RSA_GENERAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlgorithmParameterSpec getAlgorithmSpec() {
|
||||
return new RSAKeyGenParameterSpec(length.getLength(), RSAKeyGenParameterSpec.F4);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type;
|
||||
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
|
||||
public class RSA_SIGN extends RSA_GENERAL {
|
||||
|
||||
RSA_SIGN(RsaLength length) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyAlgorithm getAlgorithm() {
|
||||
return PublicKeyAlgorithm.RSA_SIGN;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type.curve;
|
||||
|
||||
public enum EllipticCurve {
|
||||
_P256("P-256"),
|
||||
;
|
||||
|
||||
private final String name;
|
||||
|
||||
EllipticCurve(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to elliptic curve cryptography.
|
||||
*/
|
||||
package org.pgpainless.key.generation.type.curve;
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type.length;
|
||||
|
||||
public enum DiffieHellmanLength implements KeyLength {
|
||||
|
||||
_1024(1024),
|
||||
_2048(2048),
|
||||
_3072(3072),
|
||||
;
|
||||
|
||||
private final int length;
|
||||
|
||||
DiffieHellmanLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type.length;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* The following primes are taken from RFC-3526.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt">
|
||||
* RFC-3526: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
|
||||
*/
|
||||
public enum ElGamalLength implements KeyLength {
|
||||
|
||||
/**
|
||||
* prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_1536(1536, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "2"),
|
||||
|
||||
/**
|
||||
* prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_2048(2048, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "2"),
|
||||
|
||||
/**
|
||||
* prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_3072(3072, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "2"),
|
||||
|
||||
/**
|
||||
* prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_4096(4096, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", "2"),
|
||||
|
||||
/**
|
||||
* prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_6144(6144, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", "2"),
|
||||
|
||||
/**
|
||||
* prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2")
|
||||
;
|
||||
|
||||
private final int length;
|
||||
private final BigInteger p;
|
||||
private final BigInteger g;
|
||||
|
||||
ElGamalLength(int length, String p, String g) {
|
||||
this.length = length;
|
||||
this.p = new BigInteger(p, 16);
|
||||
this.g = new BigInteger(g, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type.length;
|
||||
|
||||
public interface KeyLength {
|
||||
|
||||
int getLength();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.generation.type.length;
|
||||
|
||||
public enum RsaLength implements KeyLength {
|
||||
@Deprecated
|
||||
_1024(1024),
|
||||
@Deprecated
|
||||
_2048(2048),
|
||||
_3072(3072),
|
||||
_4096(4096),
|
||||
_8192(8192),
|
||||
;
|
||||
|
||||
private final int length;
|
||||
|
||||
RsaLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes describing the lengths of different public key crypto systems.
|
||||
*/
|
||||
package org.pgpainless.key.generation.type.length;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes describing different OpenPGP key types.
|
||||
*/
|
||||
package org.pgpainless.key.generation.type;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to OpenPGP keys.
|
||||
*/
|
||||
package org.pgpainless.key;
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.parsing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
|
||||
public class KeyRingReader {
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(InputStream inputStream) throws IOException {
|
||||
return readPublicKeyRing(inputStream);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(byte[] bytes) throws IOException {
|
||||
return publicKeyRing(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(String asciiArmored) throws IOException {
|
||||
return publicKeyRing(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
return readPublicKeyRingCollection(inputStream);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(byte[] bytes) throws IOException, PGPException {
|
||||
return publicKeyRingCollection(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(String asciiArmored) throws IOException, PGPException {
|
||||
return publicKeyRingCollection(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(InputStream inputStream) throws IOException, PGPException {
|
||||
return readSecretKeyRing(inputStream);
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(byte[] bytes) throws IOException, PGPException {
|
||||
return secretKeyRing(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(String asciiArmored) throws IOException, PGPException {
|
||||
return secretKeyRing(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
return readSecretKeyRingCollection(inputStream);
|
||||
}
|
||||
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(byte[] bytes) throws IOException, PGPException {
|
||||
return secretKeyRingCollection(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(String asciiArmored) throws IOException, PGPException {
|
||||
return secretKeyRingCollection(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPKeyRing keyRing(InputStream publicIn, InputStream secretIn) throws IOException, PGPException {
|
||||
return readKeyRing(publicIn, secretIn);
|
||||
}
|
||||
|
||||
public PGPKeyRing keyRing(byte[] publicBytes, byte[] secretBytes) throws IOException, PGPException {
|
||||
return keyRing(
|
||||
publicBytes != null ? new ByteArrayInputStream(publicBytes) : null,
|
||||
secretBytes != null ? new ByteArrayInputStream(secretBytes) : null
|
||||
);
|
||||
}
|
||||
|
||||
public PGPKeyRing keyRing(String asciiPublic, String asciiSecret) throws IOException, PGPException {
|
||||
return keyRing(
|
||||
asciiPublic != null ? asciiPublic.getBytes(UTF8) : null,
|
||||
asciiSecret != null ? asciiSecret.getBytes(UTF8) : null
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
STATIC METHODS
|
||||
*/
|
||||
|
||||
public static PGPPublicKeyRing readPublicKeyRing(InputStream inputStream) throws IOException {
|
||||
return new PGPPublicKeyRing(
|
||||
PGPUtil.getDecoderStream(inputStream),
|
||||
new BcKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRingCollection readPublicKeyRingCollection(InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
return new PGPPublicKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(inputStream),
|
||||
new BcKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRing readSecretKeyRing(InputStream inputStream) throws IOException, PGPException {
|
||||
return new PGPSecretKeyRing(
|
||||
PGPUtil.getDecoderStream(inputStream),
|
||||
new BcKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRingCollection readSecretKeyRingCollection(InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
return new PGPSecretKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(inputStream),
|
||||
new BcKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
public static PGPKeyRing readKeyRing(InputStream publicIn, InputStream secretIn) throws IOException, PGPException {
|
||||
PGPPublicKeyRing publicKeys = null;
|
||||
if (publicIn != null) {
|
||||
publicKeys = readPublicKeyRing(publicIn);
|
||||
}
|
||||
PGPSecretKeyRing secretKeys = null;
|
||||
if (secretIn != null) {
|
||||
secretKeys = readSecretKeyRing(secretIn);
|
||||
}
|
||||
return new PGPKeyRing(publicKeys, secretKeys);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to OpenPGP key reading.
|
||||
*/
|
||||
package org.pgpainless.key.parsing;
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.protection;
|
||||
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
|
||||
public class KeyRingProtectionSettings {
|
||||
|
||||
private final SymmetricKeyAlgorithm encryptionAlgorithm;
|
||||
private final HashAlgorithm hashAlgorithm;
|
||||
private final int s2kCount;
|
||||
|
||||
public KeyRingProtectionSettings(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount) {
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.s2kCount = s2kCount;
|
||||
}
|
||||
|
||||
public SymmetricKeyAlgorithm getEncryptionAlgorithm() {
|
||||
return encryptionAlgorithm;
|
||||
}
|
||||
|
||||
public HashAlgorithm getHashAlgorithm() {
|
||||
return hashAlgorithm;
|
||||
}
|
||||
|
||||
public int getS2kCount() {
|
||||
return s2kCount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.protection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link SecretKeyRingProtector} which holds a map of key ids and their passwords.
|
||||
*/
|
||||
public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector {
|
||||
|
||||
private static final PGPDigestCalculatorProvider calculatorProvider = new BcPGPDigestCalculatorProvider();
|
||||
|
||||
private final Map<Long, Passphrase> passphrases = new HashMap<>();
|
||||
private final KeyRingProtectionSettings protectionSettings;
|
||||
|
||||
public PassphraseMapKeyRingProtector(Map<Long, Passphrase> passphrases, KeyRingProtectionSettings protectionSettings) {
|
||||
this.passphrases.putAll(passphrases);
|
||||
this.protectionSettings = protectionSettings;
|
||||
}
|
||||
|
||||
public void addPassphrase(Long keyId, Passphrase passphrase) {
|
||||
this.passphrases.put(keyId, passphrase);
|
||||
}
|
||||
|
||||
public void forgetPassphrase(Long keyId) {
|
||||
Passphrase passphrase = passphrases.get(keyId);
|
||||
passphrase.clear();
|
||||
passphrases.remove(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) {
|
||||
Passphrase passphrase = passphrases.get(keyId);
|
||||
return new BcPBESecretKeyDecryptorBuilder(calculatorProvider)
|
||||
.build(passphrase != null ? passphrase.getChars() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
|
||||
Passphrase passphrase = passphrases.get(keyId);
|
||||
return new BcPBESecretKeyEncryptorBuilder(
|
||||
protectionSettings.getEncryptionAlgorithm().getAlgorithmId(),
|
||||
calculatorProvider.get(protectionSettings.getHashAlgorithm().getAlgorithmId()),
|
||||
protectionSettings.getS2kCount())
|
||||
.build(passphrase != null ? passphrase.getChars() : null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.protection;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
|
||||
public interface SecretKeyRingProtector {
|
||||
|
||||
/**
|
||||
* Return a decryptor for the key of id {@code keyId}.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @return decryptor for the key
|
||||
*/
|
||||
PBESecretKeyDecryptor getDecryptor(Long keyId);
|
||||
|
||||
/**
|
||||
* Return an encryptor for the key of id {@code keyId}.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @return encryptor for the key
|
||||
* @throws PGPException if the encryptor cannot be created for some reason
|
||||
*/
|
||||
PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException;
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.protection;
|
||||
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link SecretKeyRingProtector} which assumes that all handled keys are not password protected.
|
||||
*/
|
||||
public class UnprotectedKeysProtector implements SecretKeyRingProtector {
|
||||
@Override
|
||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to OpenPGP secret key password protection.
|
||||
*/
|
||||
package org.pgpainless.key.protection;
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
|
||||
/**
|
||||
* Interface that describes a selection strategy for OpenPGP keys.
|
||||
* @param <K> Type of the Key
|
||||
* @param <R> Type of the PGPKeyRing
|
||||
* @param <O> Type that describes the owner of this key
|
||||
*/
|
||||
public interface KeySelectionStrategy<K, R, O> {
|
||||
|
||||
boolean accept(O identifier, K key);
|
||||
|
||||
Set<K> selectKeysFromKeyRing(O identifier, R ring);
|
||||
|
||||
MultiMap<O, K> selectKeysFromKeyRings(MultiMap<O, R> rings);
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
/**
|
||||
* Key Selection Strategy which accepts {@link PGPPublicKey}s that are accepted by the abstract method
|
||||
* {@link #accept(Object, Object)}.
|
||||
*
|
||||
* @param <O> Type that describes the owner of the key.
|
||||
*/
|
||||
public abstract class PublicKeySelectionStrategy<O> implements KeySelectionStrategy<PGPPublicKey, PGPPublicKeyRing, O> {
|
||||
|
||||
@Override
|
||||
public Set<PGPPublicKey> selectKeysFromKeyRing(O identifier, PGPPublicKeyRing ring) {
|
||||
Set<PGPPublicKey> keys = new HashSet<>();
|
||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (accept(identifier, key)) keys.add(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiMap<O, PGPPublicKey> selectKeysFromKeyRings(MultiMap<O, PGPPublicKeyRing> keyRings) {
|
||||
MultiMap<O, PGPPublicKey> keys = new MultiMap<>();
|
||||
for (O identifier : keyRings.keySet()) {
|
||||
for (PGPPublicKeyRing ring : keyRings.get(identifier)) {
|
||||
keys.put(identifier, selectKeysFromKeyRing(identifier, ring));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
/**
|
||||
* Key Selection Strategy which accepts {@link PGPSecretKey}s that are accepted by the abstract method
|
||||
* {@link #accept(Object, Object)}.
|
||||
*
|
||||
* @param <O> Type that describes the owner of the key.
|
||||
*/
|
||||
public abstract class SecretKeySelectionStrategy<O> implements KeySelectionStrategy<PGPSecretKey, PGPSecretKeyRing, O> {
|
||||
|
||||
@Override
|
||||
public Set<PGPSecretKey> selectKeysFromKeyRing(O identifier, PGPSecretKeyRing ring) {
|
||||
Set<PGPSecretKey> keys = new HashSet<>();
|
||||
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
|
||||
PGPSecretKey key = i.next();
|
||||
if (accept(identifier, key)) keys.add(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiMap<O, PGPSecretKey> selectKeysFromKeyRings(MultiMap<O, PGPSecretKeyRing> keyRings) {
|
||||
MultiMap<O, PGPSecretKey> keys = new MultiMap<>();
|
||||
for (O identifier : keyRings.keySet()) {
|
||||
for (PGPSecretKeyRing ring : keyRings.get(identifier)) {
|
||||
keys.put(identifier, selectKeysFromKeyRing(identifier, ring));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
|
||||
public class And {
|
||||
|
||||
public static class PubKeySelectionStrategy<O> extends PublicKeySelectionStrategy<O> {
|
||||
|
||||
private final PublicKeySelectionStrategy<O> left;
|
||||
private final PublicKeySelectionStrategy<O> right;
|
||||
|
||||
public PubKeySelectionStrategy(PublicKeySelectionStrategy<O> left,
|
||||
PublicKeySelectionStrategy<O> right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKey key) {
|
||||
return left.accept(identifier, key) && right.accept(identifier, key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecKeySelectionStrategy<O> extends SecretKeySelectionStrategy<O> {
|
||||
|
||||
private final SecretKeySelectionStrategy<O> left;
|
||||
private final SecretKeySelectionStrategy<O> right;
|
||||
|
||||
public SecKeySelectionStrategy(SecretKeySelectionStrategy<O> left,
|
||||
SecretKeySelectionStrategy<O> right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKey key) {
|
||||
return left.accept(identifier, key) && right.accept(identifier, key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
|
||||
/**
|
||||
* Key Selection Strategy that only accepts {@link PGPPublicKey}s which are capable of encryption.
|
||||
*
|
||||
* @param <O> Type that describes the owner of the key (not used for decision).
|
||||
*/
|
||||
public class EncryptionKeySelectionStrategy<O> extends PublicKeySelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKey key) {
|
||||
return key.isEncryptionKey();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
|
||||
/**
|
||||
* Key Selection Strategies that do accept only keys, which have no revocation.
|
||||
*/
|
||||
public class NoRevocation {
|
||||
|
||||
/**
|
||||
* Key Selection Strategy which only accepts {@link PGPPublicKey}s which have no revocation.
|
||||
*
|
||||
* @param <O> Type that describes the owner of this key (not used for this decision).
|
||||
*/
|
||||
public static class PubKeySelectionStrategy<O> extends PublicKeySelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKey key) {
|
||||
return !key.hasRevocation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key Selection Strategy which only accepts {@link PGPSecretKey}s which have no revocation.
|
||||
*
|
||||
* @param <O> Type that describes the owner of this key (not used for this decision).
|
||||
*/
|
||||
public static class SecKeySelectionStrategy<O> extends SecretKeySelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKey key) {
|
||||
return !key.getPublicKey().hasRevocation();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
|
||||
public class Or {
|
||||
|
||||
public static class PubKeySelectionStrategy<O> extends PublicKeySelectionStrategy<O> {
|
||||
|
||||
private final PublicKeySelectionStrategy<O> left;
|
||||
private final PublicKeySelectionStrategy<O> right;
|
||||
|
||||
public PubKeySelectionStrategy(PublicKeySelectionStrategy<O> left,
|
||||
PublicKeySelectionStrategy<O> right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKey key) {
|
||||
return left.accept(identifier, key) || right.accept(identifier, key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecKeySelectionStrategy<O> extends SecretKeySelectionStrategy<O> {
|
||||
|
||||
private final SecretKeySelectionStrategy<O> left;
|
||||
private final SecretKeySelectionStrategy<O> right;
|
||||
|
||||
public SecKeySelectionStrategy(SecretKeySelectionStrategy<O> left,
|
||||
SecretKeySelectionStrategy<O> right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKey key) {
|
||||
return left.accept(identifier, key) || right.accept(identifier, key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
|
||||
/**
|
||||
* Key Selection Strategy that only accepts {@link PGPSecretKey}s which are capable of signing.
|
||||
*
|
||||
* @param <O> Type that describes the owner of the key (not used for this decision).
|
||||
*/
|
||||
public class SignatureKeySelectionStrategy<O> extends SecretKeySelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKey key) {
|
||||
return key.isSigningKey();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.key.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
|
||||
public class SignedByMasterKey {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SignedByMasterKey.class.getName());
|
||||
|
||||
public static class PubkeySelectionStrategy extends PublicKeySelectionStrategy<PGPPublicKey> {
|
||||
|
||||
@Override
|
||||
public boolean accept(PGPPublicKey masterKey, PGPPublicKey key) {
|
||||
// Same key -> accept
|
||||
if (Arrays.equals(masterKey.getFingerprint(), key.getFingerprint())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Iterator<PGPSignature> signatures = key.getSignaturesForKeyID(masterKey.getKeyID());
|
||||
while (signatures.hasNext()) {
|
||||
PGPSignature signature = signatures.next();
|
||||
if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
|
||||
try {
|
||||
signature.init(new BcPGPContentVerifierBuilderProvider(), masterKey);
|
||||
return signature.verifyCertification(masterKey, key);
|
||||
} catch (PGPException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not verify subkey signature of key " +
|
||||
Long.toHexString(masterKey.getKeyID()) + " on key " + Long.toHexString(key.getKeyID()));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Implementations of Key Selection Strategies.
|
||||
*/
|
||||
package org.pgpainless.key.selection.key.impl;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Different Key Selection Strategies.
|
||||
*/
|
||||
package org.pgpainless.key.selection.key;
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public interface KeyRingSelectionStrategy<R, C, O> {
|
||||
|
||||
boolean accept(O identifier, R keyRing);
|
||||
|
||||
Set<R> selectKeyRingsFromCollection(O identifier, C keyRingCollection);
|
||||
|
||||
MultiMap<O, R> selectKeyRingsFromCollections(MultiMap<O, C> keyRingCollections);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public abstract class PublicKeyRingSelectionStrategy<O> implements KeyRingSelectionStrategy<PGPPublicKeyRing, PGPPublicKeyRingCollection, O> {
|
||||
|
||||
@Override
|
||||
public Set<PGPPublicKeyRing> selectKeyRingsFromCollection(O identifier, PGPPublicKeyRingCollection keyRingCollection) {
|
||||
Set<PGPPublicKeyRing> accepted = new HashSet<>();
|
||||
for (Iterator<PGPPublicKeyRing> i = keyRingCollection.getKeyRings(); i.hasNext(); ) {
|
||||
PGPPublicKeyRing ring = i.next();
|
||||
if (accept(identifier, ring)) accepted.add(ring);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiMap<O, PGPPublicKeyRing> selectKeyRingsFromCollections(MultiMap<O, PGPPublicKeyRingCollection> keyRingCollections) {
|
||||
MultiMap<O, PGPPublicKeyRing> keyRings = new MultiMap<>();
|
||||
for (O identifier : keyRingCollections.keySet()) {
|
||||
for (PGPPublicKeyRingCollection collection : keyRingCollections.get(identifier)) {
|
||||
keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection));
|
||||
}
|
||||
}
|
||||
return keyRings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public abstract class SecretKeyRingSelectionStrategy<O> implements KeyRingSelectionStrategy<PGPSecretKeyRing, PGPSecretKeyRingCollection, O> {
|
||||
@Override
|
||||
public Set<PGPSecretKeyRing> selectKeyRingsFromCollection(O identifier, PGPSecretKeyRingCollection keyRingCollection) {
|
||||
Set<PGPSecretKeyRing> accepted = new HashSet<>();
|
||||
for (Iterator<PGPSecretKeyRing> i = keyRingCollection.getKeyRings(); i.hasNext(); ) {
|
||||
PGPSecretKeyRing ring = i.next();
|
||||
if (accept(identifier, ring)) accepted.add(ring);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiMap<O, PGPSecretKeyRing> selectKeyRingsFromCollections(MultiMap<O, PGPSecretKeyRingCollection> keyRingCollections) {
|
||||
MultiMap<O, PGPSecretKeyRing> keyRings = new MultiMap<>();
|
||||
for (O identifier : keyRingCollections.keySet()) {
|
||||
for (PGPSecretKeyRingCollection collection : keyRingCollections.get(identifier)) {
|
||||
keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection));
|
||||
}
|
||||
}
|
||||
return keyRings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
|
||||
public class Email {
|
||||
|
||||
public static class PubRingSelectionStrategy extends PartialUserId.PubRingSelectionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean accept(String email, PGPPublicKey key) {
|
||||
// Ensure, that email address is encapsulated in "<",">"
|
||||
if (!email.matches("^<.+>$")) {
|
||||
email = "<" + email + ">";
|
||||
}
|
||||
return super.accept(email, key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends PartialUserId.SecRingSelectionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean accept(String email, PGPSecretKey key) {
|
||||
// Ensure, that email address is encapsulated in "<",">"
|
||||
if (!email.matches("^<.+>$")) {
|
||||
email = "<" + email + ">";
|
||||
}
|
||||
return super.accept(email, key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
|
||||
public class ExactUserId {
|
||||
|
||||
public static class PubRingSelectionStrategy extends PublicKeyRingSelectionStrategy<String> {
|
||||
|
||||
@Override
|
||||
public boolean accept(String identifier, PGPPublicKeyRing keyRing) {
|
||||
Iterator<String> userIds = keyRing.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
if (userIds.next().equals(identifier)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends SecretKeyRingSelectionStrategy<String> {
|
||||
|
||||
@Override
|
||||
public boolean accept(String identifier, PGPSecretKeyRing keyRing) {
|
||||
Iterator<String> userIds = keyRing.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
if (userIds.next().equals(identifier)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
|
||||
|
||||
public class PartialUserId {
|
||||
|
||||
public static class PubRingSelectionStrategy extends PublicKeySelectionStrategy<String> {
|
||||
|
||||
@Override
|
||||
public boolean accept(String identifier, PGPPublicKey key) {
|
||||
for (Iterator<String> userIds = key.getUserIDs(); userIds.hasNext(); ) {
|
||||
String userId = userIds.next();
|
||||
if (userId.contains(identifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends SecretKeySelectionStrategy<String> {
|
||||
|
||||
@Override
|
||||
public boolean accept(String identifier, PGPSecretKey key) {
|
||||
for (Iterator userIds = key.getUserIDs(); userIds.hasNext(); ) {
|
||||
String userId = (String) userIds.next();
|
||||
if (userId.contains(identifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
import org.pgpainless.util.MultiMap;
|
||||
|
||||
public class Whitelist {
|
||||
|
||||
public static class PubRingSelectionStrategy<O> extends PublicKeyRingSelectionStrategy<O> {
|
||||
|
||||
private final MultiMap<O, Long> whitelist;
|
||||
|
||||
public PubRingSelectionStrategy(MultiMap<O, Long> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public PubRingSelectionStrategy(Map<O, Set<Long>> whitelist) {
|
||||
this.whitelist = new MultiMap<>(whitelist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKeyRing keyRing) {
|
||||
Set<Long> whitelistedKeyIds = whitelist.get(identifier);
|
||||
|
||||
if (whitelistedKeyIds == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return whitelistedKeyIds.contains(keyRing.getPublicKey().getKeyID());
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy<O> extends SecretKeyRingSelectionStrategy<O> {
|
||||
|
||||
private final MultiMap<O, Long> whitelist;
|
||||
|
||||
public SecRingSelectionStrategy(MultiMap<O, Long> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public SecRingSelectionStrategy(Map<O, Set<Long>> whitelist) {
|
||||
this.whitelist = new MultiMap<>(whitelist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKeyRing keyRing) {
|
||||
Set<Long> whitelistedKeyIds = whitelist.get(identifier);
|
||||
|
||||
if (whitelistedKeyIds == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return whitelistedKeyIds.contains(keyRing.getPublicKey().getKeyID());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
|
||||
public class Wildcard {
|
||||
|
||||
public class PubRingSelectionStrategy<O> extends PublicKeyRingSelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPPublicKeyRing keyRing) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SecRingSelectionStrategy<O> extends SecretKeyRingSelectionStrategy<O> {
|
||||
|
||||
@Override
|
||||
public boolean accept(O identifier, PGPSecretKeyRing keyRing) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2018 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.key.selection.keyring.impl;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
|
||||
public class XMPP {
|
||||
|
||||
public static class PubRingSelectionStrategy extends ExactUserId.PubRingSelectionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean accept(String jid, PGPPublicKeyRing keyRing) {
|
||||
return super.accept("xmpp:" + jid, keyRing);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends ExactUserId.SecRingSelectionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean accept(String jid, PGPSecretKeyRing keyRing) {
|
||||
return super.accept("xmpp:" + jid, keyRing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Implementations of Key Ring Selection Strategies.
|
||||
*/
|
||||
package org.pgpainless.key.selection.keyring.impl;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Different Key Ring Selection Strategies.
|
||||
*/
|
||||
package org.pgpainless.key.selection.keyring;
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* PGPainless - Use OpenPGP Painlessly!
|
||||
*
|
||||
* @see <a href="http://pgpainless.org">org.pgpainless.core.org</a>
|
||||
*/
|
||||
package org.pgpainless;
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2018 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.symmetric_encryption;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
* Stolen from <a href="https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java">
|
||||
* Bouncycastle examples</a>.
|
||||
*/
|
||||
public class SymmetricEncryptorDecryptor {
|
||||
|
||||
/**
|
||||
* Encrypt some {@code data} symmetrically using an {@code encryptionAlgorithm} and a given {@code password}.
|
||||
* The input data will be compressed using the given {@code compressionAlgorithm} and packed in a modification
|
||||
* detection package, which is then encrypted.
|
||||
*
|
||||
* @param data bytes that will be encrypted
|
||||
* @param password password that will be used to encrypt the data
|
||||
* @param encryptionAlgorithm symmetric algorithm that will be used to encrypt the data
|
||||
* @param compressionAlgorithm compression algorithm that will be used to compress the data
|
||||
* @return encrypted data
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException OpenPGP is brittle
|
||||
*/
|
||||
public static byte[] symmetricallyEncrypt(byte[] data,
|
||||
Passphrase password,
|
||||
SymmetricKeyAlgorithm encryptionAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm)
|
||||
throws IOException, PGPException {
|
||||
|
||||
byte[] compressedData = compress(data, compressionAlgorithm.getAlgorithmId());
|
||||
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
|
||||
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
|
||||
new JcePGPDataEncryptorBuilder(encryptionAlgorithm.getAlgorithmId())
|
||||
.setWithIntegrityPacket(true)
|
||||
.setSecureRandom(new SecureRandom())
|
||||
.setProvider("BC"));
|
||||
|
||||
encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(password.getChars()).setProvider("BC"));
|
||||
|
||||
OutputStream encOut = encGen.open(bOut, compressedData.length);
|
||||
|
||||
encOut.write(compressedData);
|
||||
encOut.close();
|
||||
|
||||
return bOut.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt and decompress some symmetrically encrypted data using a password.
|
||||
* Note, that decryption will fail if the given data is not integrity protected with a modification detection
|
||||
* package.
|
||||
*
|
||||
* @param data encrypted data
|
||||
* @param password password to decrypt the data
|
||||
* @return decrypted data
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException OpenPGP is brittle
|
||||
*/
|
||||
public static byte[] symmetricallyDecrypt(byte[] data, Passphrase password) throws IOException, PGPException {
|
||||
InputStream in = new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
in = PGPUtil.getDecoderStream(in);
|
||||
|
||||
BcPGPObjectFactory pgpF = new BcPGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
if (o instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) o;
|
||||
} else {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
|
||||
PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) enc.get(0);
|
||||
|
||||
InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(
|
||||
password.getChars(), new BcPGPDigestCalculatorProvider()));
|
||||
|
||||
|
||||
BcPGPObjectFactory pgpFact = new BcPGPObjectFactory(clear);
|
||||
|
||||
o = pgpFact.nextObject();
|
||||
if (o instanceof PGPCompressedData) {
|
||||
PGPCompressedData cData = (PGPCompressedData) o;
|
||||
pgpFact = new BcPGPObjectFactory(cData.getDataStream());
|
||||
o = pgpFact.nextObject();
|
||||
}
|
||||
|
||||
PGPLiteralData ld = (PGPLiteralData) o;
|
||||
InputStream unc = ld.getInputStream();
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Streams.pipeAll(unc, outputStream);
|
||||
|
||||
outputStream.close();
|
||||
|
||||
if (pbe.isIntegrityProtected()) {
|
||||
if (!pbe.verify()) {
|
||||
throw new PGPException("Integrity check failed.");
|
||||
}
|
||||
} else {
|
||||
throw new PGPException("Symmetrically encrypted data is not integrity protected.");
|
||||
}
|
||||
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap some data in an OpenPGP compressed data package.
|
||||
*
|
||||
* @param clearData uncompressed data
|
||||
* @param algorithm compression algorithm
|
||||
* @return compressed data
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
private static byte[] compress(byte[] clearData, int algorithm) throws IOException {
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
|
||||
OutputStream cos = comData.open(bOut);
|
||||
|
||||
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
|
||||
|
||||
OutputStream pOut = lData.open(cos,
|
||||
PGPLiteralData.BINARY,
|
||||
PGPLiteralDataGenerator.CONSOLE,
|
||||
clearData.length,
|
||||
new Date()
|
||||
);
|
||||
|
||||
pOut.write(clearData);
|
||||
pOut.close();
|
||||
|
||||
comData.close();
|
||||
|
||||
return bOut.toByteArray();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Classes related to OpenPGP symmetric encryption.
|
||||
*/
|
||||
package org.pgpainless.symmetric_encryption;
|
321
pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java
Normal file
321
pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.key.selection.key.impl.And;
|
||||
import org.pgpainless.key.selection.key.impl.NoRevocation;
|
||||
import org.pgpainless.key.selection.key.impl.SignedByMasterKey;
|
||||
|
||||
public class BCUtil {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName());
|
||||
|
||||
/*
|
||||
PGPXxxKeyRing -> PGPXxxKeyRingCollection
|
||||
*/
|
||||
public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(PGPPublicKeyRing... rings)
|
||||
throws IOException, PGPException {
|
||||
return new PGPPublicKeyRingCollection(Arrays.asList(rings));
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(PGPSecretKeyRing... rings)
|
||||
throws IOException, PGPException {
|
||||
return new PGPSecretKeyRingCollection(Arrays.asList(rings));
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRing publicKeyRingFromSecretKeyRing(PGPSecretKeyRing secretKeys)
|
||||
throws PGPException, IOException {
|
||||
PGPSecretKeyRing fixedSecretKeys = KeyRingSubKeyFix.repairSubkeyPackets(secretKeys, null, null);
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(512);
|
||||
for (PGPSecretKey secretKey : fixedSecretKeys) {
|
||||
PGPPublicKey publicKey = secretKey.getPublicKey();
|
||||
if (publicKey != null) {
|
||||
publicKey.encode(buffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
return new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
/*
|
||||
PGPXxxKeyRingCollection -> PGPXxxKeyRing
|
||||
*/
|
||||
|
||||
public static PGPSecretKeyRing getKeyRingFromCollection(PGPSecretKeyRingCollection collection, Long id)
|
||||
throws PGPException, IOException {
|
||||
PGPSecretKeyRing uncleanedRing = collection.getSecretKeyRing(id);
|
||||
|
||||
// Determine ids of signed keys
|
||||
Set<Long> signedKeyIds = new HashSet<>();
|
||||
signedKeyIds.add(id); // Add the signing key itself
|
||||
Iterator<PGPPublicKey> signedPubKeys = uncleanedRing.getKeysWithSignaturesBy(id);
|
||||
while (signedPubKeys.hasNext()) {
|
||||
signedKeyIds.add(signedPubKeys.next().getKeyID());
|
||||
}
|
||||
|
||||
PGPSecretKeyRing cleanedRing = uncleanedRing;
|
||||
Iterator<PGPSecretKey> secretKeys = uncleanedRing.getSecretKeys();
|
||||
while (secretKeys.hasNext()) {
|
||||
PGPSecretKey secretKey = secretKeys.next();
|
||||
if (!signedKeyIds.contains(secretKey.getKeyID())) {
|
||||
cleanedRing = PGPSecretKeyRing.removeSecretKey(cleanedRing, secretKey);
|
||||
}
|
||||
}
|
||||
return cleanedRing;
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRing getKeyRingFromCollection(PGPPublicKeyRingCollection collection, Long id)
|
||||
throws PGPException {
|
||||
PGPPublicKey key = collection.getPublicKey(id);
|
||||
return removeUnassociatedKeysFromKeyRing(collection.getPublicKeyRing(id), key);
|
||||
}
|
||||
|
||||
public static InputStream getPgpDecoderInputStream(byte[] bytes) throws IOException {
|
||||
return getPgpDecoderInputStream(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public static InputStream getPgpDecoderInputStream(InputStream inputStream) throws IOException {
|
||||
return PGPUtil.getDecoderStream(inputStream);
|
||||
}
|
||||
|
||||
public static byte[] getDecodedBytes(byte[] bytes) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(getPgpDecoderInputStream(bytes), buffer);
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] getDecodedBytes(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(inputStream, buffer);
|
||||
return getDecodedBytes(buffer.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all keys from the key ring, are either not having a subkey signature from the master key
|
||||
* (identified by {@code masterKeyId}), or are revoked ("normal" key revocation, as well as subkey revocation).
|
||||
*
|
||||
* @param ring key ring
|
||||
* @param masterKey master key
|
||||
* @return "cleaned" key ring
|
||||
*/
|
||||
public static PGPPublicKeyRing removeUnassociatedKeysFromKeyRing(PGPPublicKeyRing ring, PGPPublicKey masterKey) {
|
||||
if (!masterKey.isMasterKey()) {
|
||||
throw new IllegalArgumentException("Given key is not a master key.");
|
||||
}
|
||||
// Only select keys which are signed by the master key and not revoked.
|
||||
PublicKeySelectionStrategy<PGPPublicKey> selector = new And.PubKeySelectionStrategy<>(
|
||||
new SignedByMasterKey.PubkeySelectionStrategy(),
|
||||
new NoRevocation.PubKeySelectionStrategy<>());
|
||||
|
||||
PGPPublicKeyRing cleaned = ring;
|
||||
|
||||
Iterator<PGPPublicKey> publicKeys = ring.getPublicKeys();
|
||||
while (publicKeys.hasNext()) {
|
||||
PGPPublicKey publicKey = publicKeys.next();
|
||||
if (!selector.accept(masterKey, publicKey)) {
|
||||
cleaned = PGPPublicKeyRing.removePublicKey(cleaned, publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all keys from the key ring, are either not having a subkey signature from the master key
|
||||
* (identified by {@code masterKeyId}), or are revoked ("normal" key revocation, as well as subkey revocation).
|
||||
*
|
||||
* @param ring key ring
|
||||
* @param masterKey master key
|
||||
* @return "cleaned" key ring
|
||||
*/
|
||||
public static PGPSecretKeyRing removeUnassociatedKeysFromKeyRing(PGPSecretKeyRing ring, PGPPublicKey masterKey) {
|
||||
if (!masterKey.isMasterKey()) {
|
||||
throw new IllegalArgumentException("Given key is not a master key.");
|
||||
}
|
||||
// Only select keys which are signed by the master key and not revoked.
|
||||
PublicKeySelectionStrategy<PGPPublicKey> selector = new And.PubKeySelectionStrategy<>(
|
||||
new SignedByMasterKey.PubkeySelectionStrategy(),
|
||||
new NoRevocation.PubKeySelectionStrategy<>());
|
||||
|
||||
PGPSecretKeyRing cleaned = ring;
|
||||
|
||||
Iterator<PGPSecretKey> secretKeys = ring.getSecretKeys();
|
||||
while (secretKeys.hasNext()) {
|
||||
PGPSecretKey secretKey = secretKeys.next();
|
||||
if (!selector.accept(masterKey, secretKey.getPublicKey())) {
|
||||
cleaned = PGPSecretKeyRing.removeSecretKey(cleaned, secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link PGPPublicKey} which is the master key of the key ring.
|
||||
*
|
||||
* @param ring key ring
|
||||
* @return master key
|
||||
*/
|
||||
public static PGPPublicKey getMasterKeyFrom(PGPPublicKeyRing ring) {
|
||||
Iterator<PGPPublicKey> it = ring.getPublicKeys();
|
||||
while (it.hasNext()) {
|
||||
PGPPublicKey k = it.next();
|
||||
if (k.isMasterKey()) {
|
||||
// There can only be one master key, so we can immediately return
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PGPPublicKey getMasterKeyFrom(PGPKeyRing ring) {
|
||||
Iterator<PGPPublicKey> it = ring.getPublicKeys();
|
||||
while (it.hasNext()) {
|
||||
PGPPublicKey k = it.next();
|
||||
if (k.isMasterKey()) {
|
||||
// There can only be one master key, so we can immediately return
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Set<Long> signingKeyIds(PGPSecretKeyRing ring) {
|
||||
Set<Long> ids = new HashSet<>();
|
||||
Iterator<PGPPublicKey> it = ring.getPublicKeys();
|
||||
while (it.hasNext()) {
|
||||
PGPPublicKey k = it.next();
|
||||
|
||||
boolean signingKey = false;
|
||||
|
||||
Iterator sit = k.getSignatures();
|
||||
while (sit.hasNext()) {
|
||||
Object n = sit.next();
|
||||
if (!(n instanceof PGPSignature)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSignature s = (PGPSignature) n;
|
||||
if (!s.hasSubpackets()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
s.verifyCertification(ring.getPublicKey(s.getKeyID()));
|
||||
} catch (PGPException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not verify signature on " + Long.toHexString(k.getKeyID()) + " made by " + Long.toHexString(s.getKeyID()));
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector hashed = s.getHashedSubPackets();
|
||||
if (KeyFlag.fromInteger(hashed.getKeyFlags()).contains(KeyFlag.SIGN_DATA)) {
|
||||
signingKey = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (signingKey) {
|
||||
ids.add(k.getKeyID());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
public static boolean keyRingContainsKeyWithId(PGPPublicKeyRing ring, long keyId) {
|
||||
return ring.getPublicKey(keyId) != null;
|
||||
}
|
||||
|
||||
public static boolean keyRingContainsKeyWithId(PGPSecretKeyRing ring, long keyId) {
|
||||
return ring.getSecretKey(keyId) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
public static PGPKeyRing merge(PGPKeyRing one, PGPKeyRing other) {
|
||||
|
||||
PGPPublicKey masterOne = getMasterKeyFrom(one);
|
||||
if (masterOne == null) {
|
||||
throw new IllegalArgumentException("First PGPKeyRing has no master key");
|
||||
}
|
||||
|
||||
PGPPublicKey masterOther = getMasterKeyFrom(other);
|
||||
if (masterOther == null) {
|
||||
throw new IllegalArgumentException("Other PGPKeyRing has no master key");
|
||||
}
|
||||
|
||||
if (masterOne.getKeyID() != masterOther.getKeyID() ||
|
||||
Arrays.equals(masterOne.getFingerprint(), masterOther.getFingerprint())) {
|
||||
throw new IllegalArgumentException("Keys are not the same.");
|
||||
}
|
||||
|
||||
PGPKeyRing merged = one;
|
||||
|
||||
boolean mergedIsSecret = (merged instanceof PGPSecretKeyRing);
|
||||
boolean otherIsSecret = (other instanceof PGPSecretKeyRing);
|
||||
|
||||
for (Iterator it = other.getPublicKeys(); it.hasNext(); ) {
|
||||
|
||||
PGPPublicKey nextPublicKey = (PGPPublicKey) it.next();
|
||||
PGPPublicKey pendant = merged.getPublicKey(nextPublicKey.getKeyID());
|
||||
|
||||
if (pendant == null) {
|
||||
if (mergedIsSecret && otherIsSecret) {
|
||||
// Add secret key
|
||||
PGPSecretKey secretKey = ((PGPSecretKeyRing) other).getSecretKey(nextPublicKey.getKeyID());
|
||||
merged = PGPSecretKeyRing.insertSecretKey((PGPSecretKeyRing) merged, secretKey);
|
||||
} else {
|
||||
if (mergedIsSecret) {
|
||||
PGPSecretKeyRing mergedAsSecret = (PGPSecretKeyRing) merged;
|
||||
PGPSecretKey secretKey = mergedAsSecret.getSecretKey(nextPublicKey.getKeyID());
|
||||
if (secretKey == null) {
|
||||
PGPPublicKeyRing mergedAsPublic = publicKeyRingFromSecretKeyRing((PGPSecretKeyRing) merged);
|
||||
mergedAsPublic = PGPPublicKeyRing.insertPublicKey(mergedAsPublic, nextPublicKey);
|
||||
mergedAsSecret = PGPSecretKeyRing.replacePublicKeys(mergedAsSecret, mergedAsPublic);
|
||||
merged = mergedAsSecret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2018 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.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.bcpg.PublicSubkeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||
|
||||
public class KeyRingSubKeyFix {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(KeyRingSubKeyFix.class.getName());
|
||||
|
||||
/**
|
||||
* This method makes sure, that sub keys do consist of sub key packets.
|
||||
* Bouncycastle versions up to and including 1.60 created {@link PGPSecretKeyRing}s which sub keys consisted of
|
||||
* normal public key packets, which would result in lost keys when converting PGPSecretKeyRings to PGPPublicKeyRings.
|
||||
*
|
||||
* This method throws a {@link RuntimeException} of a {@link NoSuchFieldException} or {@link IllegalAccessException}.
|
||||
*
|
||||
* @see <a href="https://github.com/bcgit/bc-java/issues/381">Bouncycastle Java bug report #381</a>
|
||||
*
|
||||
* @param secretKeys possibly faulty PGPSecretKeyRing
|
||||
* @param decryptor decryptor in case the keys are encrypted (can be null)
|
||||
* @param encryptor encryptor to re-encrypt the keys in case they are encrypted (can be null)
|
||||
*
|
||||
* @return fixed PGPSecretKeyRing
|
||||
*
|
||||
* @throws PGPException in case we cannot dismantle or reassemble the key.
|
||||
*/
|
||||
public static PGPSecretKeyRing repairSubkeyPackets(PGPSecretKeyRing secretKeys,
|
||||
PBESecretKeyDecryptor decryptor,
|
||||
PBESecretKeyEncryptor encryptor)
|
||||
throws PGPException {
|
||||
|
||||
PGPDigestCalculator calculator = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
List<PGPSecretKey> _secretKeys = new ArrayList<>();
|
||||
Iterator<PGPSecretKey> secretKeyIterator = secretKeys.iterator();
|
||||
try {
|
||||
|
||||
while (secretKeyIterator.hasNext()) {
|
||||
PGPSecretKey key = secretKeyIterator.next();
|
||||
|
||||
if (key.isMasterKey()) {
|
||||
LOGGER.log(Level.INFO, Long.toHexString(key.getKeyID()) + " is master key. Skip.");
|
||||
_secretKeys.add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSecretKey secSubKey = key;
|
||||
PGPPublicKey pubSubKey = secSubKey.getPublicKey();
|
||||
|
||||
// check for public key packet type
|
||||
|
||||
Field publicPk = pubSubKey.getClass().getDeclaredField("publicPk");
|
||||
publicPk.setAccessible(true);
|
||||
PublicKeyPacket keyPacket = (PublicKeyPacket) publicPk.get(pubSubKey);
|
||||
|
||||
if (keyPacket instanceof PublicSubkeyPacket) {
|
||||
// Sub key is already sub key
|
||||
_secretKeys.add(secSubKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sub key is normal key -> fix
|
||||
LOGGER.log(Level.INFO, "Subkey " + Long.toHexString(secSubKey.getKeyID()) + " does not have a subkey key packet. Convert it...");
|
||||
keyPacket = new PublicSubkeyPacket(pubSubKey.getAlgorithm(), pubSubKey.getCreationTime(), keyPacket.getKey());
|
||||
publicPk.set(pubSubKey, keyPacket);
|
||||
|
||||
PGPPrivateKey privateKey = secSubKey.extractPrivateKey(decryptor);
|
||||
|
||||
PGPSecretKey secretKey = new PGPSecretKey(privateKey, pubSubKey, calculator, false, encryptor);
|
||||
_secretKeys.add(secretKey);
|
||||
}
|
||||
|
||||
return new PGPSecretKeyRing(_secretKeys);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot apply fix due to an error while using reflections.", e);
|
||||
}
|
||||
}
|
||||
}
|
135
pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java
Normal file
135
pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2018 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.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class MultiMap<K, V> {
|
||||
|
||||
private final Map<K, Set<V>> map;
|
||||
|
||||
public MultiMap() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
public MultiMap(MultiMap<K, V> other) {
|
||||
this.map = new HashMap<>();
|
||||
for (K k : other.map.keySet()) {
|
||||
map.put(k, new HashSet<>(other.map.get(k)));
|
||||
}
|
||||
}
|
||||
|
||||
public MultiMap(Map<K, Set<V>> content) {
|
||||
this.map = new HashMap<>(content);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsKey(Object o) {
|
||||
return map.containsKey(o);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object o) {
|
||||
for (Set<V> values : map.values()) {
|
||||
if (values.contains(o)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<V> get(Object o) {
|
||||
return map.get(o);
|
||||
}
|
||||
|
||||
public void put(K k, V v) {
|
||||
Set<V> values = map.get(k);
|
||||
if (values == null) {
|
||||
values = new HashSet<>();
|
||||
map.put(k, values);
|
||||
}
|
||||
values.add(v);
|
||||
}
|
||||
|
||||
public void put(K k, Set<V> vs) {
|
||||
for (V v : vs) {
|
||||
put(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Object o) {
|
||||
for (Set<V> values : map.values()) {
|
||||
values.remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends K, ? extends Set<V>> _map) {
|
||||
for (K key : _map.keySet()) {
|
||||
Set<V> vs = this.map.get(key);
|
||||
if (vs == null) {
|
||||
vs = new HashSet<>();
|
||||
this.map.put(key, vs);
|
||||
}
|
||||
vs.addAll(_map.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public Set<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
public Collection<Set<V>> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<K, Set<V>>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(o instanceof MultiMap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return map.equals(((MultiMap) o).map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return map.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2018 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.util.Arrays;
|
||||
|
||||
public class Passphrase {
|
||||
|
||||
private final char[] chars;
|
||||
|
||||
public Passphrase(char[] chars) {
|
||||
if (chars == null) {
|
||||
throw new NullPointerException("chars MUST NOT be null.");
|
||||
}
|
||||
this.chars = chars;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
Arrays.fill(chars, ' ');
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
clear();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public char[] getChars() {
|
||||
char[] copy = new char[chars.length];
|
||||
System.arraycopy(chars, 0, copy, 0, chars.length);
|
||||
return copy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
/**
|
||||
* Utility classes.
|
||||
*/
|
||||
package org.pgpainless.util;
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
public abstract class AbstractPGPainlessTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void registerProvider() {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
151
pgpainless-core/src/test/java/org/pgpainless/BCUtilTest.java
Normal file
151
pgpainless-core/src/test/java/org/pgpainless/BCUtilTest.java
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.RSA_GENERAL;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
|
||||
public class BCUtilTest extends AbstractPGPainlessTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName());
|
||||
|
||||
@Test
|
||||
public void keyRingToCollectionTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing ring = PGPainless.generateKeyRing()
|
||||
.withSubKey(KeySpec.getBuilder(RSA_GENERAL.withLength(RsaLength._3072)).withDefaultKeyFlags().withDefaultAlgorithms())
|
||||
.withMasterKey(KeySpec.getBuilder(RSA_GENERAL.withLength(RsaLength._3072)).withDefaultKeyFlags().withDefaultAlgorithms())
|
||||
.withPrimaryUserId("donald@duck.tails").withoutPassphrase().build();
|
||||
PGPSecretKeyRing sec = ring.getSecretKeys();
|
||||
PGPPublicKeyRing pub = ring.getPublicKeys();
|
||||
|
||||
LOGGER.log(Level.INFO, "Main ID: " + sec.getPublicKey().getKeyID() + " " + pub.getPublicKey().getKeyID());
|
||||
|
||||
int secSize = 1;
|
||||
Iterator<PGPPublicKey> secPubIt = sec.getPublicKeys();
|
||||
while (secPubIt.hasNext()) {
|
||||
PGPPublicKey k = secPubIt.next();
|
||||
LOGGER.log(Level.INFO, secSize + " " + k.getKeyID() + " " + k.isEncryptionKey() + " " + k.isMasterKey());
|
||||
secSize++;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.INFO, "After BCUtil.publicKeyRingFromSecretKeyRing()");
|
||||
int pubSize = 1;
|
||||
Iterator<PGPPublicKey> pubPubIt = pub.getPublicKeys();
|
||||
while (pubPubIt.hasNext()) {
|
||||
PGPPublicKey k = pubPubIt.next();
|
||||
LOGGER.log(Level.INFO, pubSize + " " + k.getKeyID() + " " + k.isEncryptionKey() + " " + k.isMasterKey());
|
||||
pubSize++;
|
||||
}
|
||||
|
||||
assertEquals(secSize, pubSize);
|
||||
|
||||
PGPSecretKeyRingCollection secCol = BCUtil.keyRingsToKeyRingCollection(sec);
|
||||
|
||||
int secColSize = 0;
|
||||
Iterator<PGPSecretKeyRing> secColIt = secCol.getKeyRings();
|
||||
while (secColIt.hasNext()) {
|
||||
PGPSecretKeyRing r = secColIt.next();
|
||||
LOGGER.log(Level.INFO, "" + r.getPublicKey().getKeyID());
|
||||
secColSize++;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.INFO, "SecCol: " + secColSize);
|
||||
|
||||
PGPPublicKeyRingCollection pubCol = BCUtil.keyRingsToKeyRingCollection(pub);
|
||||
|
||||
int pubColSize = 0;
|
||||
Iterator<PGPPublicKeyRing> pubColIt = pubCol.getKeyRings();
|
||||
while (pubColIt.hasNext()) {
|
||||
PGPPublicKeyRing r = pubColIt.next();
|
||||
LOGGER.log(Level.INFO, "" + r.getPublicKey().getKeyID());
|
||||
pubColSize++;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.INFO, "PubCol: " + pubColSize);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeUnsignedKeysTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
PGPKeyRing alice = PGPainless.generateKeyRing().simpleRsaKeyRing("alice@wonderland.lit", RsaLength._1024);
|
||||
PGPKeyRing mallory = PGPainless.generateKeyRing().simpleEcKeyRing("mallory@mall.ory");
|
||||
|
||||
PGPSecretKey subKey = null;
|
||||
Iterator<PGPSecretKey> sit = mallory.getSecretKeys().getSecretKeys();
|
||||
while (sit.hasNext()) {
|
||||
PGPSecretKey s = sit.next();
|
||||
if (!s.isMasterKey()) {
|
||||
subKey = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TestCase.assertNotNull(subKey);
|
||||
|
||||
PGPSecretKeyRing alice_mallory = PGPSecretKeyRing.insertSecretKey(alice.getSecretKeys(), subKey);
|
||||
|
||||
// Check, if alice_mallory contains mallory's key
|
||||
TestCase.assertNotNull(alice_mallory.getSecretKey(subKey.getKeyID()));
|
||||
|
||||
PGPSecretKeyRing cleaned = BCUtil.removeUnassociatedKeysFromKeyRing(alice_mallory, alice.getPublicKeys().getPublicKey());
|
||||
TestCase.assertNull(cleaned.getSecretKey(subKey.getKeyID()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeUnsignedKeysECTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing ring = PGPainless.generateKeyRing().simpleEcKeyRing("alice@wonderland.lit");
|
||||
PGPPublicKeyRing publicKeys = ring.getPublicKeys();
|
||||
PGPSecretKeyRing secretKeys = ring.getSecretKeys();
|
||||
PGPSecretKeyRing secCleaned = ring.getSecretKeys();
|
||||
|
||||
assertTrue(Arrays.equals(secretKeys.getEncoded(), secCleaned.getEncoded()));
|
||||
|
||||
PGPPublicKeyRing pubCleaned = BCUtil.removeUnassociatedKeysFromKeyRing(publicKeys, publicKeys.getPublicKey());
|
||||
|
||||
assertTrue(Arrays.equals(publicKeys.getEncoded(), pubCleaned.getEncoded()));
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.sig.Features;
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.junit.Test;
|
||||
|
||||
public class BouncycastleExportSubkeys {
|
||||
|
||||
@Test
|
||||
public void testExportImport() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, PGPException, IOException {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
KeyPairGenerator generator;
|
||||
KeyPair pair;
|
||||
|
||||
// Generate master key
|
||||
|
||||
generator = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
|
||||
generator.initialize(new ECNamedCurveGenParameterSpec("P-256"));
|
||||
|
||||
pair = generator.generateKeyPair();
|
||||
PGPKeyPair pgpMasterKey = new JcaPGPKeyPair(PublicKeyAlgorithmTags.ECDSA, pair, new Date());
|
||||
|
||||
PGPSignatureSubpacketGenerator subPackets = new PGPSignatureSubpacketGenerator();
|
||||
subPackets.setKeyFlags(false, KeyFlags.AUTHENTICATION & KeyFlags.CERTIFY_OTHER & KeyFlags.SIGN_DATA);
|
||||
subPackets.setPreferredCompressionAlgorithms(false, new int[] {
|
||||
SymmetricKeyAlgorithmTags.AES_256,
|
||||
SymmetricKeyAlgorithmTags.AES_128,
|
||||
SymmetricKeyAlgorithmTags.AES_128});
|
||||
subPackets.setPreferredHashAlgorithms(false, new int[] {
|
||||
HashAlgorithmTags.SHA512,
|
||||
HashAlgorithmTags.SHA384,
|
||||
HashAlgorithmTags.SHA256,
|
||||
HashAlgorithmTags.SHA224});
|
||||
subPackets.setPreferredCompressionAlgorithms(false, new int[] {
|
||||
CompressionAlgorithmTags.ZLIB,
|
||||
CompressionAlgorithmTags.BZIP2,
|
||||
CompressionAlgorithmTags.ZIP,
|
||||
CompressionAlgorithmTags.UNCOMPRESSED});
|
||||
subPackets.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
|
||||
|
||||
// Generate sub key
|
||||
|
||||
generator = KeyPairGenerator.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
|
||||
generator.initialize(new ECNamedCurveGenParameterSpec("P-256"));
|
||||
|
||||
pair = generator.generateKeyPair();
|
||||
PGPKeyPair pgpSubKey = new JcaPGPKeyPair(PublicKeyAlgorithmTags.ECDH, pair, new Date());
|
||||
|
||||
// Assemble key
|
||||
|
||||
PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pgpMasterKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA512)
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
|
||||
PGPKeyRingGenerator pgpGenerator = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
|
||||
pgpMasterKey, "alice@wonderland.lit", calculator, subPackets.generate(), null,
|
||||
signerBuilder, null);
|
||||
|
||||
// Add sub key
|
||||
|
||||
subPackets.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE & KeyFlags.ENCRYPT_COMMS);
|
||||
|
||||
pgpGenerator.addSubKey(pgpSubKey, subPackets.generate(), null);
|
||||
|
||||
// Generate SecretKeyRing
|
||||
|
||||
PGPSecretKeyRing secretKeys = pgpGenerator.generateSecretKeyRing();
|
||||
PGPPublicKeyRing publicKeys = pgpGenerator.generatePublicKeyRing();
|
||||
|
||||
// Test
|
||||
|
||||
/*
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048);
|
||||
outputStream.write(secretKeys.getEncoded());
|
||||
|
||||
PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(outputStream.toByteArray(), new BcKeyFingerprintCalculator());
|
||||
|
||||
Iterator<PGPPublicKey> iterator = secretKeys.getPublicKeys();
|
||||
while (iterator.hasNext()) {
|
||||
assertNotNull(publicKeys.getPublicKey(iterator.next().getKeyID()));
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.PainlessResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.ElGamal_GENERAL;
|
||||
import org.pgpainless.key.generation.type.RSA_GENERAL;
|
||||
import org.pgpainless.key.generation.type.length.ElGamalLength;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
|
||||
public class EncryptDecryptTest extends AbstractPGPainlessTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(EncryptDecryptTest.class.getName());
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
private static final String testMessage = "Ah, Juliet, if the measure of thy joy\n" +
|
||||
"Be heaped like mine, and that thy skill be more\n" +
|
||||
"To blazon it, then sweeten with thy breath\n" +
|
||||
"This neighbor air, and let rich music’s tongue\n" +
|
||||
"Unfold the imagined happiness that both\n" +
|
||||
"Receive in either by this dear encounter.";
|
||||
|
||||
public EncryptDecryptTest() {
|
||||
LOGGER.log(Level.INFO, "Plain Length: " + testMessage.getBytes(UTF8).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void freshKeysRsaToElGamalTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("romeo@montague.lit", RsaLength._3072);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing()
|
||||
.withSubKey(KeySpec.getBuilder(ElGamal_GENERAL.withLength(ElGamalLength._3072)).withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS).withDefaultAlgorithms())
|
||||
.withMasterKey(KeySpec.getBuilder(RSA_GENERAL.withLength(RsaLength._4096)).withKeyFlags(KeyFlag.SIGN_DATA, KeyFlag.CERTIFY_OTHER).withDefaultAlgorithms())
|
||||
.withPrimaryUserId("juliet@capulet.lit").withoutPassphrase().build();
|
||||
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void freshKeysRsaToRsaTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("romeo@montague.lit", RsaLength._4096);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("juliet@capulet.lit", RsaLength._4096);
|
||||
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void freshKeysEcToEcTest() throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
InvalidAlgorithmParameterException {
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("romeo@montague.lit");
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("juliet@capulet.lit");
|
||||
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void freshKeysEcToRsaTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("romeo@montague.lit");
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("juliet@capulet.lit", RsaLength._4096);
|
||||
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void freshKeysRsaToEcTest()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("romeo@montague.lit", RsaLength._4096);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("juliet@capulet.lit");
|
||||
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
private void encryptDecryptForSecretKeyRings(PGPKeyRing sender, PGPKeyRing recipient)
|
||||
throws PGPException,
|
||||
IOException {
|
||||
PGPSecretKeyRing recipientSec = recipient.getSecretKeys();
|
||||
PGPSecretKeyRing senderSec = sender.getSecretKeys();
|
||||
PGPPublicKeyRing recipientPub = recipient.getPublicKeys();
|
||||
PGPPublicKeyRing senderPub = sender.getPublicKeys();
|
||||
|
||||
SecretKeyRingProtector keyDecryptor = new UnprotectedKeysProtector();
|
||||
|
||||
byte[] secretMessage = testMessage.getBytes(UTF8);
|
||||
|
||||
ByteArrayOutputStream envelope = new ByteArrayOutputStream();
|
||||
|
||||
EncryptionStream encryptor = PGPainless.createEncryptor()
|
||||
.onOutputStream(envelope)
|
||||
.toRecipients(recipientPub)
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(keyDecryptor, senderSec)
|
||||
.noArmor();
|
||||
|
||||
PainlessResult encryptionResult = encryptor.getResult();
|
||||
|
||||
assertFalse(encryptionResult.getAllSignatureKeyFingerprints().isEmpty());
|
||||
for (long keyId : encryptionResult.getAllSignatureKeyFingerprints()) {
|
||||
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, keyId));
|
||||
}
|
||||
|
||||
assertFalse(encryptionResult.getRecipientKeyIds().isEmpty());
|
||||
for (long keyId : encryptionResult.getRecipientKeyIds()) {
|
||||
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, keyId));
|
||||
}
|
||||
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionResult.getSymmetricKeyAlgorithm());
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||
encryptor.close();
|
||||
byte[] encryptedSecretMessage = envelope.toByteArray();
|
||||
|
||||
LOGGER.log(Level.INFO, "Sender: " + PublicKeyAlgorithm.fromId(senderPub.getPublicKey().getAlgorithm()) +
|
||||
" Receiver: " + PublicKeyAlgorithm.fromId(recipientPub.getPublicKey().getAlgorithm()) +
|
||||
" Encrypted Length: " + encryptedSecretMessage.length);
|
||||
|
||||
// Juliet trieth to comprehend Romeos words
|
||||
|
||||
ByteArrayInputStream envelopeIn = new ByteArrayInputStream(encryptedSecretMessage);
|
||||
DecryptionStream decryptor = PGPainless.createDecryptor()
|
||||
.onInputStream(envelopeIn)
|
||||
.decryptWith(keyDecryptor, BCUtil.keyRingsToKeyRingCollection(recipientSec))
|
||||
.verifyWith(BCUtil.keyRingsToKeyRingCollection(senderPub))
|
||||
.ignoreMissingPublicKeys()
|
||||
.build();
|
||||
|
||||
ByteArrayOutputStream decryptedSecretMessage = new ByteArrayOutputStream();
|
||||
|
||||
Streams.pipeAll(decryptor, decryptedSecretMessage);
|
||||
decryptor.close();
|
||||
|
||||
assertTrue(Arrays.equals(secretMessage, decryptedSecretMessage.toByteArray()));
|
||||
PainlessResult result = decryptor.getResult();
|
||||
assertTrue(result.containsVerifiedSignatureFrom(senderPub));
|
||||
assertTrue(result.isIntegrityProtected());
|
||||
assertTrue(result.isSigned());
|
||||
assertTrue(result.isEncrypted());
|
||||
assertTrue(result.isVerified());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
|
||||
public class ImportExportKeyTest {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is failing. Not sure if a bug in BC or in my code...
|
||||
* @throws PGPException very
|
||||
* @throws NoSuchAlgorithmException much
|
||||
* @throws NoSuchProviderException some
|
||||
* @throws InvalidAlgorithmParameterException annoying
|
||||
* @throws IOException exceptions
|
||||
*/
|
||||
@Test
|
||||
public void test()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("alice@bla.blub");
|
||||
PGPSecretKeyRing secretKeys = keyRing.getSecretKeys();
|
||||
PGPPublicKeyRing publicKeys = keyRing.getPublicKeys();
|
||||
|
||||
BcKeyFingerprintCalculator calc = new BcKeyFingerprintCalculator();
|
||||
byte[] bytes = publicKeys.getEncoded();
|
||||
PGPPublicKeyRing parsed = new PGPPublicKeyRing(bytes, calc);
|
||||
assertTrue(Arrays.equals(publicKeys.getEncoded(), parsed.getEncoded()));
|
||||
|
||||
Iterator<PGPPublicKey> it = secretKeys.getPublicKeys();
|
||||
assertTrue(it.hasNext());
|
||||
it.next();
|
||||
assertTrue(it.hasNext());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
import org.pgpainless.util.KeyRingSubKeyFix;
|
||||
|
||||
public class KeyRingSubKeyFixTest {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
PGPKeyRing ring = PGPainless.generateKeyRing().simpleEcKeyRing("hallo@welt.de");
|
||||
PGPSecretKeyRing secretKeys = ring.getSecretKeys();
|
||||
PGPPublicKeyRing publicKeys = ring.getPublicKeys();
|
||||
|
||||
PGPSecretKeyRing fixed = KeyRingSubKeyFix.repairSubkeyPackets(secretKeys, null, null);
|
||||
|
||||
assertTrue(Arrays.equals(secretKeys.getEncoded(), fixed.getEncoded()));
|
||||
|
||||
PGPPublicKeyRing fixedPub = BCUtil.publicKeyRingFromSecretKeyRing(fixed);
|
||||
|
||||
assertTrue(Arrays.equals(publicKeys.getEncoded(), fixedPub.getEncoded()));
|
||||
}
|
||||
}
|
128
pgpainless-core/src/test/java/org/pgpainless/LengthTest.java
Normal file
128
pgpainless-core/src/test/java/org/pgpainless/LengthTest.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.Ignore;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
/**
|
||||
* Class used to determine the length of cipher-text depending on used algorithms.
|
||||
*/
|
||||
public class LengthTest extends AbstractPGPainlessTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(LengthTest.class.getName());
|
||||
|
||||
// @Test
|
||||
public void ecEc()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
LOGGER.log(Level.INFO, "\nEC -> EC");
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("simplejid@server.tld");
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("otherjid@other.srv");
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
|
||||
// @Test
|
||||
public void RsaRsa()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
LOGGER.log(Level.INFO, "\nRSA-2048 -> RSA-2048");
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("simplejid@server.tld", RsaLength._2048);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("otherjid@other.srv", RsaLength._2048);
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void RsaRsa4096()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
LOGGER.log(Level.INFO, "\nRSA-4096 -> RSA-4096");
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("simplejid@server.tld", RsaLength._4096);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("otherjid@other.srv", RsaLength._4096);
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void rsaEc() throws PGPException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
LOGGER.log(Level.INFO, "\nRSA-2048 -> EC");
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("simplejid@server.tld", RsaLength._2048);
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("otherjid@other.srv");
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void ecRsa()
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
LOGGER.log(Level.INFO, "\nEC -> RSA-2048");
|
||||
PGPKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("simplejid@server.tld");
|
||||
PGPKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("otherjid@other.srv", RsaLength._2048);
|
||||
encryptDecryptForSecretKeyRings(sender, recipient);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
private void encryptDecryptForSecretKeyRings(PGPKeyRing sender, PGPKeyRing recipient)
|
||||
throws PGPException,
|
||||
IOException {
|
||||
PGPSecretKeyRing recipientSec = recipient.getSecretKeys();
|
||||
PGPSecretKeyRing senderSec = sender.getSecretKeys();
|
||||
PGPPublicKeyRing recipientPub = recipient.getPublicKeys();
|
||||
PGPPublicKeyRing senderPub = sender.getPublicKeys();
|
||||
|
||||
SecretKeyRingProtector keyDecryptor = new UnprotectedKeysProtector();
|
||||
|
||||
for (int i = 1; i <= 100; i++) {
|
||||
byte[] secretMessage = new byte[i * 20];
|
||||
new Random().nextBytes(secretMessage);
|
||||
|
||||
ByteArrayOutputStream envelope = new ByteArrayOutputStream();
|
||||
|
||||
OutputStream encryptor = PGPainless.createEncryptor()
|
||||
.onOutputStream(envelope)
|
||||
.toRecipients(recipientPub)
|
||||
// .doNotEncrypt()
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(keyDecryptor, senderSec)
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||
encryptor.close();
|
||||
byte[] encryptedSecretMessage = envelope.toByteArray();
|
||||
|
||||
LOGGER.log(Level.INFO,"\n" + encryptedSecretMessage.length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public class OpenPgpV4FingerprintTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void fpTooShort() {
|
||||
String fp = "484f57414c495645"; // Asking Mark
|
||||
new OpenPgpV4Fingerprint(fp);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidHexTest() {
|
||||
String fp = "UNFORTUNATELYTHISISNOVALIDHEXADECIMALDOH";
|
||||
new OpenPgpV4Fingerprint(fp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validFingerprintTest() {
|
||||
String fp = "4A4F48414E4E53454E2049532041204E45524421";
|
||||
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
|
||||
assertEquals(fp, finger.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertsToUpperCaseTest() {
|
||||
String fp = "444f4e5420552048415645204120484f4242593f";
|
||||
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
|
||||
assertEquals("444F4E5420552048415645204120484F4242593F", finger.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsOtherFingerprintTest() {
|
||||
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint("5448452043414b452049532041204c4945212121");
|
||||
assertEquals(finger, new OpenPgpV4Fingerprint("5448452043414B452049532041204C4945212121"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyIdTest() throws IOException {
|
||||
PGPPublicKey key = TestKeys.getJulietPublicKeyRing().getPublicKey();
|
||||
long keyId = key.getKeyID();
|
||||
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(key);
|
||||
assertEquals(keyId, fingerprint.getKeyId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public class SymmetricTest extends AbstractPGPainlessTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SymmetricTest.class.getName());
|
||||
|
||||
private static final String message = "I grew up with the understanding that the world " +
|
||||
"I lived in was one where people enjoyed a sort of freedom " +
|
||||
"to communicate with each other in privacy, without it " +
|
||||
"being monitored, without it being measured or analyzed " +
|
||||
"or sort of judged by these shadowy figures or systems, " +
|
||||
"any time they mention anything that travels across " +
|
||||
"public lines.\n" +
|
||||
"\n" +
|
||||
"- Edward Snowden -";
|
||||
|
||||
@Test
|
||||
public void testSymmetricEncryptionDecryption() throws IOException, PGPException {
|
||||
byte[] plain = message.getBytes();
|
||||
Passphrase passphrase = new Passphrase("choose_a_better_password_please".toCharArray());
|
||||
byte[] enc = PGPainless.encryptWithPassword(plain, passphrase, SymmetricKeyAlgorithm.AES_128);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armor = new ArmoredOutputStream(out);
|
||||
armor.write(enc);
|
||||
armor.flush();
|
||||
armor.close();
|
||||
|
||||
// Print cipher text for validation with GnuPG.
|
||||
LOGGER.log(Level.INFO, new String(out.toByteArray()));
|
||||
|
||||
byte[] plain2 = PGPainless.decryptWithPassword(enc, passphrase);
|
||||
assertTrue(Arrays.equals(plain, plain2));
|
||||
}
|
||||
}
|
267
pgpainless-core/src/test/java/org/pgpainless/TestKeys.java
Normal file
267
pgpainless-core/src/test/java/org/pgpainless/TestKeys.java
Normal file
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
|
||||
public class TestKeys {
|
||||
|
||||
private static final KeyFingerPrintCalculator calc = new BcKeyFingerprintCalculator();
|
||||
private static PGPSecretKeyRing julietSecretKeyRing = null;
|
||||
private static PGPPublicKeyRing julietPublicKeyRing = null;
|
||||
private static PGPSecretKeyRing romeoSecretKeyRing = null;
|
||||
private static PGPPublicKeyRing romeoPublicKeyRing = null;
|
||||
|
||||
private static PGPSecretKeyRingCollection julietSecretKeyRingCollection = null;
|
||||
private static PGPPublicKeyRingCollection julietPublicKeyRingCollection = null;
|
||||
private static PGPSecretKeyRingCollection romeoSecretKeyRingCollection = null;
|
||||
private static PGPPublicKeyRingCollection romeoPublicKeyRingCollection = null;
|
||||
|
||||
public static final String JULIET_UID = "xmpp:juliet@capulet.lit";
|
||||
public static final long JULIET_KEY_ID = -5425419407118114754L;
|
||||
|
||||
/**
|
||||
* Public key of xmpp:juliet@capulet.lit.
|
||||
*/
|
||||
public static final String JULIET_PUB = "" +
|
||||
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"\n" +
|
||||
"mQENBFrxov4BCAChZwPrBxxIlwzpieR5T2pnaOZLWH0WqSON6rVjvfbJHWdDi3Th\n" +
|
||||
"remHW4gg4IBSTXkVFDIeQNVcOvGNgMg3Oe/x0I6FK12jrw9prycmjFxQ7A0ix7ZG\n" +
|
||||
"UkTF5jITgzJbkH100gYfXtZsfTyvgISSAT//6vvvQPZ3zCr09XvAG0CyQ1BhULsv\n" +
|
||||
"mVRe4Oh5b0VK4kLdv+GiA/T+49UKZj6lne9Vdti16ZIj7teVCbicfdhpTzsjur42\n" +
|
||||
"r8ptouKAuyFPw9KnGNwVlIiv5jt/Kit/LoOBenh74sitsCXq8IQ9kKp/eNt8TF4u\n" +
|
||||
"D4IGpxnJfB8XCiixYHoFEajmQBVJXNYtvoPvABEBAAG0F3htcHA6anVsaWV0QGNh\n" +
|
||||
"cHVsZXQubGl0iQFOBBMBCAA4FiEEHQGMdy34xe+GodzJtLUJy1k24D4FAlrxov4C\n" +
|
||||
"Gy8FCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQtLUJy1k24D6H7AgAoTjx4ezc\n" +
|
||||
"A83NeOY3tMHVQTM7hKuy0wMcSzQgVgJmhLYRZS8r+FocPZua/eke49GPhe2yozvl\n" +
|
||||
"ByWHtotklQeJiwOKxuPKMzneVA1ZK3/9LdGvtZlHMcAkEKDhit8HIaEcsFd4Z1re\n" +
|
||||
"EhF2lyvY/E+rrx9YxV0QjisSWV2dSptv6FeGSztr9e5E+Head6hEQhsugiTVRF+1\n" +
|
||||
"6mG90te0WGQ9YNiJ2FJovx5kBLTTuhwUz8Oacqihd2+RDDI5p3wJoogVL31aNb4n\n" +
|
||||
"c7dGo8ieJPHGlkBsOfmreSxijTodZz9MXsgcx7b//u0uQryViJoZHWbtnXOFjjNc\n" +
|
||||
"GWBtS084NKWl9w==\n" +
|
||||
"=ecwX\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
/**
|
||||
* Private key of xmpp:juliet@capulet.lit.
|
||||
*/
|
||||
public static final String JULIET_SEC = "" +
|
||||
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"\n" +
|
||||
"lQOYBFrxov4BCAChZwPrBxxIlwzpieR5T2pnaOZLWH0WqSON6rVjvfbJHWdDi3Th\n" +
|
||||
"remHW4gg4IBSTXkVFDIeQNVcOvGNgMg3Oe/x0I6FK12jrw9prycmjFxQ7A0ix7ZG\n" +
|
||||
"UkTF5jITgzJbkH100gYfXtZsfTyvgISSAT//6vvvQPZ3zCr09XvAG0CyQ1BhULsv\n" +
|
||||
"mVRe4Oh5b0VK4kLdv+GiA/T+49UKZj6lne9Vdti16ZIj7teVCbicfdhpTzsjur42\n" +
|
||||
"r8ptouKAuyFPw9KnGNwVlIiv5jt/Kit/LoOBenh74sitsCXq8IQ9kKp/eNt8TF4u\n" +
|
||||
"D4IGpxnJfB8XCiixYHoFEajmQBVJXNYtvoPvABEBAAEAB/4jMbXagW3q7DkOEZnm\n" +
|
||||
"0+jVTLvu0QhRsScGEphj+++8sfMq+NVPQp9p+w0Hcjy49ZjB/mnhS+zaVCYI33yJ\n" +
|
||||
"AlKubXYuVqLwBsO7HUzRrIiSwq4ol9jIo7bIWmYv+As6iRq6JvPb0k+6T2K0uDbw\n" +
|
||||
"KWKduM0fwhAcVkJFsOO/o5GrbQaJc3oioFk8uFWTnO+FPBRTJ9oTlVG2M/tEatZK\n" +
|
||||
"gl7I8Ukl0YYruCNUFKZ0tvO8HqulxBgUbGPBer1uOlfUD4RXdc8/PUiFKNo48XSu\n" +
|
||||
"ZUEAZKGbFBjuX5Z8ha7+sUMEYEt70qlbkiLQxgHKAmpyridAk3q/SB3y2VB8Ik7I\n" +
|
||||
"gpExBADInzLROYuUcXqmty+znVwm6nRIB75JBAy778zgIxx1v0O3QlVnR+YI8gJM\n" +
|
||||
"mQ/9pD6LyP9hktWDmJxG8tX+kSuIp3wNJc5EMeXtCCmkUW0CP1gUhAbNW3MezKa5\n" +
|
||||
"II5IhE9RgIsYqSU8ZgeIh72ON8XTp8i/wGipCXvJPggSAMXukQQAzfRmtLW+JHEK\n" +
|
||||
"B8ETIYh8IUjXJ6TVlmuBwZ0eXjCpqy9arJi6tacesDJwnL3sqOMQWUmqGsCGSKA5\n" +
|
||||
"cLITkVsxX/htIq8GFyludjg8t4Nr+fOGfChEq8QE0PHE2CgskQMHpfHvfIdnwKve\n" +
|
||||
"Fg2Q8twoMw849O6PF3k/848Z65lDin8EAMDbuPWL7KU2sWeqvDEuoulS5K1gsq8X\n" +
|
||||
"p3Od3+f0OG8YViMjKcVlSKHVvdlK4dlsccJrJJx6VzotV47LsmvVbzDwUE//MYq7\n" +
|
||||
"QwwQetZbpdQZDysSGVqHMTuAg/1pr2u5rqh4cFqCYatgZwinEI2TQMXEqnSc+mj8\n" +
|
||||
"xp/LNq5BZZQuO4y0F3htcHA6anVsaWV0QGNhcHVsZXQubGl0iQFOBBMBCAA4FiEE\n" +
|
||||
"HQGMdy34xe+GodzJtLUJy1k24D4FAlrxov4CGy8FCwkIBwIGFQoJCAsCBBYCAwEC\n" +
|
||||
"HgECF4AACgkQtLUJy1k24D6H7AgAoTjx4ezcA83NeOY3tMHVQTM7hKuy0wMcSzQg\n" +
|
||||
"VgJmhLYRZS8r+FocPZua/eke49GPhe2yozvlByWHtotklQeJiwOKxuPKMzneVA1Z\n" +
|
||||
"K3/9LdGvtZlHMcAkEKDhit8HIaEcsFd4Z1reEhF2lyvY/E+rrx9YxV0QjisSWV2d\n" +
|
||||
"Sptv6FeGSztr9e5E+Head6hEQhsugiTVRF+16mG90te0WGQ9YNiJ2FJovx5kBLTT\n" +
|
||||
"uhwUz8Oacqihd2+RDDI5p3wJoogVL31aNb4nc7dGo8ieJPHGlkBsOfmreSxijTod\n" +
|
||||
"Zz9MXsgcx7b//u0uQryViJoZHWbtnXOFjjNcGWBtS084NKWl9w==\n" +
|
||||
"=yPPE\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String ROMEO_UID = "xmpp:romeo@montague.lit";
|
||||
public static final long ROMEO_KEY_ID = 334147643349279223L;
|
||||
|
||||
/**
|
||||
* Public key of xmpp:romeo@montague.lit.
|
||||
*/
|
||||
public static final String ROMEO_PUB = "" +
|
||||
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"\n" +
|
||||
"mQENBFrxopkBCADiYg/+mEObXgxuMW6/LFKpEyaJK9pBMgutuxnYZ9PXWZmOhDIT\n" +
|
||||
"Ugm9X9YJ3Qh94KaHge9F4uCeFASmM1vvUTRFTEb1W5RR9ZE/sy/cdAttnZ5JloPi\n" +
|
||||
"CT3HDMIJAxIXhRJkeUR9GUb51ql27bMXl6lFh865VdNSXN/B8FzRQHENxv1Bq/6Z\n" +
|
||||
"iQOViIETeRRgO+u6u2iZkYlHgYMaoMK7+YiNlHXanU9Atcuaz0ZCJS/XFNH89iqB\n" +
|
||||
"Kvnv7KCQh4FhrNMLJRzNPXV8MY05nn0zF72qeEsniB16Xde18lMro8fQehg2mLwc\n" +
|
||||
"XGtCwCKI6QbZVxYQt77r3ZACiwl66soFWijVABEBAAG0F3htcHA6cm9tZW9AbW9u\n" +
|
||||
"dGFndWUubGl0iQFOBBMBCAA4FiEENdKZ0IovfYAjCwldBKMhguBeIfcFAlrxopkC\n" +
|
||||
"Gy8FCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQBKMhguBeIfcj8AgAu1wubUwr\n" +
|
||||
"2aQmDN3OqRM4M4yRL3oyYMkCKIjqD6KEeFsIXSSkXOuREJKEo8Mb1+ewV0SYmHCC\n" +
|
||||
"K3bKKq3m71AQ7evDhKGshacPYesiDvMdHWQdQnjfaoHhyn9qIKl7H0Xv1yf/wyuG\n" +
|
||||
"ANy1jYgtCEuYw7D+EsqNDdn8Xh+k/9s4aMI/6mfC0yGZgG8EyLTfbZkGPoS4aZfV\n" +
|
||||
"AGFbuqryg48dXtnuzAPKcdgMTTMSnmR729YlfkjCffcFaldyXoe1VMbudUO7nkO9\n" +
|
||||
"g65i5EXenkbc2h0TRDQ4lDFQyModqFTwYFYxAf/RA6tuhIQEoCnpCytFMvrRKMb3\n" +
|
||||
"Bx5vYRDVmE3jeg==\n" +
|
||||
"=2jSg\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
/**
|
||||
* Private key of xmpp:romeo@montague.lit.
|
||||
*/
|
||||
public static final String ROMEO_SEC = "" +
|
||||
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"\n" +
|
||||
"lQOYBFrxopkBCADiYg/+mEObXgxuMW6/LFKpEyaJK9pBMgutuxnYZ9PXWZmOhDIT\n" +
|
||||
"Ugm9X9YJ3Qh94KaHge9F4uCeFASmM1vvUTRFTEb1W5RR9ZE/sy/cdAttnZ5JloPi\n" +
|
||||
"CT3HDMIJAxIXhRJkeUR9GUb51ql27bMXl6lFh865VdNSXN/B8FzRQHENxv1Bq/6Z\n" +
|
||||
"iQOViIETeRRgO+u6u2iZkYlHgYMaoMK7+YiNlHXanU9Atcuaz0ZCJS/XFNH89iqB\n" +
|
||||
"Kvnv7KCQh4FhrNMLJRzNPXV8MY05nn0zF72qeEsniB16Xde18lMro8fQehg2mLwc\n" +
|
||||
"XGtCwCKI6QbZVxYQt77r3ZACiwl66soFWijVABEBAAEAB/4mu5p69/hRQ+UikWie\n" +
|
||||
"Yun9rZ4hSBR+pR5kaifA4/rV1Km2PZ4HujiaYyRO6beDOgWkF7IlpezCfzBQc2ce\n" +
|
||||
"ailkVemqHzIgV8CzQmhE8sHlzlr/wjXsXaJpRSCJxDG7PnRoJmt2b/W512WFSKQk\n" +
|
||||
"vDklAVh4U1vlsqhCGWr4DmuJbJkRyDhcX01tplRwim283F7bGqRcMBmKMZHiMgVc\n" +
|
||||
"0u84EYKKVizJ3YAaaVqZyHb4qdeKK2ak3fPNuGT/oGd2sxnkL+BZGjJpu3RGpTA1\n" +
|
||||
"tbOvOQnJGHQtABFxE8n6H9dHPJGtgyz2+udjUhL/P/E3PDoXazZkXRq2oHZKgg0f\n" +
|
||||
"AwOBBADsWncHgvz15rXPF7O6AivbGTJ5ctkgVy4U3Fu2sk9rf0fx0sryBSqtTBw1\n" +
|
||||
"Uvn/p9RwTsKw6fng6Nf78xpZFlUDB00YCcuWkGodxvjTAyB0dtBmkhopeKi0dmHh\n" +
|
||||
"ndnR6Pv0CsXu8nG7lUi+q6s3oc4h2OfDBhrqsyYY5M2gGit3dQQA9TNuinJD9XXv\n" +
|
||||
"QRyauMnSJ5xRcfOu8QCxZlllCvffZjSGCPoVjUpJEe9qsVbXVj2GYCxjLCSXV0V+\n" +
|
||||
"vlJfdPrl1BhZ3fmEpg0u7SyGDDOe8fe1ehk5sAeL8O0eFWlPSEaEccsjlpJ2FO0n\n" +
|
||||
"P04SZdOeM6wmhDTEDzpFnjbPndQTH+ED/R1zNzr55DvxQodmrW/BvTmhGQ22rHtk\n" +
|
||||
"IUfbeMaVfUvNLJA/JksrUIx3Gga9QCDZgfm1RsRhLUlHiqTQe23sPWgKOsbf5O1j\n" +
|
||||
"XJZaCNZ7LloVQbkG7xFcnb/n1+JjBr4FxXjAA6cY/iRGlznjIIaasyklKm1/4LuQ\n" +
|
||||
"hnH3QqTvCN3dOFS0F3htcHA6cm9tZW9AbW9udGFndWUubGl0iQFOBBMBCAA4FiEE\n" +
|
||||
"NdKZ0IovfYAjCwldBKMhguBeIfcFAlrxopkCGy8FCwkIBwIGFQoJCAsCBBYCAwEC\n" +
|
||||
"HgECF4AACgkQBKMhguBeIfcj8AgAu1wubUwr2aQmDN3OqRM4M4yRL3oyYMkCKIjq\n" +
|
||||
"D6KEeFsIXSSkXOuREJKEo8Mb1+ewV0SYmHCCK3bKKq3m71AQ7evDhKGshacPYesi\n" +
|
||||
"DvMdHWQdQnjfaoHhyn9qIKl7H0Xv1yf/wyuGANy1jYgtCEuYw7D+EsqNDdn8Xh+k\n" +
|
||||
"/9s4aMI/6mfC0yGZgG8EyLTfbZkGPoS4aZfVAGFbuqryg48dXtnuzAPKcdgMTTMS\n" +
|
||||
"nmR729YlfkjCffcFaldyXoe1VMbudUO7nkO9g65i5EXenkbc2h0TRDQ4lDFQyMod\n" +
|
||||
"qFTwYFYxAf/RA6tuhIQEoCnpCytFMvrRKMb3Bx5vYRDVmE3jeg==\n" +
|
||||
"=LZ1b\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static PGPSecretKeyRing getJulietSecretKeyRing() throws IOException, PGPException {
|
||||
if (julietSecretKeyRing == null) {
|
||||
julietSecretKeyRing = new PGPSecretKeyRing(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(JULIET_SEC.getBytes())), calc);
|
||||
}
|
||||
return julietSecretKeyRing;
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRingCollection getJulietSecretKeyRingCollection() throws IOException, PGPException {
|
||||
if (julietSecretKeyRingCollection == null) {
|
||||
julietSecretKeyRingCollection = new PGPSecretKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(JULIET_SEC.getBytes())), calc);
|
||||
}
|
||||
return julietSecretKeyRingCollection;
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRing getJulietPublicKeyRing() throws IOException {
|
||||
if (julietPublicKeyRing == null) {
|
||||
julietPublicKeyRing = new PGPPublicKeyRing(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(JULIET_PUB.getBytes())), calc);
|
||||
}
|
||||
return julietPublicKeyRing;
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRingCollection getJulietPublicKeyRingCollection() throws IOException, PGPException {
|
||||
if (julietPublicKeyRingCollection == null) {
|
||||
julietPublicKeyRingCollection = new PGPPublicKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(JULIET_PUB.getBytes())), calc);
|
||||
}
|
||||
return julietPublicKeyRingCollection;
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRing getRomeoSecretKeyRing() throws IOException, PGPException {
|
||||
if (romeoSecretKeyRing == null) {
|
||||
romeoSecretKeyRing = new PGPSecretKeyRing(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(ROMEO_SEC.getBytes())), calc);
|
||||
}
|
||||
return romeoSecretKeyRing;
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRingCollection getRomeoSecretKeyRingCollection() throws IOException, PGPException {
|
||||
if (romeoSecretKeyRingCollection == null) {
|
||||
romeoSecretKeyRingCollection = new PGPSecretKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(ROMEO_SEC.getBytes())), calc);
|
||||
}
|
||||
return romeoSecretKeyRingCollection;
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRing getRomeoPublicKeyRing() throws IOException {
|
||||
if (romeoPublicKeyRing == null) {
|
||||
romeoPublicKeyRing = new PGPPublicKeyRing(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(ROMEO_PUB.getBytes())), calc);
|
||||
}
|
||||
return romeoPublicKeyRing;
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRingCollection getRomeoPublicKeyRingCollection() throws IOException, PGPException {
|
||||
if (romeoPublicKeyRingCollection == null) {
|
||||
romeoPublicKeyRingCollection = new PGPPublicKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(new ByteArrayInputStream(ROMEO_PUB.getBytes())), calc);
|
||||
}
|
||||
return romeoPublicKeyRingCollection;
|
||||
}
|
||||
|
||||
public static final String TEST_MESSAGE_01_PLAIN = "This message is encrypted\n";
|
||||
|
||||
/**
|
||||
* Test Message signed with {@link #JULIET_SEC} and encrypted for {@link #JULIET_PUB}.
|
||||
*/
|
||||
public static final String TEST_MESSAGE_01 = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"hQGMAwAAAAAAAAAAAQwAoJtfpcBPCwhUzzHuVIcBzBLyfIWT/EJ527neb46lN56S\n" +
|
||||
"B05BTIRudIeCsPYz81jwiFi/k0MBecRfozZ1xCPByo8ohSvRgzEHEkCNgObQ1bz0\n" +
|
||||
"iB+Xb76OEzFOCPUebTaVscLNf8ak/GSzaW7jDc+5vnvDf7cV0x26pe4odpS/U5Tr\n" +
|
||||
"cO3wb/47K+sJ1cxJmPtcD41O02xu3QisQKPrimM0Kue6ziGeKyw1RkSowv9U47TK\n" +
|
||||
"wppPCHOTli2Nf+gZizF1oyQZzPGst4fjujygcIoajplfW9nZvxsbmYRSLSdmV9m6\n" +
|
||||
"k1jQbPDUhVs0gstH92C6hPpoBWxoxkHcwz8gy36nCyB6cYGyq3oN1UnGU4afPyD5\n" +
|
||||
"SmmEjELBd2i2Ll/DYk2x06SnKZMQuWrSCZzWgl/9HsPo5ydVb97OjuEpWtW9xDMA\n" +
|
||||
"KlYPNWEq+b+akOEstNraC3pfVKvypz6ZzaMAS1gWWNYg8dlwBJOUVMSo7iLaUQkK\n" +
|
||||
"yp4uH1DlsyVu1atCUc8thQIMAwAAAAAAAAAAAQ/5AdiZ/sG859Y/rGR7U/8MzGg0\n" +
|
||||
"j3f2vrgDF/0NRRk5aqd1lb4CaZvrztcYqW3cEK7iF9rKwImZZiWIptjJ9Mz6f1Zl\n" +
|
||||
"FbODObSVRZAcZqYGswEEfsQvpQFlwG6Qx48OaQaDPr147raFI3C3kEU9Nb2VBg8+\n" +
|
||||
"MevJaXJft5PXwUTG2Qvfxqr/3hfGAwB4/zHwA8vFd1np3spryfrC9Dq8UXUoRXIS\n" +
|
||||
"xaFPiLEYt8rLef8f11OypEpmknIibu9jjJtuVZo+SjP6jgLHDwM7rqCZFITM2Qra\n" +
|
||||
"2iBCt8YVcIiTK137t+EfsdVN/KHiRbc++e9zUbGMEextbtNbdoFOU4dnKBm6Su8l\n" +
|
||||
"Z5UerNbR8D7+xJKfAEabdi0qI7QFmhTZ/4H/22yrvoD9jMFSBXUTE9ENIX9Hfqom\n" +
|
||||
"UdsHfuE+5PC0JjkZkhchDO1M7XBX++lBCFsq2abfdpmaX+roVX0iTGboxr5Ag1Cf\n" +
|
||||
"T2zWyRX/XKnvmdeGICV5qjy/ThuSWvAclazyFxWLamMztJq5BRpfAzKNQRDqlmKw\n" +
|
||||
"eePtKW2EWUIjFQ5/UAM6Edu/K34ksFxb0w6YGLzQSskGr7gGAipLmpek6vcUSUA1\n" +
|
||||
"oc9XJGdpx93GDRcqDjKDt/ej06VxG33/pW65ntf5QM/+LScGqaLhAHyEOsBzVIXY\n" +
|
||||
"BONcadSgzkTrlbSMGAmFAQwDtLUJy1k24D4BB/0brqR0UN1LtO+Lc/vN6X/Um2CZ\n" +
|
||||
"CM6MRhPnXP63Q9HHkGJ2S8zGWvQLwWL9Y14CFCgm6rACLBSIyPbihhC2OC8afhSy\n" +
|
||||
"apGkdHtdghS2egs2U8qlJ2Y32IAG9CcUtNkRjxp+/RWSrmZeuL4l7DXCyH5lUadx\n" +
|
||||
"5bPZhAHqW9408q2rQd9dBg2o7ciGXTJSKVahjuiB/O0gchOnbqnlYJbKbCkntXUo\n" +
|
||||
"c7h4w1e8MutisSJorh7kbxgxUJSboZzEkiUfnoacPTz6bL+re9tmnpvlee70sIyM\n" +
|
||||
"BiYRCyPw7Ice4R3XyWtsMTjT/wjZ//whMpWdy2drcJSyhh+GQMbekTVsNWod0lQB\n" +
|
||||
"JTPUfti2VU7PMB3LjJA+l/T9iWPPx8lirnLhXOOerWKH9I5Wo4Kqv/47aJhfMO6+\n" +
|
||||
"jmLekAOylq+9DizrslW/EUgQyjIbcWfmyMiV6E2RwbI93tE=\n" +
|
||||
"=GAhR\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.Test;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
public class TestKeysTest extends AbstractPGPainlessTest {
|
||||
|
||||
private final PGPSecretKeyRing juliet;
|
||||
private final PGPSecretKeyRing romeo;
|
||||
|
||||
public TestKeysTest() throws IOException, PGPException {
|
||||
this.juliet = TestKeys.getJulietSecretKeyRing();
|
||||
this.romeo = TestKeys.getRomeoSecretKeyRing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyIdTest() {
|
||||
TestCase.assertEquals(TestKeys.JULIET_KEY_ID, juliet.getSecretKey().getKeyID());
|
||||
TestCase.assertEquals(TestKeys.ROMEO_KEY_ID, romeo.getSecretKey().getKeyID());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptVerifyTest() throws Exception {
|
||||
String encryptedMessage = TestKeys.TEST_MESSAGE_01;
|
||||
|
||||
DecryptionStream decryptor = PGPainless.createDecryptor()
|
||||
.onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes()))
|
||||
.decryptWith(new UnprotectedKeysProtector(), new PGPSecretKeyRingCollection(Collections.singleton(juliet)))
|
||||
.verifyWith(Collections.singleton(new PGPPublicKeyRing(Collections.singletonList(juliet.getPublicKey()))))
|
||||
.ignoreMissingPublicKeys()
|
||||
.build();
|
||||
|
||||
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptor, toPlain);
|
||||
decryptor.close();
|
||||
toPlain.close();
|
||||
|
||||
byte[] expected = TestKeys.TEST_MESSAGE_01_PLAIN.getBytes(Charset.forName("UTF-8"));
|
||||
byte[] actual = toPlain.toByteArray();
|
||||
|
||||
assertTrue(Arrays.equals(expected, actual));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue