334 lines
13 KiB
Java
334 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.service;
|
|
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
import android.os.Parcelable;
|
|
import android.support.annotation.Nullable;
|
|
|
|
import com.google.auto.value.AutoValue;
|
|
import org.bouncycastle.bcpg.sig.KeyFlags;
|
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
|
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
|
|
|
/**
|
|
* This class is a a transferable representation for a collection of changes
|
|
* to be done on a keyring.
|
|
* <p/>
|
|
* This class should include all types of operations supported in the backend.
|
|
* <p/>
|
|
* All changes are done in a differential manner. Besides the two key
|
|
* identification attributes, all attributes may be null, which indicates no
|
|
* change to the keyring. This is also the reason why boxed values are used
|
|
* instead of primitives in the subclasses.
|
|
* <p/>
|
|
* Application of operations in the backend should be fail-fast, which means an
|
|
* error in any included operation (for example revocation of a non-existent
|
|
* subkey) will cause the operation as a whole to fail.
|
|
*/
|
|
@AutoValue
|
|
public abstract class SaveKeyringParcel implements Parcelable {
|
|
|
|
// the master key id to be edited. if this is null, a new one will be created
|
|
@Nullable
|
|
public abstract Long getMasterKeyId();
|
|
// the key fingerprint, for safety. MUST be null for a new key.
|
|
@Nullable
|
|
@SuppressWarnings("mutable")
|
|
public abstract byte[] getFingerprint();
|
|
|
|
public abstract List<String> getAddUserIds();
|
|
public abstract List<WrappedUserAttribute> getAddUserAttribute();
|
|
public abstract List<SubkeyAdd> getAddSubKeys();
|
|
|
|
public abstract List<SubkeyChange> getChangeSubKeys();
|
|
@Nullable
|
|
public abstract String getChangePrimaryUserId();
|
|
|
|
public abstract List<String> getRevokeUserIds();
|
|
public abstract List<Long> getRevokeSubKeys();
|
|
|
|
// if these are non-null, PINs will be changed on the token
|
|
@Nullable
|
|
public abstract Passphrase getSecurityTokenPin();
|
|
@Nullable
|
|
public abstract Passphrase getSecurityTokenAdminPin();
|
|
|
|
public abstract boolean isShouldUpload();
|
|
public abstract boolean isShouldUploadAtomic();
|
|
@Nullable
|
|
public abstract HkpKeyserverAddress getUploadKeyserver();
|
|
|
|
@Nullable
|
|
public abstract ChangeUnlockParcel getNewUnlock();
|
|
|
|
public static Builder buildNewKeyringParcel() {
|
|
return new AutoValue_SaveKeyringParcel.Builder()
|
|
.setShouldUpload(false)
|
|
.setShouldUploadAtomic(false);
|
|
}
|
|
|
|
public static Builder buildChangeKeyringParcel(long masterKeyId, byte[] fingerprint) {
|
|
return buildNewKeyringParcel()
|
|
.setMasterKeyId(masterKeyId)
|
|
.setFingerprint(fingerprint);
|
|
}
|
|
|
|
abstract Builder toBuilder();
|
|
|
|
public static Builder buildUpon(SaveKeyringParcel saveKeyringParcel) {
|
|
SaveKeyringParcel.Builder builder = saveKeyringParcel.toBuilder();
|
|
builder.addUserIds.addAll(saveKeyringParcel.getAddUserIds());
|
|
builder.revokeUserIds.addAll(saveKeyringParcel.getRevokeUserIds());
|
|
builder.addUserAttribute.addAll(saveKeyringParcel.getAddUserAttribute());
|
|
builder.addSubKeys.addAll(saveKeyringParcel.getAddSubKeys());
|
|
builder.changeSubKeys.addAll(saveKeyringParcel.getChangeSubKeys());
|
|
builder.revokeSubKeys.addAll(saveKeyringParcel.getRevokeSubKeys());
|
|
return builder;
|
|
}
|
|
|
|
@AutoValue.Builder
|
|
public static abstract class Builder {
|
|
private ArrayList<String> addUserIds = new ArrayList<>();
|
|
private ArrayList<String> revokeUserIds = new ArrayList<>();
|
|
private ArrayList<WrappedUserAttribute> addUserAttribute = new ArrayList<>();
|
|
private ArrayList<SubkeyAdd> addSubKeys = new ArrayList<>();
|
|
private ArrayList<SubkeyChange> changeSubKeys = new ArrayList<>();
|
|
private ArrayList<Long> revokeSubKeys = new ArrayList<>();
|
|
|
|
|
|
public abstract Builder setChangePrimaryUserId(String changePrimaryUserId);
|
|
public abstract Builder setSecurityTokenPin(Passphrase securityTokenPin);
|
|
public abstract Builder setSecurityTokenAdminPin(Passphrase securityTokenAdminPin);
|
|
public abstract Builder setNewUnlock(ChangeUnlockParcel newUnlock);
|
|
|
|
public abstract Long getMasterKeyId();
|
|
public abstract byte[] getFingerprint();
|
|
public abstract String getChangePrimaryUserId();
|
|
|
|
|
|
public ArrayList<SubkeyAdd> getMutableAddSubKeys() {
|
|
return addSubKeys;
|
|
}
|
|
public ArrayList<String> getMutableAddUserIds() {
|
|
return addUserIds;
|
|
}
|
|
public ArrayList<Long> getMutableRevokeSubKeys() {
|
|
return revokeSubKeys;
|
|
}
|
|
public ArrayList<String> getMutableRevokeUserIds() {
|
|
return revokeUserIds;
|
|
}
|
|
|
|
abstract Builder setMasterKeyId(Long masterKeyId);
|
|
abstract Builder setFingerprint(byte[] fingerprint);
|
|
abstract Builder setAddUserIds(List<String> addUserIds);
|
|
abstract Builder setAddUserAttribute(List<WrappedUserAttribute> addUserAttribute);
|
|
abstract Builder setAddSubKeys(List<SubkeyAdd> addSubKeys);
|
|
abstract Builder setChangeSubKeys(List<SubkeyChange> changeSubKeys);
|
|
abstract Builder setRevokeUserIds(List<String> revokeUserIds);
|
|
abstract Builder setRevokeSubKeys(List<Long> revokeSubKeys);
|
|
abstract Builder setShouldUpload(boolean upload);
|
|
abstract Builder setShouldUploadAtomic(boolean uploadAtomic);
|
|
abstract Builder setUploadKeyserver(HkpKeyserverAddress keyserver);
|
|
|
|
public void setUpdateOptions(boolean upload, boolean uploadAtomic, HkpKeyserverAddress keyserver) {
|
|
setShouldUpload(upload);
|
|
setShouldUploadAtomic(uploadAtomic);
|
|
setUploadKeyserver(keyserver);
|
|
}
|
|
|
|
public void addSubkeyAdd(SubkeyAdd subkeyAdd) {
|
|
addSubKeys.add(subkeyAdd);
|
|
}
|
|
|
|
public void addUserId(String userId) {
|
|
addUserIds.add(userId);
|
|
}
|
|
|
|
public void addRevokeSubkey(long masterKeyId) {
|
|
revokeSubKeys.add(masterKeyId);
|
|
}
|
|
|
|
public void removeRevokeSubkey(long keyId) {
|
|
revokeSubKeys.remove(keyId);
|
|
}
|
|
|
|
public void addRevokeUserId(String userId) {
|
|
revokeUserIds.add(userId);
|
|
}
|
|
|
|
public void removeRevokeUserId(String userId) {
|
|
revokeUserIds.remove(userId);
|
|
}
|
|
|
|
public void addOrReplaceSubkeyChange(SubkeyChange newChange) {
|
|
SubkeyChange foundSubkeyChange = getSubkeyChange(newChange.getSubKeyId());
|
|
|
|
if (foundSubkeyChange != null) {
|
|
changeSubKeys.remove(foundSubkeyChange);
|
|
}
|
|
changeSubKeys.add(newChange);
|
|
}
|
|
|
|
public void removeSubkeyChange(SubkeyChange change) {
|
|
changeSubKeys.remove(change);
|
|
}
|
|
|
|
public SubkeyChange getSubkeyChange(long keyId) {
|
|
if (changeSubKeys == null) {
|
|
return null;
|
|
}
|
|
|
|
for (SubkeyChange subkeyChange : changeSubKeys) {
|
|
if (subkeyChange.getSubKeyId() == keyId) {
|
|
return subkeyChange;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void addUserAttribute(WrappedUserAttribute ua) {
|
|
addUserAttribute.add(ua);
|
|
}
|
|
|
|
abstract SaveKeyringParcel autoBuild();
|
|
|
|
public SaveKeyringParcel build() {
|
|
setAddUserAttribute(Collections.unmodifiableList(addUserAttribute));
|
|
setRevokeSubKeys(Collections.unmodifiableList(revokeSubKeys));
|
|
setRevokeUserIds(Collections.unmodifiableList(revokeUserIds));
|
|
setAddSubKeys(Collections.unmodifiableList(addSubKeys));
|
|
setAddUserIds(Collections.unmodifiableList(addUserIds));
|
|
setChangeSubKeys(Collections.unmodifiableList(changeSubKeys));
|
|
|
|
return autoBuild();
|
|
}
|
|
|
|
public boolean hasModificationsForSubkey(long keyId) {
|
|
return revokeSubKeys.contains(keyId) || getSubkeyChange(keyId) != null;
|
|
}
|
|
|
|
public void removeModificationsForSubkey(long keyId) {
|
|
revokeSubKeys.remove(keyId);
|
|
SubkeyChange subkeyChange = getSubkeyChange(keyId);
|
|
if (subkeyChange != null) {
|
|
changeSubKeys.remove(subkeyChange);
|
|
}
|
|
}
|
|
}
|
|
|
|
// performance gain for using Parcelable here would probably be negligible,
|
|
// use Serializable instead.
|
|
@AutoValue
|
|
public abstract static class SubkeyAdd implements Serializable {
|
|
public abstract Algorithm getAlgorithm();
|
|
@Nullable
|
|
public abstract Integer getKeySize();
|
|
@Nullable
|
|
public abstract Curve getCurve();
|
|
public abstract int getFlags();
|
|
@Nullable
|
|
public abstract Long getExpiry();
|
|
|
|
public static SubkeyAdd createSubkeyAdd(Algorithm algorithm, Integer keySize, Curve curve, int flags,
|
|
Long expiry) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyAdd(algorithm, keySize, curve, flags, expiry);
|
|
}
|
|
|
|
public boolean canCertify() {
|
|
return (getFlags() & KeyFlags.CERTIFY_OTHER) > 0;
|
|
}
|
|
|
|
public boolean canSign() {
|
|
return (getFlags() & KeyFlags.SIGN_DATA) > 0;
|
|
}
|
|
|
|
public boolean canEncrypt() {
|
|
return ((getFlags() & KeyFlags.ENCRYPT_COMMS) > 0) || ((getFlags() & KeyFlags.ENCRYPT_STORAGE) > 0);
|
|
}
|
|
|
|
public boolean canAuthenticate() {
|
|
return (getFlags() & KeyFlags.AUTHENTICATION) > 0;
|
|
}
|
|
}
|
|
|
|
@AutoValue
|
|
public abstract static class SubkeyChange implements Serializable {
|
|
public abstract long getSubKeyId();
|
|
@Nullable
|
|
public abstract Integer getFlags();
|
|
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
|
@Nullable
|
|
public abstract Long getExpiry();
|
|
// if this flag is true, the key will be recertified even if all above
|
|
// values are no-ops
|
|
public abstract boolean getRecertify();
|
|
// if this flag is true, the subkey should be changed to a stripped key
|
|
public abstract boolean getDummyStrip();
|
|
// if this flag is true, the subkey should be moved to a security token
|
|
public abstract boolean getMoveKeyToSecurityToken();
|
|
// if this is non-null, the subkey will be changed to a divert-to-card
|
|
// (security token) key for the given serial number
|
|
@Nullable
|
|
@SuppressWarnings("mutable")
|
|
public abstract byte[] getSecurityTokenSerialNo();
|
|
|
|
public static SubkeyChange createRecertifyChange(long keyId, boolean recertify) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyChange(keyId, null, null, recertify, false, false, null);
|
|
}
|
|
|
|
public static SubkeyChange createFlagsOrExpiryChange(long keyId, Integer flags, Long expiry) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyChange(keyId, flags, expiry, false, false, false, null);
|
|
}
|
|
|
|
public static SubkeyChange createStripChange(long keyId) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyChange(keyId, null, null, false, true, false, null);
|
|
}
|
|
|
|
public static SubkeyChange createMoveToSecurityTokenChange(long keyId) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyChange(keyId, null, null, false, false, true, null);
|
|
}
|
|
|
|
public static SubkeyChange createSecurityTokenSerialNo(long keyId, byte[] securityTokenSerialNo) {
|
|
return new AutoValue_SaveKeyringParcel_SubkeyChange(keyId, null, null, false, false, false, securityTokenSerialNo);
|
|
}
|
|
}
|
|
|
|
// All supported algorithms
|
|
public enum Algorithm {
|
|
RSA, DSA, ELGAMAL, ECDSA, ECDH, EDDSA
|
|
}
|
|
|
|
// All curves defined in the standard
|
|
// http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269
|
|
public enum Curve {
|
|
NIST_P256, NIST_P384, NIST_P521,
|
|
|
|
// these are supported by gpg, but they are not in rfc6637 and not supported by BouncyCastle yet
|
|
// (adding support would be trivial though -> JcaPGPKeyConverter.java:190)
|
|
// BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512
|
|
}
|
|
}
|